@@ -47,19 +47,30 @@ def lambda_handler(event, context):
47
47
import logging
48
48
import os
49
49
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
52
53
53
- # TODO: aws propagator
54
- from opentelemetry .sdk .extension .aws .trace .propagation .aws_xray_format import (
55
- AwsXRayFormat ,
56
- )
57
54
from opentelemetry .instrumentation .aws_lambda .package import _instruments
58
55
from opentelemetry .instrumentation .aws_lambda .version import __version__
59
56
from opentelemetry .instrumentation .instrumentor import BaseInstrumentor
60
57
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
+ )
61
64
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
63
74
64
75
logger = logging .getLogger (__name__ )
65
76
@@ -69,15 +80,22 @@ def instrumentation_dependencies(self) -> Collection[str]:
69
80
return _instruments
70
81
71
82
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
73
87
74
88
Args:
75
89
**kwargs: Optional arguments
76
90
``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)
77
94
"""
78
95
tracer = get_tracer (
79
96
__name__ , __version__ , kwargs .get ("tracer_provider" )
80
97
)
98
+ event_context_extractor = kwargs .get ("event_context_extractor" )
81
99
82
100
lambda_handler = os .environ .get (
83
101
"ORIG_HANDLER" , os .environ .get ("_HANDLER" )
@@ -87,7 +105,10 @@ def _instrument(self, **kwargs):
87
105
self ._wrapped_function_name = wrapped_names [1 ]
88
106
89
107
_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 ,
91
112
)
92
113
93
114
def _uninstrument (self , ** kwargs ):
@@ -97,16 +118,83 @@ def _uninstrument(self, **kwargs):
97
118
)
98
119
99
120
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
+
101
190
def _instrumented_lambda_handler_call (call_wrapped , instance , args , kwargs ):
102
191
orig_handler_name = "." .join (
103
192
[wrapped_module_name , wrapped_function_name ]
104
193
)
105
194
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 )
110
198
111
199
with tracer .start_as_current_span (
112
200
name = orig_handler_name , context = parent_context , kind = SpanKind .SERVER
0 commit comments