Skip to content

Commit 38bf31c

Browse files
committed
Support other propagators in Python Layer
1 parent 1280fb9 commit 38bf31c

File tree

1 file changed

+102
-14
lines changed
  • python/src/otel/otel_sdk/opentelemetry/instrumentation/aws_lambda

1 file changed

+102
-14
lines changed

python/src/otel/otel_sdk/opentelemetry/instrumentation/aws_lambda/__init__.py

+102-14
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,30 @@ def lambda_handler(event, context):
4747
import logging
4848
import os
4949
from importlib import import_module
50-
from typing import Collection
51-
from wrapt import wrap_function_wrapper
50+
from typing import Any, Collection
51+
52+
from opentelemetry.context.context import Context
5253

53-
# TODO: aws propagator
54-
from opentelemetry.sdk.extension.aws.trace.propagation.aws_xray_format import (
55-
AwsXRayFormat,
56-
)
5754
from opentelemetry.instrumentation.aws_lambda.package import _instruments
5855
from opentelemetry.instrumentation.aws_lambda.version import __version__
5956
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
6057
from opentelemetry.instrumentation.utils import unwrap
58+
from opentelemetry.propagate import get_global_textmap
59+
60+
from opentelemetry.sdk.extension.aws.trace.propagation.aws_xray_format import (
61+
AwsXRayFormat,
62+
TRACE_HEADER_KEY,
63+
)
6164
from opentelemetry.semconv.trace import SpanAttributes
62-
from opentelemetry.trace import SpanKind, get_tracer, get_tracer_provider
65+
from opentelemetry.trace import (
66+
SpanKind,
67+
Tracer,
68+
get_tracer,
69+
get_tracer_provider,
70+
)
71+
from opentelemetry.trace.propagation import get_current_span
72+
73+
from wrapt import wrap_function_wrapper
6374

6475
logger = logging.getLogger(__name__)
6576

@@ -69,15 +80,22 @@ def instrumentation_dependencies(self) -> Collection[str]:
6980
return _instruments
7081

7182
def _instrument(self, **kwargs):
72-
"""Instruments Lambda Handlers on AWS Lambda
83+
"""Instruments Lambda Handlers on AWS Lambda.
84+
85+
Read more about how instrumentation is decided:
86+
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/aws-lambda.md#instrumenting-aws-lambda
7387
7488
Args:
7589
**kwargs: Optional arguments
7690
``tracer_provider``: a TracerProvider, defaults to global
91+
``event_context_extractor``: a method which takes the Lambda
92+
Event as input and provides the object which contains the
93+
context as output (usually HTTP headers)
7794
"""
7895
tracer = get_tracer(
7996
__name__, __version__, kwargs.get("tracer_provider")
8097
)
98+
event_context_extractor = kwargs.get("event_context_extractor")
8199

82100
lambda_handler = os.environ.get(
83101
"ORIG_HANDLER", os.environ.get("_HANDLER")
@@ -87,7 +105,10 @@ def _instrument(self, **kwargs):
87105
self._wrapped_function_name = wrapped_names[1]
88106

89107
_instrument(
90-
tracer, self._wrapped_module_name, self._wrapped_function_name
108+
tracer,
109+
self._wrapped_module_name,
110+
self._wrapped_function_name,
111+
event_context_extractor,
91112
)
92113

93114
def _uninstrument(self, **kwargs):
@@ -97,16 +118,83 @@ def _uninstrument(self, **kwargs):
97118
)
98119

99120

100-
def _instrument(tracer, wrapped_module_name, wrapped_function_name):
121+
def _default_event_context_extractor(lambda_event: Any) -> Context:
122+
"""Default way of extracting the context from the Lambda Event.
123+
124+
Assumes the Lambda Event is a map with the headers under the 'headers' key.
125+
This is the mapping to use when the Lambda is invoked by an API Gateway
126+
REST API where API Gateway is acting as a pure proxy for the request.
127+
128+
Args:
129+
lambda_event: user-defined, so it could be anything, but this
130+
method counts it being a map with a 'headers' key
131+
Returns:
132+
A Context with configuration found in the carrier.
133+
134+
"""
135+
try:
136+
headers = lambda_event["headers"]
137+
except (TypeError, KeyError):
138+
logger.warning("Failed to extract context from Lambda Event.")
139+
headers = {}
140+
return get_global_textmap().extract(headers)
141+
142+
143+
def _instrument(
144+
tracer: Tracer,
145+
wrapped_module_name,
146+
wrapped_function_name,
147+
event_context_extractor=None,
148+
):
149+
def _determine_parent_context(lambda_event: Any) -> Context:
150+
"""Determine the parent context for the current Lambda invocation.
151+
152+
Refer:
153+
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/aws-lambda.md#determining-the-parent-of-a-span
154+
155+
Args:
156+
lambda_event: user-defined, so it could be anything, but this
157+
method counts it being a map with a 'headers' key
158+
Returns:
159+
A Context with configuration found in the carrier.
160+
161+
"""
162+
parent_context = None
163+
164+
xray_env_var = os.environ.get("_X_AMZN_TRACE_ID")
165+
166+
if xray_env_var:
167+
parent_context = AwsXRayFormat().extract(
168+
{TRACE_HEADER_KEY: xray_env_var}
169+
)
170+
171+
if (
172+
parent_context
173+
and get_current_span(parent_context)
174+
.get_span_context()
175+
.trace_flags.sampled
176+
):
177+
return parent_context
178+
179+
logger.debug(
180+
"X-Ray propagation failed, use user-configured propagators to extract context from Lambda Event."
181+
)
182+
183+
if event_context_extractor:
184+
parent_context = event_context_extractor(lambda_event)
185+
else:
186+
parent_context = _default_event_context_extractor(lambda_event)
187+
188+
return parent_context
189+
101190
def _instrumented_lambda_handler_call(call_wrapped, instance, args, kwargs):
102191
orig_handler_name = ".".join(
103192
[wrapped_module_name, wrapped_function_name]
104193
)
105194

106-
# TODO: enable propagate from AWS by env variable
107-
xray_trace_id = os.environ.get("_X_AMZN_TRACE_ID", "")
108-
propagator = AwsXRayFormat()
109-
parent_context = propagator.extract({"X-Amzn-Trace-Id": xray_trace_id})
195+
lambda_event = args[0]
196+
197+
parent_context = _determine_parent_context(lambda_event)
110198

111199
with tracer.start_as_current_span(
112200
name=orig_handler_name, context=parent_context, kind=SpanKind.SERVER

0 commit comments

Comments
 (0)