Skip to content

fix(lambda): align Python context propagation with JS implementation#727

Merged
wangzlei merged 5 commits intoaws-observability:mainfrom
wangzlei:fix/lambda-context-propagation-align-with-js
Apr 23, 2026
Merged

fix(lambda): align Python context propagation with JS implementation#727
wangzlei merged 5 commits intoaws-observability:mainfrom
wangzlei:fix/lambda-context-propagation-align-with-js

Conversation

@wangzlei
Copy link
Copy Markdown
Contributor

@wangzlei wangzlei commented Apr 21, 2026

Closes #663

Summary

  • Problem: custom_event_context_extractor in otel_wrapper.py always used the X-Ray env var (_X_AMZN_TRACE_ID) as parent context because it is always set and valid when active tracing is enabled, causing upstream W3C trace context (traceparent) from event headers to be ignored and breaking trace continuity.
  • Fix: Align with the JS implementation — inject the X-Ray env var into the event headers (replacing any existing X-Ray header, case-insensitive), then delegate to the global composite propagator via get_global_textmap().extract(). This respects the propagator priority ordering configured in otel-instrument (baggage,xray,tracecontext), where tracecontext (W3C) takes precedence over xray when both are present.
  • Tests updated: Existing tests adapted to explicitly set the global textmap propagator. Two new tests added:
    • test_xray_ignored_when_propagator_does_not_include_xray — X-Ray active tracing on, but only tracecontext propagator configured; W3C headers should be used.
    • test_w3c_takes_precedence_over_xray_when_both_present — both X-Ray and W3C headers present with composite propagator; W3C should win.

Test plan

  • All 4 unit tests pass locally (pytest -v)
  • Coverage: otel_wrapper.py at 90% (uncovered lines are error-handling edge cases)
  • Lint passes: black, isort, flake8 all clean

The custom_event_context_extractor was always using the X-Ray env var
as parent context because _X_AMZN_TRACE_ID is always set and valid
when active tracing is enabled, causing upstream W3C trace context
(traceparent) from event headers to be ignored and breaking trace
continuity.

Align with the JS implementation: inject the X-Ray env var into the
event headers and delegate to the global composite propagator, which
respects propagator priority ordering (xray, then tracecontext).
@wangzlei wangzlei requested a review from a team as a code owner April 21, 2026 00:10
@wangzlei wangzlei enabled auto-merge (squash) April 21, 2026 00:55
jj22ee
jj22ee previously approved these changes Apr 21, 2026
Comment thread lambda-layer/src/otel_wrapper.py Outdated
vastin
vastin previously approved these changes Apr 21, 2026
@wangzlei wangzlei dismissed stale reviews from vastin and jj22ee via 984893b April 23, 2026 03:01
@wangzlei wangzlei merged commit 314aa88 into aws-observability:main Apr 23, 2026
18 checks passed
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: 1 bug, 2 minor items - see inline comments.


return lambda_trace_context
extracted_context = get_global_textmap().extract(headers)
if get_current_span(extracted_context).get_span_context():
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Dead code - condition is always truthy

get_current_span(extracted_context).get_span_context() always returns a SpanContext object (even INVALID_SPAN_CONTEXT). Since SpanContext does not define bool, any instance is truthy in Python. Line 76 (return Context()) is unreachable dead code.

Fix: use .is_valid:
if get_current_span(extracted_context).get_span_context().is_valid:
return extracted_context
return Context()

Or just return extracted_context if the fallback is unnecessary.


return get_global_textmap().extract(headers)
if xray_env_var:
headers = {k: v for k, v in headers.items() if k.lower() != TRACE_HEADER_KEY.lower()}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: TRACE_HEADER_KEY.lower() is recomputed on every iteration of the dict comprehension. Consider pre-computing into a local variable.

assert spans
def test_parent_context_from_lambda_event(self):
original_propagator = get_global_textmap()
set_global_textmap(TraceContextTextMapPropagator())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (test gap): This test uses only TraceContextTextMapPropagator (no X-Ray support), so the injected X-Ray header is always ignored - making it functionally identical to test_xray_ignored_when_propagator_does_not_include_xray. Consider adding a test with a composite propagator and a passthrough X-Ray env var.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

otel_wrapper.py hardcodes AwsXRayPropagator bypassing OTEL_PROPAGATORS and OTEL_LAMBDA_DISABLE_AWS_CONTEXT_PROPAGATION

3 participants