17
17
from unittest import mock
18
18
19
19
from opentelemetry .instrumentation .aws_lambda import AwsLambdaInstrumentor
20
+ from opentelemetry .propagate import get_global_textmap
20
21
from opentelemetry .sdk .extension .aws .trace .propagation .aws_xray_format import (
21
22
TRACE_ID_FIRST_PART_LENGTH ,
23
+ TRACE_ID_VERSION ,
22
24
)
23
25
from opentelemetry .semconv .resource import ResourceAttributes
24
26
from opentelemetry .semconv .trace import SpanAttributes
25
27
from opentelemetry .test .test_base import TestBase
26
28
from opentelemetry .trace import SpanKind
27
29
28
30
_HANDLER = "_HANDLER"
31
+ _X_AMZN_TRACE_ID = "_X_AMZN_TRACE_ID"
29
32
AWS_LAMBDA_EXEC_WRAPPER = "AWS_LAMBDA_EXEC_WRAPPER"
30
33
INSTRUMENTATION_SRC_DIR = os .path .join (
31
34
* (os .path .dirname (__file__ ), ".." , "otel_sdk" )
@@ -42,11 +45,26 @@ def __init__(self, aws_request_id, invoked_function_arn):
42
45
aws_request_id = "mock_aws_request_id" ,
43
46
invoked_function_arn = "arn://mock-lambda-function-arn" ,
44
47
)
45
- MOCK_TRACE_ID = 0x5FB7331105E8BB83207FA31D4D9CDB4C
46
- MOCK_TRACE_ID_HEX_STR = f"{ MOCK_TRACE_ID :32x} "
47
- MOCK_PARENT_SPAN_ID = 0x3328B8445A6DBAD2
48
- MOCK_PARENT_SPAN_ID_STR = f"{ MOCK_PARENT_SPAN_ID :32x} "
49
- MOCK_LAMBDA_TRACE_CONTEXT_SAMPLED = f"Root=1-{ MOCK_TRACE_ID_HEX_STR [:TRACE_ID_FIRST_PART_LENGTH ]} -{ MOCK_TRACE_ID_HEX_STR [TRACE_ID_FIRST_PART_LENGTH :]} ;Parent={ MOCK_PARENT_SPAN_ID_STR } ;Sampled=1"
48
+
49
+ MOCK_XRAY_TRACE_ID = 0x5FB7331105E8BB83207FA31D4D9CDB4C
50
+ MOCK_XRAY_TRACE_ID_STR = f"{ MOCK_XRAY_TRACE_ID :x} "
51
+ MOCK_XRAY_PARENT_SPAN_ID = 0x3328B8445A6DBAD2
52
+ MOCK_XRAY_TRACE_CONTEXT_COMMON = f"Root={ TRACE_ID_VERSION } -{ MOCK_XRAY_TRACE_ID_STR [:TRACE_ID_FIRST_PART_LENGTH ]} -{ MOCK_XRAY_TRACE_ID_STR [TRACE_ID_FIRST_PART_LENGTH :]} ;Parent={ MOCK_XRAY_PARENT_SPAN_ID :x} "
53
+ MOCK_XRAY_TRACE_CONTEXT_SAMPLED = f"{ MOCK_XRAY_TRACE_CONTEXT_COMMON } ;Sampled=1"
54
+ MOCK_XRAY_TRACE_CONTEXT_NOT_SAMPLED = (
55
+ f"{ MOCK_XRAY_TRACE_CONTEXT_COMMON } ;Sampled=0"
56
+ )
57
+
58
+ # Read more:
59
+ # https://www.w3.org/TR/trace-context/#examples-of-http-traceparent-headers
60
+ MOCK_W3C_TRACE_ID = 0x5CE0E9A56015FEC5AADFA328AE398115
61
+ MOCK_W3C_PARENT_SPAN_ID = 0xAB54A98CEB1F0AD2
62
+ MOCK_W3C_TRACE_CONTEXT_SAMPLED = (
63
+ f"00-{ MOCK_W3C_TRACE_ID :x} -{ MOCK_W3C_PARENT_SPAN_ID :x} -01"
64
+ )
65
+
66
+ MOCK_W3C_TRACE_STATE_KEY = "vendor_specific_key"
67
+ MOCK_W3C_TRACE_STATE_VALUE = "test_value"
50
68
51
69
52
70
def mock_aws_lambda_exec_wrapper ():
@@ -63,13 +81,20 @@ def mock_aws_lambda_exec_wrapper():
63
81
exec (open (os .path .join (INSTRUMENTATION_SRC_DIR , "otel-instrument" )).read ())
64
82
65
83
66
- def mock_execute_lambda ():
84
+ def mock_execute_lambda (event = None ):
85
+ """Mocks Lambda importing and then calling the method at the current
86
+ `_HANDLER` environment variable. Like the real Lambda, if
87
+ `AWS_LAMBDA_EXEC_WRAPPER` is defined, if executes that before `_HANDLER`.
88
+
89
+ See more:
90
+ https://aws-otel.github.io/docs/getting-started/lambda/lambda-python
91
+ """
67
92
if os .environ [AWS_LAMBDA_EXEC_WRAPPER ]:
68
93
globals ()[os .environ [AWS_LAMBDA_EXEC_WRAPPER ]]()
69
94
70
95
module_name , handler_name = os .environ [_HANDLER ].split ("." )
71
96
handler_module = import_module ("." .join (module_name .split ("/" )))
72
- getattr (handler_module , handler_name )("mock_event" , MOCK_LAMBDA_CONTEXT )
97
+ getattr (handler_module , handler_name )(event , MOCK_LAMBDA_CONTEXT )
73
98
74
99
75
100
class TestAwsLambdaInstrumentor (TestBase ):
@@ -109,7 +134,7 @@ def test_active_tracing(self):
109
134
"os.environ" ,
110
135
{
111
136
** os .environ ,
112
- " _X_AMZN_TRACE_ID" : MOCK_LAMBDA_TRACE_CONTEXT_SAMPLED ,
137
+ _X_AMZN_TRACE_ID : MOCK_XRAY_TRACE_CONTEXT_SAMPLED ,
113
138
},
114
139
)
115
140
test_env_patch .start ()
@@ -123,7 +148,7 @@ def test_active_tracing(self):
123
148
self .assertEqual (len (spans ), 1 )
124
149
span = spans [0 ]
125
150
self .assertEqual (span .name , os .environ ["ORIG_HANDLER" ])
126
- self .assertEqual (span .get_span_context ().trace_id , MOCK_TRACE_ID )
151
+ self .assertEqual (span .get_span_context ().trace_id , MOCK_XRAY_TRACE_ID )
127
152
self .assertEqual (span .kind , SpanKind .SERVER )
128
153
self .assertSpanHasAttributes (
129
154
span ,
@@ -149,7 +174,112 @@ def test_active_tracing(self):
149
174
self .assertEqual (
150
175
parent_context .trace_id , span .get_span_context ().trace_id
151
176
)
152
- self .assertEqual (parent_context .span_id , MOCK_PARENT_SPAN_ID )
177
+ self .assertEqual (parent_context .span_id , MOCK_XRAY_PARENT_SPAN_ID )
178
+ self .assertTrue (parent_context .is_remote )
179
+
180
+ test_env_patch .stop ()
181
+
182
+ def test_parent_context_from_lambda_event (self ):
183
+ test_env_patch = mock .patch .dict (
184
+ "os.environ" ,
185
+ {
186
+ ** os .environ ,
187
+ # NOT Active Tracing
188
+ _X_AMZN_TRACE_ID : MOCK_XRAY_TRACE_CONTEXT_NOT_SAMPLED ,
189
+ # NOT using the X-Ray Propagator
190
+ "OTEL_PROPAGATORS" : "tracecontext" ,
191
+ },
192
+ )
193
+ test_env_patch .start ()
194
+
195
+ mock_execute_lambda (
196
+ {
197
+ "headers" : {
198
+ "traceparent" : MOCK_W3C_TRACE_CONTEXT_SAMPLED ,
199
+ "tracestate" : f"{ MOCK_W3C_TRACE_STATE_KEY } ={ MOCK_W3C_TRACE_STATE_VALUE } ,foo=1,bar=2" ,
200
+ }
201
+ }
202
+ )
203
+
204
+ spans = self .memory_exporter .get_finished_spans ()
205
+
206
+ assert spans
207
+
208
+ self .assertEqual (len (spans ), 1 )
209
+ span = spans [0 ]
210
+ self .assertEqual (span .get_span_context ().trace_id , MOCK_W3C_TRACE_ID )
211
+
212
+ parent_context = span .parent
213
+ self .assertEqual (
214
+ parent_context .trace_id , span .get_span_context ().trace_id
215
+ )
216
+ self .assertEqual (parent_context .span_id , MOCK_W3C_PARENT_SPAN_ID )
217
+ self .assertEqual (len (parent_context .trace_state ), 3 )
218
+ self .assertEqual (
219
+ parent_context .trace_state .get (MOCK_W3C_TRACE_STATE_KEY ),
220
+ MOCK_W3C_TRACE_STATE_VALUE ,
221
+ )
222
+ self .assertTrue (parent_context .is_remote )
223
+
224
+ test_env_patch .stop ()
225
+
226
+ def test_using_custom_extractor (self ):
227
+ def custom_event_context_extractor (lambda_event ):
228
+ return get_global_textmap ().extract (lambda_event ["foo" ]["headers" ])
229
+
230
+ test_env_patch = mock .patch .dict (
231
+ "os.environ" ,
232
+ {
233
+ ** os .environ ,
234
+ # DO NOT use `otel-instrument` script, resort to "manual"
235
+ # instrumentation below
236
+ AWS_LAMBDA_EXEC_WRAPPER : "" ,
237
+ # NOT Active Tracing
238
+ _X_AMZN_TRACE_ID : MOCK_XRAY_TRACE_CONTEXT_NOT_SAMPLED ,
239
+ # NOT using the X-Ray Propagator
240
+ "OTEL_PROPAGATORS" : "tracecontext" ,
241
+ },
242
+ )
243
+ test_env_patch .start ()
244
+
245
+ # NOTE: Instead of using `AWS_LAMBDA_EXEC_WRAPPER` to point `_HANDLER`
246
+ # to a module which instruments and calls the user `ORIG_HANDLER`, we
247
+ # leave `_HANDLER` as is and replace `AWS_LAMBDA_EXEC_WRAPPER` with this
248
+ # line below. This is like "manual" instrumentation for Lambda.
249
+ AwsLambdaInstrumentor ().instrument (
250
+ event_context_extractor = custom_event_context_extractor ,
251
+ skip_dep_check = True ,
252
+ )
253
+
254
+ mock_execute_lambda (
255
+ {
256
+ "foo" : {
257
+ "headers" : {
258
+ "traceparent" : MOCK_W3C_TRACE_CONTEXT_SAMPLED ,
259
+ "tracestate" : f"{ MOCK_W3C_TRACE_STATE_KEY } ={ MOCK_W3C_TRACE_STATE_VALUE } ,foo=1,bar=2" ,
260
+ }
261
+ }
262
+ }
263
+ )
264
+
265
+ spans = self .memory_exporter .get_finished_spans ()
266
+
267
+ assert spans
268
+
269
+ self .assertEqual (len (spans ), 1 )
270
+ span = spans [0 ]
271
+ self .assertEqual (span .get_span_context ().trace_id , MOCK_W3C_TRACE_ID )
272
+
273
+ parent_context = span .parent
274
+ self .assertEqual (
275
+ parent_context .trace_id , span .get_span_context ().trace_id
276
+ )
277
+ self .assertEqual (parent_context .span_id , MOCK_W3C_PARENT_SPAN_ID )
278
+ self .assertEqual (len (parent_context .trace_state ), 3 )
279
+ self .assertEqual (
280
+ parent_context .trace_state .get (MOCK_W3C_TRACE_STATE_KEY ),
281
+ MOCK_W3C_TRACE_STATE_VALUE ,
282
+ )
153
283
self .assertTrue (parent_context .is_remote )
154
284
155
285
test_env_patch .stop ()
0 commit comments