chalk-remote-call-python 1.10.5__cp315-cp315-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chalk_remote_call/__init__.py +4 -0
- chalk_remote_call/__main__.py +3 -0
- chalk_remote_call/_gen/__init__.py +0 -0
- chalk_remote_call/_gen/chalk/__init__.py +0 -0
- chalk_remote_call/_gen/chalk/auth/__init__.py +0 -0
- chalk_remote_call/_gen/chalk/auth/v1/__init__.py +0 -0
- chalk_remote_call/_gen/chalk/auth/v1/permissions_pb2.py +115 -0
- chalk_remote_call/_gen/chalk/auth/v1/permissions_pb2_grpc.py +4 -0
- chalk_remote_call/_gen/chalk/common/__init__.py +0 -0
- chalk_remote_call/_gen/chalk/common/v1/__init__.py +0 -0
- chalk_remote_call/_gen/chalk/common/v1/chalk_error_pb2.py +33 -0
- chalk_remote_call/_gen/chalk/common/v1/chalk_error_pb2_grpc.py +4 -0
- chalk_remote_call/_gen/chalk/runtime/__init__.py +0 -0
- chalk_remote_call/_gen/chalk/runtime/v1/__init__.py +0 -0
- chalk_remote_call/_gen/chalk/runtime/v1/fnq_consumer_pb2.py +53 -0
- chalk_remote_call/_gen/chalk/runtime/v1/fnq_consumer_pb2_grpc.py +138 -0
- chalk_remote_call/_gen/chalk/runtime/v1/remote_python_call_pb2.py +104 -0
- chalk_remote_call/_gen/chalk/runtime/v1/remote_python_call_pb2_grpc.py +363 -0
- chalk_remote_call/_gen/chalk/utils/__init__.py +0 -0
- chalk_remote_call/_gen/chalk/utils/v1/__init__.py +0 -0
- chalk_remote_call/_gen/chalk/utils/v1/encoding_pb2.py +32 -0
- chalk_remote_call/_gen/chalk/utils/v1/encoding_pb2_grpc.py +4 -0
- chalk_remote_call/_gen/chalk/utils/v1/field_change_pb2.py +29 -0
- chalk_remote_call/_gen/chalk/utils/v1/field_change_pb2_grpc.py +4 -0
- chalk_remote_call/_gen/chalk/utils/v1/large_pb2.py +26 -0
- chalk_remote_call/_gen/chalk/utils/v1/large_pb2_grpc.py +4 -0
- chalk_remote_call/_gen/chalk/utils/v1/sensitive_pb2.py +26 -0
- chalk_remote_call/_gen/chalk/utils/v1/sensitive_pb2_grpc.py +4 -0
- chalk_remote_call/_native.cp315-win_amd64.pyd +0 -0
- chalk_remote_call/_native.pyi +13 -0
- chalk_remote_call/_version.py +1 -0
- chalk_remote_call/arrow_utils.py +21 -0
- chalk_remote_call/cli.py +160 -0
- chalk_remote_call/errors.py +118 -0
- chalk_remote_call/handler_loader.py +57 -0
- chalk_remote_call/input_transform.py +50 -0
- chalk_remote_call/logging.py +29 -0
- chalk_remote_call/server.py +117 -0
- chalk_remote_call/servicer.py +400 -0
- chalk_remote_call/tracing.py +445 -0
- chalk_remote_call_python-1.10.5.dist-info/METADATA +288 -0
- chalk_remote_call_python-1.10.5.dist-info/RECORD +45 -0
- chalk_remote_call_python-1.10.5.dist-info/WHEEL +5 -0
- chalk_remote_call_python-1.10.5.dist-info/entry_points.txt +2 -0
- chalk_remote_call_python-1.10.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import atexit
|
|
4
|
+
import contextlib
|
|
5
|
+
import importlib
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import threading
|
|
9
|
+
from collections.abc import Callable, Iterator, Mapping, Sequence
|
|
10
|
+
from typing import Any, cast
|
|
11
|
+
|
|
12
|
+
_TRACER_NAME = "chalk_remote_call"
|
|
13
|
+
_TRACE_CONTEXT_METADATA_KEYS = ("traceparent", "tracestate", "baggage")
|
|
14
|
+
_REMOTE_FUNCTION_TRACE_POLICY_ENV_VAR = "CHALK_REMOTE_FUNCTION_TRACE_POLICY"
|
|
15
|
+
_REMOTE_FUNCTION_TRACE_SAMPLE_RATE_ENV_VAR = "CHALK_REMOTE_FUNCTION_TRACE_SAMPLE_RATE"
|
|
16
|
+
_REMOTE_FUNCTION_TRACE_OVERRIDE_KEY = "x-chalk-remote-function-tracing"
|
|
17
|
+
_TRACE_OVERRIDE_ON = "on"
|
|
18
|
+
_TRACE_OVERRIDE_OFF = "off"
|
|
19
|
+
_TRACE_POLICY_PARENT_BASED_ALWAYS_OFF = "parentbased_always_off"
|
|
20
|
+
_TRACE_POLICY_PARENT_BASED_TRACE_ID_RATIO = "parentbased_traceidratio"
|
|
21
|
+
_TRACE_POLICY_ALWAYS_OFF = "always_off"
|
|
22
|
+
_TRACE_POLICIES = frozenset(
|
|
23
|
+
{
|
|
24
|
+
_TRACE_POLICY_PARENT_BASED_ALWAYS_OFF,
|
|
25
|
+
_TRACE_POLICY_PARENT_BASED_TRACE_ID_RATIO,
|
|
26
|
+
_TRACE_POLICY_ALWAYS_OFF,
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
_missing_otel_modules = object()
|
|
30
|
+
_missing_always_off_tracer = object()
|
|
31
|
+
_otel_modules: tuple[Any, Any, Any] | object | None = None
|
|
32
|
+
_always_off_tracer: Any | object | None = None
|
|
33
|
+
_runtime_tracing_configured = False
|
|
34
|
+
_runtime_tracer_provider: Any | None = None
|
|
35
|
+
_runtime_span_flusher: _BackgroundSpanFlusher | None = None
|
|
36
|
+
# Bounds each background flush so a stalled collector cannot wedge the flush thread.
|
|
37
|
+
_SPAN_FLUSH_TIMEOUT_MILLIS = 5000
|
|
38
|
+
|
|
39
|
+
_logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class _BackgroundSpanFlusher:
|
|
43
|
+
"""Runs a tracer provider's ``force_flush`` on one daemon thread, off the request path.
|
|
44
|
+
|
|
45
|
+
Function pods outlive invocations, so without a flush an invocation's ended spans wait
|
|
46
|
+
for the batch processor's schedule delay before export. Requests coalesce: every request
|
|
47
|
+
made before a flush starts is satisfied by that flush, while a request made after a flush
|
|
48
|
+
starts schedules another, because spans ended after the flush began may have missed it.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, flush: Callable[[int], object], timeout_millis: int) -> None:
|
|
52
|
+
super().__init__()
|
|
53
|
+
self._flush = flush
|
|
54
|
+
self._timeout_millis = timeout_millis
|
|
55
|
+
self._cond = threading.Condition()
|
|
56
|
+
# Monotonic counters: requests made, the request count covered by the latest flush to
|
|
57
|
+
# start, and the request count covered by the latest flush to finish.
|
|
58
|
+
self._requested = 0
|
|
59
|
+
self._started = 0
|
|
60
|
+
self._completed = 0
|
|
61
|
+
self._thread: threading.Thread | None = None
|
|
62
|
+
|
|
63
|
+
def request(self) -> None:
|
|
64
|
+
with self._cond:
|
|
65
|
+
self._requested += 1
|
|
66
|
+
# is_alive (rather than None) also restarts the worker in a forked child.
|
|
67
|
+
if self._thread is None or not self._thread.is_alive():
|
|
68
|
+
self._thread = threading.Thread(
|
|
69
|
+
target=self._run,
|
|
70
|
+
name="chalk-remote-call-span-flush",
|
|
71
|
+
daemon=True,
|
|
72
|
+
)
|
|
73
|
+
self._thread.start()
|
|
74
|
+
self._cond.notify_all()
|
|
75
|
+
|
|
76
|
+
def wait_until_idle(self, timeout: float) -> bool:
|
|
77
|
+
"""Block until every request made so far is covered by a finished flush."""
|
|
78
|
+
with self._cond:
|
|
79
|
+
return self._cond.wait_for(lambda: self._completed >= self._requested, timeout)
|
|
80
|
+
|
|
81
|
+
def _run(self) -> None:
|
|
82
|
+
while True:
|
|
83
|
+
with self._cond:
|
|
84
|
+
self._cond.wait_for(lambda: self._started < self._requested)
|
|
85
|
+
target = self._requested
|
|
86
|
+
self._started = target
|
|
87
|
+
try:
|
|
88
|
+
if self._flush(self._timeout_millis) is False:
|
|
89
|
+
_logger.debug("Span flush did not finish within %d ms", self._timeout_millis)
|
|
90
|
+
except Exception:
|
|
91
|
+
_logger.warning("Span flush failed", exc_info=True)
|
|
92
|
+
with self._cond:
|
|
93
|
+
self._completed = target
|
|
94
|
+
self._cond.notify_all()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _request_span_flush() -> None:
|
|
98
|
+
flusher = _runtime_span_flusher
|
|
99
|
+
if flusher is None:
|
|
100
|
+
return
|
|
101
|
+
try:
|
|
102
|
+
flusher.request()
|
|
103
|
+
except Exception:
|
|
104
|
+
# Flushing is best effort; it must never fail the invocation it follows.
|
|
105
|
+
_logger.debug("Could not schedule span flush", exc_info=True)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _raw_metadata(context_metadata: Any) -> Mapping[str, Any] | None:
|
|
109
|
+
if not isinstance(context_metadata, Mapping):
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
raw = context_metadata.get("metadata", context_metadata)
|
|
113
|
+
if isinstance(raw, Mapping):
|
|
114
|
+
return raw
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _collect_trace_metadata(
|
|
119
|
+
context_metadata: Any,
|
|
120
|
+
) -> tuple[Mapping[str, Any] | None, Mapping[str, Any] | None]:
|
|
121
|
+
if isinstance(context_metadata, Mapping):
|
|
122
|
+
first = _raw_metadata(context_metadata)
|
|
123
|
+
if first is not None and first.get("traceparent"):
|
|
124
|
+
return first, first
|
|
125
|
+
return first, None
|
|
126
|
+
|
|
127
|
+
first: Mapping[str, Any] | None = None
|
|
128
|
+
if isinstance(context_metadata, Sequence) and not isinstance(context_metadata, str | bytes):
|
|
129
|
+
for item in context_metadata:
|
|
130
|
+
metadata = _raw_metadata(item)
|
|
131
|
+
if metadata is None:
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
if first is None:
|
|
135
|
+
first = metadata
|
|
136
|
+
if metadata.get("traceparent"):
|
|
137
|
+
return first, metadata
|
|
138
|
+
|
|
139
|
+
return first, None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _trace_policy() -> str | None:
|
|
143
|
+
raw_policy = os.environ.get(_REMOTE_FUNCTION_TRACE_POLICY_ENV_VAR)
|
|
144
|
+
if raw_policy is None:
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
policy = raw_policy.strip().lower()
|
|
148
|
+
if policy in _TRACE_POLICIES:
|
|
149
|
+
return policy
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _trace_sample_rate(force_trace: bool) -> float | None:
|
|
154
|
+
if force_trace:
|
|
155
|
+
return 1.0
|
|
156
|
+
raw_rate = os.environ.get(_REMOTE_FUNCTION_TRACE_SAMPLE_RATE_ENV_VAR)
|
|
157
|
+
if raw_rate is None:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
rate = float(raw_rate)
|
|
162
|
+
except ValueError:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
if rate < 0.0 or rate > 1.0:
|
|
166
|
+
return None
|
|
167
|
+
return rate
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _otel_exporter_configured() -> bool:
|
|
171
|
+
return bool(os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT") or os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"))
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _runtime_resource_attributes() -> dict[str, str]:
|
|
175
|
+
attributes = {"service.name": os.environ.get("CHALK_SERVICE") or _TRACER_NAME}
|
|
176
|
+
if environment_id := os.environ.get("CHALK_ENVIRONMENT_ID"):
|
|
177
|
+
attributes["chalk.environment_id"] = environment_id
|
|
178
|
+
return attributes
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _configure_runtime_tracing(sample_rate: float) -> None:
|
|
182
|
+
global _runtime_tracing_configured, _runtime_tracer_provider, _runtime_span_flusher
|
|
183
|
+
if _runtime_tracing_configured:
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
resources = importlib.import_module("opentelemetry.sdk.resources")
|
|
188
|
+
sdk_trace = importlib.import_module("opentelemetry.sdk.trace")
|
|
189
|
+
sdk_sampling = importlib.import_module("opentelemetry.sdk.trace.sampling")
|
|
190
|
+
trace_api = importlib.import_module("opentelemetry.trace")
|
|
191
|
+
except Exception:
|
|
192
|
+
_runtime_tracing_configured = True
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
provider = sdk_trace.TracerProvider(
|
|
197
|
+
resource=resources.Resource.create(_runtime_resource_attributes()),
|
|
198
|
+
sampler=sdk_sampling.ParentBased(sdk_sampling.TraceIdRatioBased(sample_rate)),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if _otel_exporter_configured():
|
|
202
|
+
try:
|
|
203
|
+
otlp_exporter = importlib.import_module("opentelemetry.exporter.otlp.proto.grpc.trace_exporter")
|
|
204
|
+
sdk_export = importlib.import_module("opentelemetry.sdk.trace.export")
|
|
205
|
+
provider.add_span_processor(sdk_export.BatchSpanProcessor(otlp_exporter.OTLPSpanExporter()))
|
|
206
|
+
_runtime_span_flusher = _BackgroundSpanFlusher(provider.force_flush, _SPAN_FLUSH_TIMEOUT_MILLIS)
|
|
207
|
+
except Exception:
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
trace_api.set_tracer_provider(provider)
|
|
211
|
+
_runtime_tracer_provider = provider
|
|
212
|
+
atexit.register(provider.shutdown)
|
|
213
|
+
except Exception:
|
|
214
|
+
pass
|
|
215
|
+
_runtime_tracing_configured = True
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _sample_rate_for_inherited_parent(policy: str, force_trace: bool) -> float | None:
|
|
219
|
+
if policy == _TRACE_POLICY_PARENT_BASED_ALWAYS_OFF:
|
|
220
|
+
return 0.0
|
|
221
|
+
if policy == _TRACE_POLICY_PARENT_BASED_TRACE_ID_RATIO:
|
|
222
|
+
return _trace_sample_rate(force_trace)
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _span_metadata(
|
|
227
|
+
first_metadata: Mapping[str, Any] | None,
|
|
228
|
+
traceparent_metadata: Mapping[str, Any] | None,
|
|
229
|
+
policy: str,
|
|
230
|
+
force_trace: bool,
|
|
231
|
+
) -> tuple[Mapping[str, Any] | None, Any | None, Any]:
|
|
232
|
+
if traceparent_metadata is not None:
|
|
233
|
+
carrier = _trace_carrier(traceparent_metadata)
|
|
234
|
+
if not carrier:
|
|
235
|
+
return None, None, None
|
|
236
|
+
|
|
237
|
+
otel_modules = _get_otel_modules()
|
|
238
|
+
if otel_modules is None:
|
|
239
|
+
return None, None, None
|
|
240
|
+
_, propagate, trace = otel_modules
|
|
241
|
+
|
|
242
|
+
parent_context = propagate.extract(carrier)
|
|
243
|
+
parent_span_context = trace.get_current_span(parent_context).get_span_context()
|
|
244
|
+
if not parent_span_context.is_valid:
|
|
245
|
+
return None, None, None
|
|
246
|
+
|
|
247
|
+
sample_rate = _sample_rate_for_inherited_parent(policy, force_trace)
|
|
248
|
+
if sample_rate is None:
|
|
249
|
+
return None, None, None
|
|
250
|
+
_configure_runtime_tracing(sample_rate)
|
|
251
|
+
return traceparent_metadata, parent_context, otel_modules
|
|
252
|
+
|
|
253
|
+
if policy != _TRACE_POLICY_PARENT_BASED_TRACE_ID_RATIO:
|
|
254
|
+
return None, None, None
|
|
255
|
+
|
|
256
|
+
sample_rate = _trace_sample_rate(force_trace)
|
|
257
|
+
if sample_rate is None:
|
|
258
|
+
return None, None, None
|
|
259
|
+
|
|
260
|
+
_configure_runtime_tracing(sample_rate)
|
|
261
|
+
otel_modules = _get_otel_modules()
|
|
262
|
+
if otel_modules is None:
|
|
263
|
+
return None, None, None
|
|
264
|
+
return first_metadata, None, otel_modules
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _force_sampled_traceparent(metadata: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
268
|
+
parts = str(metadata.get("traceparent") or "").split("-")
|
|
269
|
+
if len(parts) != 4 or len(parts[3]) != 2:
|
|
270
|
+
return metadata
|
|
271
|
+
try:
|
|
272
|
+
parts[3] = f"{int(parts[3], 16) | 1:02x}"
|
|
273
|
+
except ValueError:
|
|
274
|
+
return metadata
|
|
275
|
+
return {**metadata, "traceparent": "-".join(parts)}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _always_off_span_metadata(
|
|
279
|
+
first_metadata: Mapping[str, Any] | None,
|
|
280
|
+
traceparent_metadata: Mapping[str, Any] | None,
|
|
281
|
+
) -> tuple[Mapping[str, Any] | None, Any | None, Any | None, Any | None]:
|
|
282
|
+
otel_modules = _get_otel_modules()
|
|
283
|
+
if otel_modules is None:
|
|
284
|
+
return None, None, None, None
|
|
285
|
+
_, propagate, trace = otel_modules
|
|
286
|
+
|
|
287
|
+
parent_context = None
|
|
288
|
+
if traceparent_metadata is not None:
|
|
289
|
+
carrier = _trace_carrier(traceparent_metadata)
|
|
290
|
+
if carrier:
|
|
291
|
+
extracted_context = propagate.extract(carrier)
|
|
292
|
+
parent_span_context = trace.get_current_span(extracted_context).get_span_context()
|
|
293
|
+
if parent_span_context.is_valid:
|
|
294
|
+
parent_context = extracted_context
|
|
295
|
+
|
|
296
|
+
tracer = _get_always_off_tracer()
|
|
297
|
+
if tracer is None:
|
|
298
|
+
return None, None, None, None
|
|
299
|
+
return traceparent_metadata or first_metadata, parent_context, otel_modules, tracer
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _trace_carrier(metadata: Mapping[str, Any]) -> dict[str, str]:
|
|
303
|
+
return {
|
|
304
|
+
str(key).lower(): str(value)
|
|
305
|
+
for key, value in metadata.items()
|
|
306
|
+
if str(key).lower() in _TRACE_CONTEXT_METADATA_KEYS and value
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _function_name(context_metadata: Any) -> str:
|
|
311
|
+
if isinstance(context_metadata, Mapping):
|
|
312
|
+
return str(context_metadata.get("function_name") or "") or "unknown"
|
|
313
|
+
|
|
314
|
+
if isinstance(context_metadata, Sequence) and not isinstance(context_metadata, str | bytes):
|
|
315
|
+
for item in context_metadata:
|
|
316
|
+
if isinstance(item, Mapping):
|
|
317
|
+
function_name = str(item.get("function_name") or "")
|
|
318
|
+
if function_name:
|
|
319
|
+
return function_name
|
|
320
|
+
|
|
321
|
+
return "unknown"
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _get_tracer(trace_api: Any) -> Any:
|
|
325
|
+
return trace_api.get_tracer(_TRACER_NAME)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _get_otel_modules() -> tuple[Any, Any, Any] | None:
|
|
329
|
+
global _otel_modules
|
|
330
|
+
if _otel_modules is None:
|
|
331
|
+
try:
|
|
332
|
+
_otel_modules = (
|
|
333
|
+
importlib.import_module("opentelemetry.context"),
|
|
334
|
+
importlib.import_module("opentelemetry.propagate"),
|
|
335
|
+
importlib.import_module("opentelemetry.trace"),
|
|
336
|
+
)
|
|
337
|
+
except ImportError:
|
|
338
|
+
_otel_modules = _missing_otel_modules
|
|
339
|
+
|
|
340
|
+
if _otel_modules is _missing_otel_modules:
|
|
341
|
+
return None
|
|
342
|
+
return cast(tuple[Any, Any, Any], _otel_modules)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _get_always_off_tracer() -> Any | None:
|
|
346
|
+
global _always_off_tracer
|
|
347
|
+
if _always_off_tracer is None:
|
|
348
|
+
try:
|
|
349
|
+
sdk_trace = importlib.import_module("opentelemetry.sdk.trace")
|
|
350
|
+
sdk_sampling = importlib.import_module("opentelemetry.sdk.trace.sampling")
|
|
351
|
+
except ImportError:
|
|
352
|
+
_always_off_tracer = _missing_always_off_tracer
|
|
353
|
+
else:
|
|
354
|
+
# Keep always-off local to this invocation instead of replacing the
|
|
355
|
+
# process-wide tracer provider.
|
|
356
|
+
provider = sdk_trace.TracerProvider(sampler=sdk_sampling.ALWAYS_OFF)
|
|
357
|
+
_always_off_tracer = provider.get_tracer(_TRACER_NAME)
|
|
358
|
+
|
|
359
|
+
if _always_off_tracer is _missing_always_off_tracer:
|
|
360
|
+
return None
|
|
361
|
+
return _always_off_tracer
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
@contextlib.contextmanager
|
|
365
|
+
def remote_function_invocation_span(
|
|
366
|
+
context_metadata: Any,
|
|
367
|
+
coalesced_count: int | None = None,
|
|
368
|
+
) -> Iterator[None]:
|
|
369
|
+
first_metadata, traceparent_metadata = _collect_trace_metadata(context_metadata)
|
|
370
|
+
override = str((first_metadata or {}).get(_REMOTE_FUNCTION_TRACE_OVERRIDE_KEY) or "").strip().lower()
|
|
371
|
+
if override == _TRACE_OVERRIDE_ON:
|
|
372
|
+
policy = _TRACE_POLICY_PARENT_BASED_TRACE_ID_RATIO
|
|
373
|
+
elif override == _TRACE_OVERRIDE_OFF:
|
|
374
|
+
policy = _TRACE_POLICY_ALWAYS_OFF
|
|
375
|
+
else:
|
|
376
|
+
policy = _trace_policy()
|
|
377
|
+
if policy is None:
|
|
378
|
+
yield
|
|
379
|
+
return
|
|
380
|
+
|
|
381
|
+
force_trace = override == _TRACE_OVERRIDE_ON
|
|
382
|
+
if force_trace and traceparent_metadata is not None:
|
|
383
|
+
traceparent_metadata = _force_sampled_traceparent(traceparent_metadata)
|
|
384
|
+
if policy == _TRACE_POLICY_PARENT_BASED_ALWAYS_OFF and traceparent_metadata is None:
|
|
385
|
+
yield
|
|
386
|
+
return
|
|
387
|
+
|
|
388
|
+
if policy == _TRACE_POLICY_ALWAYS_OFF:
|
|
389
|
+
_metadata, parent_context, otel_modules, tracer = _always_off_span_metadata(
|
|
390
|
+
first_metadata,
|
|
391
|
+
traceparent_metadata,
|
|
392
|
+
)
|
|
393
|
+
else:
|
|
394
|
+
_metadata, parent_context, otel_modules = _span_metadata(
|
|
395
|
+
first_metadata,
|
|
396
|
+
traceparent_metadata,
|
|
397
|
+
policy,
|
|
398
|
+
force_trace,
|
|
399
|
+
)
|
|
400
|
+
tracer = _get_tracer(otel_modules[2]) if otel_modules is not None else None
|
|
401
|
+
|
|
402
|
+
if otel_modules is None:
|
|
403
|
+
yield
|
|
404
|
+
return
|
|
405
|
+
if tracer is None:
|
|
406
|
+
yield
|
|
407
|
+
return
|
|
408
|
+
otel_context, _, trace = otel_modules
|
|
409
|
+
|
|
410
|
+
SpanKind = trace.SpanKind
|
|
411
|
+
Status = trace.Status
|
|
412
|
+
StatusCode = trace.StatusCode
|
|
413
|
+
function_name = _function_name(context_metadata)
|
|
414
|
+
span_kwargs: dict[str, Any] = {"kind": SpanKind.SERVER}
|
|
415
|
+
if parent_context is not None:
|
|
416
|
+
span_kwargs["context"] = parent_context
|
|
417
|
+
|
|
418
|
+
# The always-off tracer exports nothing, so only runtime-provider spans need a flush.
|
|
419
|
+
flush_after = policy != _TRACE_POLICY_ALWAYS_OFF
|
|
420
|
+
try:
|
|
421
|
+
with tracer.start_as_current_span("chalkcompute.remote_function.invoke", **span_kwargs) as span:
|
|
422
|
+
span.set_attribute("chalk.remote_function.name", function_name)
|
|
423
|
+
if coalesced_count is not None:
|
|
424
|
+
span.set_attribute(
|
|
425
|
+
"chalk.remote_function.coalesced_count",
|
|
426
|
+
coalesced_count,
|
|
427
|
+
)
|
|
428
|
+
if parent_context is not None and not span.get_span_context().is_valid:
|
|
429
|
+
token = otel_context.attach(parent_context)
|
|
430
|
+
try:
|
|
431
|
+
yield
|
|
432
|
+
finally:
|
|
433
|
+
otel_context.detach(token)
|
|
434
|
+
return
|
|
435
|
+
try:
|
|
436
|
+
yield
|
|
437
|
+
except Exception as exc:
|
|
438
|
+
span.record_exception(exc)
|
|
439
|
+
span.set_status(Status(StatusCode.ERROR, str(exc)))
|
|
440
|
+
raise
|
|
441
|
+
finally:
|
|
442
|
+
# Runs after the invocation span (and any span the handler opened inside it) has
|
|
443
|
+
# ended, so the flush it schedules covers this invocation's spans.
|
|
444
|
+
if flush_after:
|
|
445
|
+
_request_span_flush()
|