aws-durable-execution-sdk-python-otel 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.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.
- aws_durable_execution_sdk_python_otel/__about__.py +1 -1
- aws_durable_execution_sdk_python_otel/__init__.py +2 -2
- aws_durable_execution_sdk_python_otel/deterministic_id_generator.py +4 -3
- aws_durable_execution_sdk_python_otel/log_filter.py +10 -11
- aws_durable_execution_sdk_python_otel/plugin.py +99 -39
- aws_durable_execution_sdk_python_otel-0.3.0.dist-info/METADATA +318 -0
- aws_durable_execution_sdk_python_otel-0.3.0.dist-info/RECORD +12 -0
- {aws_durable_execution_sdk_python_otel-0.2.0.dist-info → aws_durable_execution_sdk_python_otel-0.3.0.dist-info}/WHEEL +1 -1
- aws_durable_execution_sdk_python_otel-0.2.0.dist-info/METADATA +0 -67
- aws_durable_execution_sdk_python_otel-0.2.0.dist-info/RECORD +0 -12
|
@@ -14,7 +14,7 @@ from aws_durable_execution_sdk_python_otel.log_filter import (
|
|
|
14
14
|
install_log_filter,
|
|
15
15
|
)
|
|
16
16
|
from aws_durable_execution_sdk_python_otel.plugin import (
|
|
17
|
-
|
|
17
|
+
OtelPlugin,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
|
|
@@ -22,8 +22,8 @@ __all__ = [
|
|
|
22
22
|
"__version__",
|
|
23
23
|
"ContextExtractor",
|
|
24
24
|
"DeterministicIdGenerator",
|
|
25
|
-
"DurableExecutionOtelPlugin",
|
|
26
25
|
"OtelContextLogFilter",
|
|
26
|
+
"OtelPlugin",
|
|
27
27
|
"install_log_filter",
|
|
28
28
|
"w3c_client_context_extractor",
|
|
29
29
|
"xray_context_extractor",
|
|
@@ -66,9 +66,10 @@ def _to_otel_trace_id(execution_arn: str, start_timestamp: datetime | None) -> i
|
|
|
66
66
|
return int(f"{time_part}{hash_part}", 16)
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
def operation_id_to_span_id(operation_id: str) -> int:
|
|
70
|
-
"""Derive a deterministic span ID (64 bits) from an operation
|
|
71
|
-
|
|
69
|
+
def operation_id_to_span_id(durable_execution_arn: str, operation_id: str) -> int:
|
|
70
|
+
"""Derive a deterministic span ID (64 bits) from an execution and operation."""
|
|
71
|
+
plain_value = f"{durable_execution_arn}:{operation_id}"
|
|
72
|
+
hashed_operation_id = hashlib.blake2b(plain_value.encode()).hexdigest()[:16]
|
|
72
73
|
return int(hashed_operation_id, 16)
|
|
73
74
|
|
|
74
75
|
|
|
@@ -4,12 +4,11 @@ The filter attaches to a stdlib logging handler and enriches *every* record
|
|
|
4
4
|
that flows through it: direct ``logging.getLogger().info(...)`` calls,
|
|
5
5
|
child-logger records that propagate to root, and third-party library logs.
|
|
6
6
|
|
|
7
|
-
The span/trace identifiers are added as ``LogRecord`` attributes
|
|
8
|
-
underscore names, since dotted names are not valid record attributes):
|
|
7
|
+
The span/trace identifiers are added as ``LogRecord`` attributes:
|
|
9
8
|
|
|
10
|
-
- ``
|
|
11
|
-
- ``
|
|
12
|
-
- ``
|
|
9
|
+
- ``traceId``: 32-char hex trace identifier
|
|
10
|
+
- ``spanId``: 16-char hex span identifier
|
|
11
|
+
- ``otelTraceSampled``: boolean indicating if the trace is sampled
|
|
13
12
|
|
|
14
13
|
These attributes are only set when a valid span context is active. Records
|
|
15
14
|
emitted outside an active invocation (e.g. during Lambda teardown) pass through
|
|
@@ -25,7 +24,7 @@ from opentelemetry.trace import TraceFlags
|
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
if TYPE_CHECKING:
|
|
28
|
-
from aws_durable_execution_sdk_python_otel.plugin import
|
|
27
|
+
from aws_durable_execution_sdk_python_otel.plugin import OtelPlugin
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
class OtelContextLogFilter(logging.Filter):
|
|
@@ -45,7 +44,7 @@ class OtelContextLogFilter(logging.Filter):
|
|
|
45
44
|
plugin: The OTel plugin instance that resolves the current span context.
|
|
46
45
|
"""
|
|
47
46
|
|
|
48
|
-
def __init__(self, plugin:
|
|
47
|
+
def __init__(self, plugin: OtelPlugin) -> None:
|
|
49
48
|
super().__init__()
|
|
50
49
|
self._plugin = plugin
|
|
51
50
|
|
|
@@ -53,16 +52,16 @@ class OtelContextLogFilter(logging.Filter):
|
|
|
53
52
|
"""Stamp the active span context onto the record, then allow it through."""
|
|
54
53
|
span_context = self._plugin.get_current_span_context()
|
|
55
54
|
if span_context and span_context.is_valid:
|
|
56
|
-
record.
|
|
57
|
-
record.
|
|
58
|
-
record.
|
|
55
|
+
record.traceId = format(span_context.trace_id, "032x")
|
|
56
|
+
record.spanId = format(span_context.span_id, "016x")
|
|
57
|
+
record.otelTraceSampled = bool(
|
|
59
58
|
span_context.trace_flags & TraceFlags.SAMPLED
|
|
60
59
|
)
|
|
61
60
|
return True
|
|
62
61
|
|
|
63
62
|
|
|
64
63
|
def install_log_filter(
|
|
65
|
-
plugin:
|
|
64
|
+
plugin: OtelPlugin,
|
|
66
65
|
target_logger: logging.Logger | None = None,
|
|
67
66
|
) -> OtelContextLogFilter | None:
|
|
68
67
|
"""Attach an OtelContextLogFilter to a logger's handlers, idempotently.
|
|
@@ -21,7 +21,6 @@ from aws_durable_execution_sdk_python.plugin import (
|
|
|
21
21
|
from opentelemetry import context, trace
|
|
22
22
|
from opentelemetry.context import Context
|
|
23
23
|
from opentelemetry.sdk.trace import TracerProvider as SdkTracerProvider
|
|
24
|
-
from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
|
|
25
24
|
from opentelemetry.trace import (
|
|
26
25
|
Link,
|
|
27
26
|
Span,
|
|
@@ -52,7 +51,7 @@ def _to_otel_timestamp(dt: datetime.datetime | None) -> int | None:
|
|
|
52
51
|
return int(dt.timestamp() * 1_000_000_000)
|
|
53
52
|
|
|
54
53
|
|
|
55
|
-
class
|
|
54
|
+
class OtelPlugin(DurableInstrumentationPlugin):
|
|
56
55
|
"""OpenTelemetry instrumentation plugin for durable executions.
|
|
57
56
|
|
|
58
57
|
The plugin creates spans for Lambda invocations, durable operations, and
|
|
@@ -68,9 +67,10 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
68
67
|
|
|
69
68
|
Args:
|
|
70
69
|
trace_provider: OpenTelemetry tracer provider used to create spans.
|
|
70
|
+
Optional; when omitted, the globally configured tracer provider
|
|
71
|
+
(``opentelemetry.trace.get_tracer_provider()``) is used.
|
|
71
72
|
context_extractor: Optional extractor for upstream context. Defaults to
|
|
72
73
|
AWS X-Ray header extraction.
|
|
73
|
-
sampling_rate: Ratio used by ``TraceIdRatioBased`` sampling.
|
|
74
74
|
instrument_name: Instrumentation scope name registered with the tracer.
|
|
75
75
|
"""
|
|
76
76
|
|
|
@@ -78,17 +78,17 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
78
78
|
|
|
79
79
|
def __init__(
|
|
80
80
|
self,
|
|
81
|
-
trace_provider: SdkTracerProvider,
|
|
81
|
+
trace_provider: SdkTracerProvider | None = None,
|
|
82
82
|
context_extractor: ContextExtractor | None = None,
|
|
83
|
-
sampling_rate: float = 1.0,
|
|
84
83
|
instrument_name: str = DEFAULT_INSTRUMENT_NAME,
|
|
85
84
|
enrich_logger: bool = True,
|
|
86
85
|
) -> None:
|
|
87
86
|
"""Initialize the plugin with an OpenTelemetry tracer provider.
|
|
88
87
|
|
|
89
|
-
The
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
The tracer provider is configured with this plugin's deterministic ID
|
|
89
|
+
generator so spans for a durable execution share stable trace and
|
|
90
|
+
logical operation identifiers. When no provider is supplied, the
|
|
91
|
+
globally configured tracer provider is used.
|
|
92
92
|
|
|
93
93
|
When enrich_logger is enabled (default), the plugin installs a logging
|
|
94
94
|
filter on the root logger at invocation start that stamps the active
|
|
@@ -99,15 +99,28 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
99
99
|
context_extractor or xray_context_extractor
|
|
100
100
|
)
|
|
101
101
|
|
|
102
|
-
self._provider = trace_provider
|
|
103
|
-
# A ProxyTracerProvider (the API default from trace.get_tracer_provider()
|
|
104
|
-
# before an SDK provider is configured) has no id_generator; fall back to
|
|
105
|
-
# None so DeterministicIdGenerator uses its own default generator.
|
|
102
|
+
self._provider = trace_provider or trace.get_tracer_provider()
|
|
106
103
|
self._id_generator: DeterministicIdGenerator = DeterministicIdGenerator(
|
|
107
104
|
fallback_id_generator=getattr(self._provider, "id_generator", None)
|
|
108
105
|
)
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
# Deterministic trace stitching requires the SDK TracerProvider, which
|
|
107
|
+
# exposes id_generator/sampler. The API's default ProxyTracerProvider
|
|
108
|
+
# (returned before an SDK provider is configured) does not. Rather than
|
|
109
|
+
# fail the invocation over an observability concern, warn and continue:
|
|
110
|
+
# the proxy's tracer is effectively a no-op (and auto-delegates if an SDK
|
|
111
|
+
# provider is configured later). In a Lambda OTel/ADOT deployment the
|
|
112
|
+
# layer configures a real SDK provider before the handler imports.
|
|
113
|
+
if isinstance(self._provider, SdkTracerProvider):
|
|
114
|
+
self._provider.id_generator = self._id_generator
|
|
115
|
+
else:
|
|
116
|
+
logger.warning(
|
|
117
|
+
"OtelPlugin expected an SDK TracerProvider "
|
|
118
|
+
"(opentelemetry.sdk.trace.TracerProvider) but got %s. Spans will "
|
|
119
|
+
"not use deterministic IDs. "
|
|
120
|
+
"Ensure the OpenTelemetry SDK is configured (e.g. via the ADOT "
|
|
121
|
+
"Lambda layer) or pass an explicit trace_provider.",
|
|
122
|
+
type(self._provider).__name__,
|
|
123
|
+
)
|
|
111
124
|
self._tracer: Tracer = self._provider.get_tracer(instrument_name)
|
|
112
125
|
|
|
113
126
|
# per invocation status:
|
|
@@ -139,12 +152,18 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
139
152
|
with self._operation_spans_lock:
|
|
140
153
|
return self._operation_spans.get(operation_id)
|
|
141
154
|
|
|
155
|
+
@staticmethod
|
|
156
|
+
def _attempt_span_key(info: UserFunctionStartInfo | UserFunctionEndInfo) -> str:
|
|
157
|
+
"""Return the registry key for a STEP attempt span."""
|
|
158
|
+
return f"{info.operation_id}:attempt:{info.attempt or 1}"
|
|
159
|
+
|
|
142
160
|
def get_current_span_context(self) -> SpanContext | None:
|
|
143
161
|
"""Return the span context to use for log correlation.
|
|
144
162
|
|
|
145
163
|
Resolution order:
|
|
146
|
-
1. The span attached to the OTel thread-local context. Inside a step
|
|
147
|
-
|
|
164
|
+
1. The span attached to the OTel thread-local context. Inside a step
|
|
165
|
+
this is the active attempt span, and inside a child context this is
|
|
166
|
+
the active context span (attached in
|
|
148
167
|
on_user_function_start), and between operations it is the enclosing
|
|
149
168
|
operation span (restored in on_user_function_end).
|
|
150
169
|
2. The invocation span from the plugin registry. This is the path used
|
|
@@ -196,6 +215,8 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
196
215
|
start_time: datetime.datetime | None = None,
|
|
197
216
|
parent_span: Span | None = None,
|
|
198
217
|
existed: bool = False,
|
|
218
|
+
span_key: str | None = None,
|
|
219
|
+
deterministic_span_id: bool = True,
|
|
199
220
|
) -> Span:
|
|
200
221
|
"""Start and store a span for an invocation or durable operation.
|
|
201
222
|
|
|
@@ -210,6 +231,10 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
210
231
|
existed: Whether the logical operation already had a previous span.
|
|
211
232
|
Continuation spans link back to the deterministic span ID for
|
|
212
233
|
the operation while using a fresh generated span ID.
|
|
234
|
+
span_key: Optional registry key. Defaults to ``operation_id``.
|
|
235
|
+
deterministic_span_id: Whether to use the deterministic operation
|
|
236
|
+
span ID. Attempt spans set this to ``False`` so they can be
|
|
237
|
+
separate children of the logical operation span.
|
|
213
238
|
|
|
214
239
|
Returns:
|
|
215
240
|
The started OpenTelemetry span.
|
|
@@ -220,11 +245,15 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
220
245
|
name,
|
|
221
246
|
parent_span,
|
|
222
247
|
)
|
|
248
|
+
registry_key = span_key if span_key is not None else operation_id
|
|
223
249
|
with self._operation_spans_lock:
|
|
224
|
-
if
|
|
250
|
+
if not deterministic_span_id:
|
|
251
|
+
links = []
|
|
252
|
+
self._id_generator.set_next_span_id(None)
|
|
253
|
+
elif existed:
|
|
225
254
|
if not operation_id:
|
|
226
255
|
raise ValueError("operation id is required")
|
|
227
|
-
span_id = operation_id_to_span_id(operation_id)
|
|
256
|
+
span_id = operation_id_to_span_id(self._execution_arn, operation_id)
|
|
228
257
|
links = [
|
|
229
258
|
Link(
|
|
230
259
|
context=SpanContext(
|
|
@@ -240,7 +269,9 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
240
269
|
links = []
|
|
241
270
|
|
|
242
271
|
self._id_generator.set_next_span_id(
|
|
243
|
-
operation_id_to_span_id(operation_id)
|
|
272
|
+
operation_id_to_span_id(self._execution_arn, operation_id)
|
|
273
|
+
if operation_id
|
|
274
|
+
else None
|
|
244
275
|
)
|
|
245
276
|
if parent_span is None:
|
|
246
277
|
# root span
|
|
@@ -256,7 +287,7 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
256
287
|
context=parent_context,
|
|
257
288
|
links=links,
|
|
258
289
|
)
|
|
259
|
-
self._operation_spans[
|
|
290
|
+
self._operation_spans[registry_key] = span
|
|
260
291
|
|
|
261
292
|
logger.debug("Started OTel span: %s", span)
|
|
262
293
|
return span
|
|
@@ -324,8 +355,8 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
324
355
|
def on_operation_start(self, info: OperationStartInfo) -> None:
|
|
325
356
|
"""Called when an operation begins. Creates a span for the operation."""
|
|
326
357
|
logger.debug("Durable operation started: %s", info)
|
|
327
|
-
if info.operation_type
|
|
328
|
-
# Context
|
|
358
|
+
if info.operation_type is OperationType.CONTEXT:
|
|
359
|
+
# Context operations are tracked using on_user_function_start.
|
|
329
360
|
return
|
|
330
361
|
parent_span = self._resolve_parent_span(info.parent_id)
|
|
331
362
|
attributes = self._extract_attributes(info)
|
|
@@ -336,6 +367,7 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
336
367
|
attributes=attributes,
|
|
337
368
|
start_time=info.start_time,
|
|
338
369
|
parent_span=parent_span,
|
|
370
|
+
existed=info.is_replayed,
|
|
339
371
|
)
|
|
340
372
|
|
|
341
373
|
def on_operation_end(self, info: OperationEndInfo) -> None:
|
|
@@ -348,8 +380,8 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
348
380
|
operation span before being ended.
|
|
349
381
|
"""
|
|
350
382
|
logger.debug("Durable operation ended: %s", info)
|
|
351
|
-
if info.operation_type
|
|
352
|
-
# Context
|
|
383
|
+
if info.operation_type is OperationType.CONTEXT:
|
|
384
|
+
# Context operations are tracked using on_user_function_end.
|
|
353
385
|
return
|
|
354
386
|
span = self._get_span(info.operation_id)
|
|
355
387
|
if not span:
|
|
@@ -365,6 +397,8 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
365
397
|
parent_span=parent_span,
|
|
366
398
|
existed=True,
|
|
367
399
|
)
|
|
400
|
+
else:
|
|
401
|
+
span.set_attributes(self._extract_attributes(info))
|
|
368
402
|
|
|
369
403
|
if info.error:
|
|
370
404
|
span.set_status(StatusCode.ERROR, info.error.message or "")
|
|
@@ -384,8 +418,9 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
384
418
|
|
|
385
419
|
This callback runs inside the thread that executes user code so the
|
|
386
420
|
started span can be attached to the OpenTelemetry context for any
|
|
387
|
-
instrumentation used by that code.
|
|
388
|
-
|
|
421
|
+
instrumentation used by that code. STEP attempts are emitted as child
|
|
422
|
+
spans beneath the logical STEP operation span created by
|
|
423
|
+
``on_operation_start``.
|
|
389
424
|
|
|
390
425
|
Args:
|
|
391
426
|
info: Information about the operation attempt.
|
|
@@ -396,15 +431,30 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
396
431
|
raise RuntimeError(
|
|
397
432
|
"on_user_function_start should only be called for CONTEXT and STEP operations"
|
|
398
433
|
)
|
|
399
|
-
|
|
434
|
+
if info.operation_type is OperationType.STEP:
|
|
435
|
+
parent_span = self._get_span(
|
|
436
|
+
info.operation_id
|
|
437
|
+
) or self._resolve_parent_span(info.parent_id)
|
|
438
|
+
else:
|
|
439
|
+
parent_span = self._resolve_parent_span(info.parent_id)
|
|
400
440
|
attributes = self._extract_attributes(info)
|
|
441
|
+
span_name = info.name or info.operation_id
|
|
442
|
+
if info.operation_type is OperationType.STEP:
|
|
443
|
+
span_name = f"{span_name} attempt {info.attempt or 1}"
|
|
444
|
+
span_key = (
|
|
445
|
+
self._attempt_span_key(info)
|
|
446
|
+
if info.operation_type is OperationType.STEP
|
|
447
|
+
else info.operation_id
|
|
448
|
+
)
|
|
401
449
|
span = self._start_span(
|
|
402
450
|
operation_id=info.operation_id,
|
|
403
|
-
name=
|
|
451
|
+
name=span_name,
|
|
404
452
|
attributes=attributes,
|
|
405
453
|
start_time=info.start_time,
|
|
406
454
|
parent_span=parent_span,
|
|
407
|
-
existed=info.attempt != 1,
|
|
455
|
+
existed=info.attempt != 1 and info.operation_type is not OperationType.STEP,
|
|
456
|
+
span_key=span_key,
|
|
457
|
+
deterministic_span_id=info.operation_type is not OperationType.STEP,
|
|
408
458
|
)
|
|
409
459
|
context.attach(trace.set_span_in_context(span, self._extracted_context))
|
|
410
460
|
|
|
@@ -424,7 +474,12 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
424
474
|
"on_user_function_end should only be called for CONTEXT and STEP operations"
|
|
425
475
|
)
|
|
426
476
|
# key = f"{info.operation_id}-{int(info.start_time.timestamp())}"
|
|
427
|
-
|
|
477
|
+
span_key = (
|
|
478
|
+
self._attempt_span_key(info)
|
|
479
|
+
if info.operation_type is OperationType.STEP
|
|
480
|
+
else info.operation_id
|
|
481
|
+
)
|
|
482
|
+
span = self._get_span(span_key)
|
|
428
483
|
if not span:
|
|
429
484
|
raise RuntimeError(
|
|
430
485
|
"on_user_function_end called without matching on_user_function_start"
|
|
@@ -440,16 +495,13 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
440
495
|
else "Unknown error"
|
|
441
496
|
)
|
|
442
497
|
)
|
|
443
|
-
elif info.outcome is UserFunctionOutcome.SUCCEEDED:
|
|
444
|
-
span.set_status(StatusCode.OK)
|
|
445
498
|
else:
|
|
446
|
-
|
|
447
|
-
span.set_status(StatusCode.UNSET, "PENDING")
|
|
499
|
+
span.set_status(StatusCode.OK)
|
|
448
500
|
|
|
449
501
|
end_timestamp = info.end_time
|
|
450
502
|
if end_timestamp is not None and end_timestamp == info.start_time:
|
|
451
503
|
end_timestamp += datetime.timedelta(microseconds=1)
|
|
452
|
-
self._end_span(
|
|
504
|
+
self._end_span(span_key, end_timestamp)
|
|
453
505
|
# Restore the enclosing operation span as current so code that runs
|
|
454
506
|
# after this operation (e.g. between steps in a child context)
|
|
455
507
|
# correlates to its enclosing operation, not the operation that just
|
|
@@ -478,11 +530,19 @@ class DurableExecutionOtelPlugin(DurableInstrumentationPlugin):
|
|
|
478
530
|
attributes["durable.operation.id"] = info.operation_id
|
|
479
531
|
if hasattr(info, "operation_type") and info.operation_type is not None:
|
|
480
532
|
attributes["durable.operation.type"] = info.operation_type.value
|
|
533
|
+
if hasattr(info, "sub_type") and info.sub_type is not None:
|
|
534
|
+
attributes["durable.operation.subtype"] = info.sub_type.value
|
|
535
|
+
if hasattr(info, "status") and info.status is not None:
|
|
536
|
+
attributes["durable.operation.status"] = info.status.value
|
|
481
537
|
if hasattr(info, "name") and info.name is not None:
|
|
482
538
|
attributes["durable.operation.name"] = info.name
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
539
|
+
# Per-attempt fields are meaningful for STEP (each attempt is retried)
|
|
540
|
+
# but not for CONTEXT (a context is entered once per invocation, not
|
|
541
|
+
# retried). Omit them on CONTEXT spans for cross-SDK consistency.
|
|
542
|
+
if getattr(info, "operation_type", None) is not OperationType.CONTEXT:
|
|
543
|
+
if hasattr(info, "attempt") and info.attempt is not None:
|
|
544
|
+
attributes["durable.attempt.number"] = info.attempt
|
|
545
|
+
if hasattr(info, "outcome") and info.outcome is not None:
|
|
546
|
+
attributes["durable.attempt.outcome"] = info.outcome.value
|
|
487
547
|
|
|
488
548
|
return attributes
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aws-durable-execution-sdk-python-otel
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: OpenTelemetry instrumentation for AWS Durable Execution SDK for Python
|
|
5
|
+
Project-URL: Documentation, https://github.com/aws/aws-durable-execution-sdk-python#readme
|
|
6
|
+
Project-URL: Issues, https://github.com/aws/aws-durable-execution-sdk-python/issues
|
|
7
|
+
Project-URL: Source, https://github.com/aws/aws-durable-execution-sdk-python
|
|
8
|
+
Author-email: AWS durable-execution-dev <durable-execution-dev@amazon.com>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
Keywords: durable-execution,observability,opentelemetry,tracing
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: aws-durable-execution-sdk-python>=1.5.0
|
|
21
|
+
Requires-Dist: opentelemetry-api>=1.20.0
|
|
22
|
+
Requires-Dist: opentelemetry-exporter-otlp
|
|
23
|
+
Requires-Dist: opentelemetry-propagator-aws-xray
|
|
24
|
+
Requires-Dist: opentelemetry-sdk>=1.20.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# AWS Durable Execution SDK - OpenTelemetry Plugin
|
|
28
|
+
|
|
29
|
+
OpenTelemetry instrumentation plugin for the [AWS Durable Execution SDK for Python](https://github.com/aws/aws-durable-execution-sdk-python). Emits distributed traces that correlate across multiple Lambda invocations of a single durable execution, producing deterministic span and trace IDs so that spans from different invocations are stitched into a single coherent trace.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **Deterministic Trace IDs**: All invocations of the same durable execution share a single trace, derived from the X-Ray trace header or execution ARN
|
|
34
|
+
- **Span-per-Operation**: Each durable operation (step, wait, invoke) gets its own span with accurate timing
|
|
35
|
+
- **Continuation Spans**: Operations completing in a different invocation are linked back to the original span
|
|
36
|
+
- **Log Correlation**: Enrich application logs with trace ID and span ID for end-to-end observability
|
|
37
|
+
- **Configurable Sampling**: Control trace volume via plugin options
|
|
38
|
+
- **Self-Contained Setup**: No manual TracerProvider configuration required
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install aws-durable-execution-sdk-python-otel
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start using X-Ray/CloudWatch Tracing
|
|
47
|
+
|
|
48
|
+
1. Add the [ADOT Lambda Layer](#1-adot-lambda-layer) to your function and set `AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-instrument`
|
|
49
|
+
2. Enable [X-Ray Active Tracing](#2-aws-x-ray-active-tracing) on the function
|
|
50
|
+
3. Pass `OtelPlugin` to your handler's `plugins` list
|
|
51
|
+
4. Add X-Ray write permissions
|
|
52
|
+
|
|
53
|
+
### 1. ADOT Lambda Layer
|
|
54
|
+
|
|
55
|
+
This plugin requires the [AWS Distro for OpenTelemetry (ADOT) Lambda layer](https://aws-otel.github.io/docs/getting-started/lambda) to export traces from your Lambda function.
|
|
56
|
+
|
|
57
|
+
The layer ARN follows the format:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
arn:aws:lambda:<region>:<awsAccountId>:layer:aws-otel-python-<arch>-ver-<version>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Refer to the [ADOT Lambda Layer ARNs](https://aws-otel.github.io/docs/getting-started/lambda/lambda-python) page for the latest version number, architecture, and supported regions.
|
|
64
|
+
|
|
65
|
+
**AWS CLI:**
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
aws lambda update-function-configuration \
|
|
69
|
+
--function-name your-function-name \
|
|
70
|
+
--layers "arn:aws:lambda:<region>:<awsAccountId>:layer:aws-otel-python-amd64-ver-<version>"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
You must also set the `AWS_LAMBDA_EXEC_WRAPPER` environment variable:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
aws lambda update-function-configuration \
|
|
77
|
+
--function-name your-function-name \
|
|
78
|
+
--environment "Variables={AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-instrument}"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> **Note:** Replace `<region>` with your function's region and `<version>`/`<arch>` with the latest layer version and architecture from the ADOT docs.
|
|
82
|
+
|
|
83
|
+
**CloudFormation / SAM:**
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
MyFunction:
|
|
87
|
+
Type: AWS::Serverless::Function
|
|
88
|
+
Properties:
|
|
89
|
+
Layers:
|
|
90
|
+
- !Sub arn:aws:lambda:${AWS::Region}:<awsAccountId>:layer:aws-otel-python-amd64-ver-<version>
|
|
91
|
+
Environment:
|
|
92
|
+
Variables:
|
|
93
|
+
AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-instrument
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**CDK:**
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from aws_cdk import aws_lambda as lambda_
|
|
100
|
+
|
|
101
|
+
adot_layer = lambda_.LayerVersion.from_layer_version_arn(
|
|
102
|
+
self,
|
|
103
|
+
"AdotLayer",
|
|
104
|
+
f"arn:aws:lambda:<region>:<awsAccountId>:layer:aws-otel-python-amd64-ver-<version>",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
fn = lambda_.Function(
|
|
108
|
+
self,
|
|
109
|
+
"MyFunction",
|
|
110
|
+
runtime=lambda_.Runtime.PYTHON_3_12,
|
|
111
|
+
handler="index.handler",
|
|
112
|
+
code=lambda_.Code.from_asset("lambda"),
|
|
113
|
+
layers=[adot_layer],
|
|
114
|
+
environment={"AWS_LAMBDA_EXEC_WRAPPER": "/opt/otel-instrument"},
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> **Tip:** Pin the layer version to a specific number in production deployments to avoid unexpected behavior from automatic version changes.
|
|
119
|
+
|
|
120
|
+
### 2. AWS X-Ray Active Tracing
|
|
121
|
+
|
|
122
|
+
Enable active tracing on your Lambda function so the `_X_AMZN_TRACE_ID` environment variable is populated at invocation time. The plugin uses this header to derive deterministic trace IDs that remain consistent across all invocations of the same durable execution.
|
|
123
|
+
|
|
124
|
+
**AWS Console:** Lambda → Configuration → Monitoring and operations tools → Active tracing → Enable
|
|
125
|
+
|
|
126
|
+
**AWS CLI:**
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
aws lambda update-function-configuration \
|
|
130
|
+
--function-name your-function-name \
|
|
131
|
+
--tracing-config Mode=Active
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**CloudFormation / SAM:**
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
MyFunction:
|
|
138
|
+
Type: AWS::Lambda::Function
|
|
139
|
+
Properties:
|
|
140
|
+
TracingConfig:
|
|
141
|
+
Mode: Active
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**CDK:**
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
lambda_.Function(
|
|
148
|
+
self,
|
|
149
|
+
"MyFunction",
|
|
150
|
+
tracing=lambda_.Tracing.ACTIVE,
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 3. In your Lambda handler (index.py)
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from aws_durable_execution_sdk_python import DurableContext
|
|
158
|
+
from aws_durable_execution_sdk_python.execution import durable_execution
|
|
159
|
+
from aws_durable_execution_sdk_python_otel import OtelPlugin
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@durable_execution(plugins=[OtelPlugin()])
|
|
163
|
+
def handler(event: dict, context: DurableContext) -> dict:
|
|
164
|
+
result = context.step(lambda _: fetch_data(event["id"]), name="fetch-data")
|
|
165
|
+
|
|
166
|
+
context.wait(duration=Duration.from_seconds(5))
|
|
167
|
+
|
|
168
|
+
context.step(lambda _: process(result), name="process")
|
|
169
|
+
|
|
170
|
+
return result
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
That's it. The plugin handles TracerProvider setup, deterministic ID generation, and span lifecycle internally.
|
|
174
|
+
|
|
175
|
+
### 4. Grant Permissions
|
|
176
|
+
|
|
177
|
+
The function's execution role needs the `AWSXRayDaemonWriteAccess` managed policy (or equivalent permissions) if using X-Ray as the tracing backend.
|
|
178
|
+
|
|
179
|
+
### Environment Variables for ADOT layer
|
|
180
|
+
|
|
181
|
+
| Variable | Description | Default |
|
|
182
|
+
| ----------------------------- | --------------------------------------------------------------------------------------------- | ----------------- |
|
|
183
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Endpoint for the OTLP exporter (e.g., `http://localhost:4318` for the ADOT collector sidecar) | Set by ADOT layer |
|
|
184
|
+
| `AWS_LAMBDA_EXEC_WRAPPER` | Set to `/opt/otel-instrument` for the ADOT layer to instrument your function | — |
|
|
185
|
+
| `OTEL_TRACES_SAMPLER` | Sampler to use (e.g., `traceidratio` for ratio-based sampling) | `always_on` |
|
|
186
|
+
| `OTEL_TRACES_SAMPLER_ARG` | Argument for the sampler (e.g., `0.3` to sample 30% of traces) | — |
|
|
187
|
+
|
|
188
|
+
See the [ADOT sampling configuration](https://aws-otel.github.io/docs/getting-started/lambda#sampling-configuration) for more details.
|
|
189
|
+
|
|
190
|
+
## Configuration
|
|
191
|
+
|
|
192
|
+
### Plugin Options
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from aws_durable_execution_sdk_python_otel import (
|
|
196
|
+
OtelPlugin,
|
|
197
|
+
xray_context_extractor,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
plugin = OtelPlugin(
|
|
201
|
+
# Provide your own TracerProvider if you already have one configured.
|
|
202
|
+
# Defaults to the globally configured tracer provider.
|
|
203
|
+
trace_provider=None,
|
|
204
|
+
# Use a custom context extractor (default: xray_context_extractor).
|
|
205
|
+
context_extractor=xray_context_extractor,
|
|
206
|
+
# Custom instrumentation scope name
|
|
207
|
+
# (default: "aws-durable-execution-sdk-python").
|
|
208
|
+
instrument_name="my-service",
|
|
209
|
+
# Install a root-logger filter that stamps trace context onto every
|
|
210
|
+
# log record (default: True).
|
|
211
|
+
enrich_logger=True,
|
|
212
|
+
)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Context Extractors
|
|
216
|
+
|
|
217
|
+
The plugin supports multiple strategies for extracting upstream trace context:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from aws_durable_execution_sdk_python_otel import (
|
|
221
|
+
OtelPlugin,
|
|
222
|
+
w3c_client_context_extractor,
|
|
223
|
+
xray_context_extractor,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Default: X-Ray trace header (recommended for most Lambda deployments)
|
|
227
|
+
OtelPlugin(context_extractor=xray_context_extractor)
|
|
228
|
+
|
|
229
|
+
# W3C Trace Context via clientContext (requires backend propagation support)
|
|
230
|
+
OtelPlugin(context_extractor=w3c_client_context_extractor)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Log Correlation
|
|
234
|
+
|
|
235
|
+
When `enrich_logger=True` (the default), the plugin installs a logging filter on
|
|
236
|
+
the root logger at invocation start. The filter stamps the active OTel trace
|
|
237
|
+
context onto every emitted log record using these attributes:
|
|
238
|
+
|
|
239
|
+
- `traceId`: 32-char hex trace identifier
|
|
240
|
+
- `spanId`: 16-char hex span identifier
|
|
241
|
+
- `otelTraceSampled`: boolean indicating if the trace is sampled
|
|
242
|
+
|
|
243
|
+
These attributes are only set when a valid span context is active, so any log
|
|
244
|
+
formatter or schema must treat the fields as optional.
|
|
245
|
+
|
|
246
|
+
## Verification
|
|
247
|
+
|
|
248
|
+
After deploying your function with the plugin configured:
|
|
249
|
+
|
|
250
|
+
1. **Invoke your durable function** — trigger at least one execution that includes multiple steps or a wait/resume cycle.
|
|
251
|
+
|
|
252
|
+
2. **Check the CloudWatch console** — Navigate to CloudWatch → Traces in the AWS Console. You should see a trace with:
|
|
253
|
+
- An "invocation" span per invocation
|
|
254
|
+
- Child spans for each durable operation (named after your step names)
|
|
255
|
+
- All invocations of the same execution grouped under one trace ID
|
|
256
|
+
|
|
257
|
+
3. **Check log correlation** — verify that your logs include `traceId` and `spanId` fields matching the spans in X-Ray.
|
|
258
|
+
|
|
259
|
+
4. **Confirm sampling** — If you set `OTEL_TRACES_SAMPLER=traceidratio` and `OTEL_TRACES_SAMPLER_ARG` to a value less than 1.0, verify that only the expected proportion of traces appear.
|
|
260
|
+
|
|
261
|
+
5. **Span links** — For operations that span multiple invocations (e.g., after a wait resumes), though span links are set, they are not visualized within the CloudWatch console.
|
|
262
|
+
|
|
263
|
+
### Troubleshooting
|
|
264
|
+
|
|
265
|
+
| Symptom | Likely Cause |
|
|
266
|
+
| --------------------------------- | --------------------------------------------------------------- |
|
|
267
|
+
| No traces appear | ADOT layer not configured, or `AWS_LAMBDA_EXEC_WRAPPER` not set |
|
|
268
|
+
| Traces appear but are fragmented | X-Ray active tracing not enabled on the Lambda function |
|
|
269
|
+
| Missing spans for some operations | `OTEL_TRACES_SAMPLER_ARG` set below 1.0 |
|
|
270
|
+
| `_X_AMZN_TRACE_ID` not populated | X-Ray active tracing not enabled |
|
|
271
|
+
|
|
272
|
+
## API Reference
|
|
273
|
+
|
|
274
|
+
### `OtelPlugin`
|
|
275
|
+
|
|
276
|
+
The main plugin class. Implements `DurableInstrumentationPlugin` from `aws_durable_execution_sdk_python`.
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
OtelPlugin(
|
|
280
|
+
trace_provider=None,
|
|
281
|
+
context_extractor=None,
|
|
282
|
+
instrument_name="aws-durable-execution-sdk-python",
|
|
283
|
+
enrich_logger=True,
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### `DeterministicIdGenerator`
|
|
288
|
+
|
|
289
|
+
A custom OpenTelemetry `IdGenerator` that produces reproducible trace and span IDs from execution metadata. Exported for advanced use cases.
|
|
290
|
+
|
|
291
|
+
### `xray_context_extractor`
|
|
292
|
+
|
|
293
|
+
Default context extractor. Reads the `_X_AMZN_TRACE_ID` environment variable to derive trace context.
|
|
294
|
+
|
|
295
|
+
### `w3c_client_context_extractor`
|
|
296
|
+
|
|
297
|
+
Alternative context extractor. Reads W3C `traceparent` from `context.clientContext.custom.traceparent`. Requires backend `clientContext` propagation to be enabled.
|
|
298
|
+
|
|
299
|
+
### `ContextExtractor`
|
|
300
|
+
|
|
301
|
+
Type alias for custom context extractor functions.
|
|
302
|
+
|
|
303
|
+
### `OtelContextLogFilter` / `install_log_filter`
|
|
304
|
+
|
|
305
|
+
The logging filter (and its installer) used to stamp trace context onto log
|
|
306
|
+
records. Installed automatically when `enrich_logger=True`; exported for manual
|
|
307
|
+
setups.
|
|
308
|
+
|
|
309
|
+
## Requirements
|
|
310
|
+
|
|
311
|
+
- Python >= 3.11
|
|
312
|
+
- `aws-durable-execution-sdk-python` >= 1.5.0
|
|
313
|
+
- `opentelemetry-api` >= 1.20.0
|
|
314
|
+
- `opentelemetry-sdk` >= 1.20.0
|
|
315
|
+
|
|
316
|
+
## License
|
|
317
|
+
|
|
318
|
+
Apache-2.0
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
aws_durable_execution_sdk_python_otel/__about__.py,sha256=eMP6nYgoHQWcCgV4SvwAgcs8UrO25qIlEa33am5Bfpw,137
|
|
2
|
+
aws_durable_execution_sdk_python_otel/__init__.py,sha256=8MbvOj7Bde-BHrZzvFqphYvGghUoXG5dN0hGhmSHMLw,844
|
|
3
|
+
aws_durable_execution_sdk_python_otel/context_extractors.py,sha256=hI3ZeVsNZHYyw1eOgvDJv8Y5NQY2VDpzTr_VyNJMtPU,1332
|
|
4
|
+
aws_durable_execution_sdk_python_otel/deterministic_id_generator.py,sha256=5lV3pyu0krZaoPwscOQG0lwV4caz5XXnPsa-FPFWQKs,5061
|
|
5
|
+
aws_durable_execution_sdk_python_otel/log_filter.py,sha256=ypfbFWDbE35sxOzOZ32yIHbOIOR7ySMVymfsMfyr46U,4128
|
|
6
|
+
aws_durable_execution_sdk_python_otel/plugin.py,sha256=VSyo160gyro4xv1sRM0JPiOYhSQPI2NYEUKz5zU5aQs,23926
|
|
7
|
+
aws_durable_execution_sdk_python_otel/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
aws_durable_execution_sdk_python_otel/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
|
|
9
|
+
aws_durable_execution_sdk_python_otel/NOTICE,sha256=1CkO1kwu3Q_OHYTj-d-yiBJA_lNN73a4zSntavaD4oc,67
|
|
10
|
+
aws_durable_execution_sdk_python_otel-0.3.0.dist-info/METADATA,sha256=F8Pc8geychW3Pq2hFnRUM6ny07bptbjPnBXps8qgJiQ,12240
|
|
11
|
+
aws_durable_execution_sdk_python_otel-0.3.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
12
|
+
aws_durable_execution_sdk_python_otel-0.3.0.dist-info/RECORD,,
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: aws-durable-execution-sdk-python-otel
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: OpenTelemetry instrumentation for AWS Durable Execution SDK for Python
|
|
5
|
-
Project-URL: Documentation, https://github.com/aws/aws-durable-execution-sdk-python#readme
|
|
6
|
-
Project-URL: Issues, https://github.com/aws/aws-durable-execution-sdk-python/issues
|
|
7
|
-
Project-URL: Source, https://github.com/aws/aws-durable-execution-sdk-python
|
|
8
|
-
Author-email: AWS durable-execution-dev <durable-execution-dev@amazon.com>
|
|
9
|
-
License-Expression: Apache-2.0
|
|
10
|
-
Keywords: durable-execution,observability,opentelemetry,tracing
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Classifier: Programming Language :: Python
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
-
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
|
-
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
19
|
-
Requires-Python: >=3.11
|
|
20
|
-
Requires-Dist: aws-durable-execution-sdk-python>=1.5.0
|
|
21
|
-
Requires-Dist: opentelemetry-api>=1.20.0
|
|
22
|
-
Requires-Dist: opentelemetry-exporter-otlp
|
|
23
|
-
Requires-Dist: opentelemetry-propagator-aws-xray
|
|
24
|
-
Requires-Dist: opentelemetry-sdk>=1.20.0
|
|
25
|
-
Description-Content-Type: text/markdown
|
|
26
|
-
|
|
27
|
-
# aws-durable-execution-sdk-python-otel
|
|
28
|
-
|
|
29
|
-
OpenTelemetry instrumentation for the [AWS Durable Execution SDK for Python](https://github.com/aws/aws-durable-execution-sdk-python).
|
|
30
|
-
|
|
31
|
-
> **Note:** v0.1.0 reserves the package name. Instrumentation lands in v0.2.0.
|
|
32
|
-
|
|
33
|
-
## Overview
|
|
34
|
-
|
|
35
|
-
This package will provide automatic OpenTelemetry tracing for durable execution workflows, giving you visibility into step execution, waits, retries, and overall workflow performance.
|
|
36
|
-
|
|
37
|
-
## Installation
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
pip install aws-durable-execution-sdk-python-otel
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Quick Start
|
|
44
|
-
|
|
45
|
-
```python
|
|
46
|
-
from aws_durable_execution_sdk_python_otel import __version__
|
|
47
|
-
|
|
48
|
-
print(__version__)
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Planned Features (v0.2.0)
|
|
52
|
-
|
|
53
|
-
- Automatic span creation for steps, waits, invokes, and child contexts
|
|
54
|
-
- Replay-aware tracing (distinguishes fresh executions from replays)
|
|
55
|
-
- Error recording with proper OTel status codes
|
|
56
|
-
- Configurable span attributes and naming
|
|
57
|
-
|
|
58
|
-
## Requirements
|
|
59
|
-
|
|
60
|
-
- Python >= 3.11
|
|
61
|
-
- `aws-durable-execution-sdk-python` >= 1.5.0
|
|
62
|
-
- `opentelemetry-api` >= 1.20.0
|
|
63
|
-
- `opentelemetry-sdk` >= 1.20.0
|
|
64
|
-
|
|
65
|
-
## License
|
|
66
|
-
|
|
67
|
-
Apache-2.0
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
aws_durable_execution_sdk_python_otel/__about__.py,sha256=fN6OMpnwGW4dbezf3aG7wuWrAhfRPpRGifGrGbfb5QI,137
|
|
2
|
-
aws_durable_execution_sdk_python_otel/__init__.py,sha256=3ssbxJTadtJWAv8TZVfWL0a6-d5kpmxj_pJCtpKT56w,876
|
|
3
|
-
aws_durable_execution_sdk_python_otel/context_extractors.py,sha256=hI3ZeVsNZHYyw1eOgvDJv8Y5NQY2VDpzTr_VyNJMtPU,1332
|
|
4
|
-
aws_durable_execution_sdk_python_otel/deterministic_id_generator.py,sha256=yVMJAvuzqOgFrw7vuHkUlrdAu53FtKtPf6jHLjK_AhQ,4963
|
|
5
|
-
aws_durable_execution_sdk_python_otel/log_filter.py,sha256=FUDiNJ1cj4p-SRZ09YEZDpvZgWOA8BMw0MKTcoWAIrg,4281
|
|
6
|
-
aws_durable_execution_sdk_python_otel/plugin.py,sha256=7OBNlpWmDxunvuXQvsMaHiKR-ZkofN0w1CUXxyWuXUE,20791
|
|
7
|
-
aws_durable_execution_sdk_python_otel/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
aws_durable_execution_sdk_python_otel/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
|
|
9
|
-
aws_durable_execution_sdk_python_otel/NOTICE,sha256=1CkO1kwu3Q_OHYTj-d-yiBJA_lNN73a4zSntavaD4oc,67
|
|
10
|
-
aws_durable_execution_sdk_python_otel-0.2.0.dist-info/METADATA,sha256=O1AdqEFUVG-gQbsBNbBvB-HjuqPwxVVnyYZOpTbWD3k,2386
|
|
11
|
-
aws_durable_execution_sdk_python_otel-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
12
|
-
aws_durable_execution_sdk_python_otel-0.2.0.dist-info/RECORD,,
|