Skip to content

Commit 6611ff7

Browse files
committed
update awslambda to use _X_AMZN_TRACE_ID as a Span Link
1 parent df32e8c commit 6611ff7

File tree

2 files changed

+105
-87
lines changed

2 files changed

+105
-87
lines changed

instrumentation/opentelemetry-instrumentation-aws-lambda/src/opentelemetry/instrumentation/aws_lambda/__init__.py

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def custom_event_context_extractor(lambda_event):
7171
import os
7272
import time
7373
from importlib import import_module
74-
from typing import Any, Callable, Collection
74+
from typing import Any, Callable, Collection, Optional, Sequence
7575
from urllib.parse import urlencode
7676

7777
from wrapt import wrap_function_wrapper
@@ -90,9 +90,11 @@ def custom_event_context_extractor(lambda_event):
9090
from opentelemetry.semconv.resource import ResourceAttributes
9191
from opentelemetry.semconv.trace import SpanAttributes
9292
from opentelemetry.trace import (
93+
Link,
9394
Span,
9495
SpanKind,
9596
TracerProvider,
97+
get_current_span,
9698
get_tracer,
9799
get_tracer_provider,
98100
)
@@ -149,7 +151,7 @@ def _determine_parent_context(
149151
"""Determine the parent context for the current Lambda invocation.
150152
151153
See more:
152-
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/aws-lambda.md#determining-the-parent-of-a-span
154+
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/aws-lambda.md
153155
154156
Args:
155157
lambda_event: user-defined, so it could be anything, but this
@@ -166,29 +168,44 @@ def _determine_parent_context(
166168
"""
167169
parent_context = None
168170

171+
if event_context_extractor:
172+
parent_context = event_context_extractor(lambda_event)
173+
else:
174+
parent_context = _default_event_context_extractor(lambda_event)
175+
176+
return parent_context
177+
178+
def _determine_links(
179+
disable_aws_context_propagation: bool = False
180+
) -> Optional[Sequence[Link]] :
181+
"""Determine if a Link should be added to the Span based on the
182+
environment variable `_X_AMZN_TRACE_ID`.
183+
184+
185+
See more:
186+
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/aws-lambda.md
187+
188+
Args:
189+
disable_aws_context_propagation: By default, this instrumentation
190+
will try to read the context from the `_X_AMZN_TRACE_ID` environment
191+
variable set by Lambda, set this to `True` to disable this behavior.
192+
Returns:
193+
A Link or None
194+
"""
195+
env_context = None
196+
links = None
197+
169198
if not disable_aws_context_propagation:
170199
xray_env_var = os.environ.get(_X_AMZN_TRACE_ID)
171200

172201
if xray_env_var:
173-
parent_context = AwsXRayPropagator().extract(
202+
env_context = AwsXRayPropagator().extract(
174203
{TRACE_HEADER_KEY: xray_env_var}
175204
)
176205

177-
if (
178-
parent_context
179-
and get_current_span(parent_context)
180-
.get_span_context()
181-
.trace_flags.sampled
182-
):
183-
return parent_context
184-
185-
if event_context_extractor:
186-
parent_context = event_context_extractor(lambda_event)
187-
else:
188-
parent_context = _default_event_context_extractor(lambda_event)
189-
190-
return parent_context
206+
links = [Link(get_current_span(env_context).get_span_context())]
191207

208+
return links
192209

193210
def _set_api_gateway_v1_proxy_attributes(
194211
lambda_event: Any, span: Span
@@ -294,6 +311,8 @@ def _instrumented_lambda_handler_call( # noqa pylint: disable=too-many-branches
294311
disable_aws_context_propagation,
295312
)
296313

314+
links = _determine_links(disable_aws_context_propagation)
315+
297316
span_kind = None
298317
try:
299318
if lambda_event["Records"][0]["eventSource"] in {
@@ -315,10 +334,13 @@ def _instrumented_lambda_handler_call( # noqa pylint: disable=too-many-branches
315334

316335
tracer = get_tracer(__name__, __version__, tracer_provider)
317336

337+
338+
318339
with tracer.start_as_current_span(
319340
name=orig_handler_name,
320341
context=parent_context,
321342
kind=span_kind,
343+
links=links
322344
) as span:
323345
if span.is_recording():
324346
lambda_context = args[1]

instrumentation/opentelemetry-instrumentation-aws-lambda/tests/test_aws_lambda_instrumentation_manual.py

Lines changed: 66 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def test_active_tracing(self):
138138
self.assertEqual(len(spans), 1)
139139
span = spans[0]
140140
self.assertEqual(span.name, os.environ[_HANDLER])
141-
self.assertEqual(span.get_span_context().trace_id, MOCK_XRAY_TRACE_ID)
141+
self.assertNotEqual(span.get_span_context().trace_id, MOCK_XRAY_TRACE_ID)
142142
self.assertEqual(span.kind, SpanKind.SERVER)
143143
self.assertSpanHasAttributes(
144144
span,
@@ -149,11 +149,7 @@ def test_active_tracing(self):
149149
)
150150

151151
parent_context = span.parent
152-
self.assertEqual(
153-
parent_context.trace_id, span.get_span_context().trace_id
154-
)
155-
self.assertEqual(parent_context.span_id, MOCK_XRAY_PARENT_SPAN_ID)
156-
self.assertTrue(parent_context.is_remote)
152+
self.assertEqual(None, parent_context)
157153

158154
test_env_patch.stop()
159155

@@ -165,11 +161,8 @@ class TestCase:
165161
context: Dict
166162
expected_traceid: int
167163
expected_parentid: int
168-
xray_traceid: str
169164
expected_state_value: str = None
170165
expected_trace_state_len: int = 0
171-
disable_aws_context_propagation: bool = False
172-
disable_aws_context_propagation_envvar: str = ""
173166

174167
def custom_event_context_extractor(lambda_event):
175168
return get_global_textmap().extract(lambda_event["foo"]["headers"])
@@ -187,11 +180,10 @@ def custom_event_context_extractor(lambda_event):
187180
expected_traceid=MOCK_W3C_TRACE_ID,
188181
expected_parentid=MOCK_W3C_PARENT_SPAN_ID,
189182
expected_trace_state_len=3,
190-
expected_state_value=MOCK_W3C_TRACE_STATE_VALUE,
191-
xray_traceid=MOCK_XRAY_TRACE_CONTEXT_NOT_SAMPLED,
183+
expected_state_value=MOCK_W3C_TRACE_STATE_VALUE
192184
),
193185
TestCase(
194-
name="custom_extractor_not_sampled_xray",
186+
name="custom_extractor",
195187
custom_extractor=custom_event_context_extractor,
196188
context={
197189
"foo": {
@@ -204,76 +196,21 @@ def custom_event_context_extractor(lambda_event):
204196
expected_traceid=MOCK_W3C_TRACE_ID,
205197
expected_parentid=MOCK_W3C_PARENT_SPAN_ID,
206198
expected_trace_state_len=3,
207-
expected_state_value=MOCK_W3C_TRACE_STATE_VALUE,
208-
xray_traceid=MOCK_XRAY_TRACE_CONTEXT_NOT_SAMPLED,
209-
),
210-
TestCase(
211-
name="custom_extractor_sampled_xray",
212-
custom_extractor=custom_event_context_extractor,
213-
context={
214-
"foo": {
215-
"headers": {
216-
TraceContextTextMapPropagator._TRACEPARENT_HEADER_NAME: MOCK_W3C_TRACE_CONTEXT_SAMPLED,
217-
TraceContextTextMapPropagator._TRACESTATE_HEADER_NAME: f"{MOCK_W3C_TRACE_STATE_KEY}={MOCK_W3C_TRACE_STATE_VALUE},foo=1,bar=2",
218-
}
219-
}
220-
},
221-
expected_traceid=MOCK_XRAY_TRACE_ID,
222-
expected_parentid=MOCK_XRAY_PARENT_SPAN_ID,
223-
xray_traceid=MOCK_XRAY_TRACE_CONTEXT_SAMPLED,
224-
),
225-
TestCase(
226-
name="custom_extractor_sampled_xray_disable_aws_propagation",
227-
custom_extractor=custom_event_context_extractor,
228-
context={
229-
"foo": {
230-
"headers": {
231-
TraceContextTextMapPropagator._TRACEPARENT_HEADER_NAME: MOCK_W3C_TRACE_CONTEXT_SAMPLED,
232-
TraceContextTextMapPropagator._TRACESTATE_HEADER_NAME: f"{MOCK_W3C_TRACE_STATE_KEY}={MOCK_W3C_TRACE_STATE_VALUE},foo=1,bar=2",
233-
}
234-
}
235-
},
236-
disable_aws_context_propagation=True,
237-
expected_traceid=MOCK_W3C_TRACE_ID,
238-
expected_parentid=MOCK_W3C_PARENT_SPAN_ID,
239-
expected_trace_state_len=3,
240-
expected_state_value=MOCK_W3C_TRACE_STATE_VALUE,
241-
xray_traceid=MOCK_XRAY_TRACE_CONTEXT_SAMPLED,
242-
),
243-
TestCase(
244-
name="no_custom_extractor_xray_disable_aws_propagation_via_env_var",
245-
custom_extractor=None,
246-
context={
247-
"headers": {
248-
TraceContextTextMapPropagator._TRACEPARENT_HEADER_NAME: MOCK_W3C_TRACE_CONTEXT_SAMPLED,
249-
TraceContextTextMapPropagator._TRACESTATE_HEADER_NAME: f"{MOCK_W3C_TRACE_STATE_KEY}={MOCK_W3C_TRACE_STATE_VALUE},foo=1,bar=2",
250-
}
251-
},
252-
disable_aws_context_propagation=False,
253-
disable_aws_context_propagation_envvar="true",
254-
expected_traceid=MOCK_W3C_TRACE_ID,
255-
expected_parentid=MOCK_W3C_PARENT_SPAN_ID,
256-
expected_trace_state_len=3,
257-
expected_state_value=MOCK_W3C_TRACE_STATE_VALUE,
258-
xray_traceid=MOCK_XRAY_TRACE_CONTEXT_SAMPLED,
259-
),
199+
expected_state_value=MOCK_W3C_TRACE_STATE_VALUE
200+
)
260201
]
261202
for test in tests:
262203
test_env_patch = mock.patch.dict(
263204
"os.environ",
264205
{
265206
**os.environ,
266-
# NOT Active Tracing
267-
_X_AMZN_TRACE_ID: test.xray_traceid,
268-
OTEL_LAMBDA_DISABLE_AWS_CONTEXT_PROPAGATION: test.disable_aws_context_propagation_envvar,
269207
# NOT using the X-Ray Propagator
270208
OTEL_PROPAGATORS: "tracecontext",
271209
},
272210
)
273211
test_env_patch.start()
274212
AwsLambdaInstrumentor().instrument(
275-
event_context_extractor=test.custom_extractor,
276-
disable_aws_context_propagation=test.disable_aws_context_propagation,
213+
event_context_extractor=test.custom_extractor
277214
)
278215
mock_execute_lambda(test.context)
279216
spans = self.memory_exporter.get_finished_spans()
@@ -301,6 +238,64 @@ def custom_event_context_extractor(lambda_event):
301238
AwsLambdaInstrumentor().uninstrument()
302239
test_env_patch.stop()
303240

241+
def test_links_from_lambda_event(self):
242+
@dataclass
243+
class TestCase:
244+
name: str
245+
context: Dict
246+
expected_link_trace_id: int
247+
xray_traceid: str
248+
disable_aws_context_propagation: bool = False
249+
disable_aws_context_propagation_envvar: str = ""
250+
251+
tests = [
252+
TestCase(
253+
name="enabled_aws_context_propagation",
254+
context={},
255+
expected_link_trace_id=MOCK_XRAY_TRACE_ID,
256+
xray_traceid=MOCK_XRAY_TRACE_CONTEXT_NOT_SAMPLED,
257+
),
258+
TestCase(
259+
name="disable_aws_context_propagation",
260+
context={},
261+
expected_link_trace_id=None,
262+
xray_traceid=MOCK_XRAY_TRACE_CONTEXT_NOT_SAMPLED,
263+
disable_aws_context_propagation=True
264+
)
265+
266+
]
267+
for test in tests:
268+
test_env_patch = mock.patch.dict(
269+
"os.environ",
270+
{
271+
**os.environ,
272+
# NOT Active Tracing
273+
_X_AMZN_TRACE_ID: test.xray_traceid,
274+
OTEL_LAMBDA_DISABLE_AWS_CONTEXT_PROPAGATION: test.disable_aws_context_propagation_envvar,
275+
# NOT using the X-Ray Propagator
276+
OTEL_PROPAGATORS: "tracecontext",
277+
},
278+
)
279+
test_env_patch.start()
280+
AwsLambdaInstrumentor().instrument(
281+
disable_aws_context_propagation=test.disable_aws_context_propagation,
282+
)
283+
mock_execute_lambda(test.context)
284+
spans = self.memory_exporter.get_finished_spans()
285+
assert spans
286+
self.assertEqual(len(spans), 1)
287+
span = spans[0]
288+
289+
if test.expected_link_trace_id == None:
290+
self.assertEqual(0, len(span.links))
291+
else:
292+
link = span.links[0]
293+
self.assertEqual(link.context.trace_id, test.expected_link_trace_id)
294+
295+
self.memory_exporter.clear()
296+
AwsLambdaInstrumentor().uninstrument()
297+
test_env_patch.stop()
298+
304299
def test_lambda_no_error_with_invalid_flush_timeout(self):
305300
test_env_patch = mock.patch.dict(
306301
"os.environ",
@@ -327,6 +322,7 @@ def test_lambda_no_error_with_invalid_flush_timeout(self):
327322

328323
test_env_patch.stop()
329324

325+
330326
def test_lambda_handles_multiple_consumers(self):
331327
test_env_patch = mock.patch.dict(
332328
"os.environ",

0 commit comments

Comments
 (0)