Skip to content

Commit 9646080

Browse files
authored
[Tracing] Adds SQS and LambdaContext Trace extraction (#105)
* adds sqs and lambda trace extraction * format * fix parsing error * remove extra whitespace * format again * add integration tests update * add lambda context fallback + int tests * add fallbacks * make client context extraction more resilient * fix lint * nit comment
1 parent f345e6a commit 9646080

39 files changed

+382
-190
lines changed

datadog_lambda/tracing.py

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import logging
77
import os
8+
import json
89

910
from aws_xray_sdk.core import xray_recorder
1011
from aws_xray_sdk.core.lambda_launcher import LambdaContext
@@ -89,7 +90,67 @@ def _context_obj_to_headers(obj):
8990
}
9091

9192

92-
def extract_dd_trace_context(event):
93+
def extract_context_from_lambda_context(lambda_context):
94+
"""
95+
Extract Datadog trace context from the `client_context` attr
96+
from the Lambda `context` object.
97+
98+
dd_trace libraries inject this trace context on synchronous invocations
99+
"""
100+
client_context = lambda_context.client_context
101+
102+
if client_context and "custom" in client_context:
103+
dd_data = client_context.get("custom", {}).get("_datadog", {})
104+
trace_id = dd_data.get(TraceHeader.TRACE_ID)
105+
parent_id = dd_data.get(TraceHeader.PARENT_ID)
106+
sampling_priority = dd_data.get(TraceHeader.SAMPLING_PRIORITY)
107+
108+
return trace_id, parent_id, sampling_priority
109+
110+
return None, None, None
111+
112+
113+
def extract_context_from_http_event_or_context(event, lambda_context):
114+
"""
115+
Extract Datadog trace context from the `headers` key in from the Lambda
116+
`event` object.
117+
118+
Falls back to lambda context if no trace data is found in the `headers`
119+
"""
120+
headers = event.get("headers", {})
121+
lowercase_headers = {k.lower(): v for k, v in headers.items()}
122+
123+
trace_id = lowercase_headers.get(TraceHeader.TRACE_ID)
124+
parent_id = lowercase_headers.get(TraceHeader.PARENT_ID)
125+
sampling_priority = lowercase_headers.get(TraceHeader.SAMPLING_PRIORITY)
126+
127+
if not trace_id or not parent_id or not sampling_priority:
128+
return extract_context_from_lambda_context(lambda_context)
129+
130+
return trace_id, parent_id, sampling_priority
131+
132+
133+
def extract_context_from_sqs_event_or_context(event, lambda_context):
134+
"""
135+
Extract Datadog trace context from the first SQS message attributes.
136+
137+
Falls back to lambda context if no trace data is found in the SQS message attributes.
138+
"""
139+
try:
140+
first_record = event["Records"][0]
141+
msg_attributes = first_record.get("messageAttributes", {})
142+
dd_json_data = msg_attributes.get("_datadog", {}).get("StringValue", r"{}")
143+
dd_data = json.loads(dd_json_data)
144+
trace_id = dd_data.get(TraceHeader.TRACE_ID)
145+
parent_id = dd_data.get(TraceHeader.PARENT_ID)
146+
sampling_priority = dd_data.get(TraceHeader.SAMPLING_PRIORITY)
147+
148+
return trace_id, parent_id, sampling_priority
149+
except Exception:
150+
return extract_context_from_lambda_context(lambda_context)
151+
152+
153+
def extract_dd_trace_context(event, lambda_context):
93154
"""
94155
Extract Datadog trace context from the Lambda `event` object.
95156
@@ -101,14 +162,26 @@ def extract_dd_trace_context(event):
101162
the correct context.
102163
"""
103164
global dd_trace_context
104-
headers = event.get("headers", {})
105-
lowercase_headers = {k.lower(): v for k, v in headers.items()}
106165

107-
trace_id = lowercase_headers.get(TraceHeader.TRACE_ID)
108-
parent_id = lowercase_headers.get(TraceHeader.PARENT_ID)
109-
sampling_priority = lowercase_headers.get(TraceHeader.SAMPLING_PRIORITY)
166+
if "headers" in event:
167+
(
168+
trace_id,
169+
parent_id,
170+
sampling_priority,
171+
) = extract_context_from_http_event_or_context(event, lambda_context)
172+
elif "Records" in event:
173+
(
174+
trace_id,
175+
parent_id,
176+
sampling_priority,
177+
) = extract_context_from_sqs_event_or_context(event, lambda_context)
178+
else:
179+
trace_id, parent_id, sampling_priority = extract_context_from_lambda_context(
180+
lambda_context
181+
)
182+
110183
if trace_id and parent_id and sampling_priority:
111-
logger.debug("Extracted Datadog trace context from headers")
184+
logger.debug("Extracted Datadog trace context from event or context")
112185
metadata = {
113186
"trace-id": trace_id,
114187
"parent-id": parent_id,

datadog_lambda/wrapper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def _before(self, event, context):
119119
set_cold_start()
120120
submit_invocations_metric(context)
121121
# Extract Datadog trace context from incoming requests
122-
dd_context = extract_dd_trace_context(event)
122+
dd_context = extract_dd_trace_context(event, context)
123123

124124
self.span = None
125125
if dd_tracing_enabled:

scripts/run_integration_tests.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,9 @@ for _sls_type in "${CONFIGS[@]}"; do
148148
sed -E "s/(\"duration\"\: )[0-9\.\-]+/\1XXXX/g" |
149149
sed -E "s/(\"start\"\: )[0-9\.\-]+/\1XXXX/g" |
150150
sed -E "s/(\"system\.pid\"\: )[0-9\.\-]+/\1XXXX/g" |
151-
sed -E "s/(\"runtime-id\"\: \")[a-z0-9\.\-]+/\1XXXX/g"
151+
sed -E "s/(\"runtime-id\"\: \")[a-z0-9\.\-]+/\1XXXX/g" |
152+
sed -E "s/(\"datadog_lambda\"\: \")([0-9]+\.[0-9]+\.[0-9])/\1X.X.X/g" |
153+
sed -E "s/(\"dd_trace\"\: \")([0-9]+\.[0-9]+\.[0-9])/\1X.X.X/g"
152154
)
153155

154156
if [ ! -f $function_snapshot_path ]; then

scripts/run_tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ do
2323
docker run -v `pwd`:/datadog-lambda-python \
2424
-w /datadog-lambda-python \
2525
datadog-lambda-python-test:$python_version \
26-
flake8
26+
flake8 datadog_lambda/
2727
done

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
install_requires=[
3131
"aws-xray-sdk==2.6.0",
3232
"datadog==0.39.0",
33-
"ddtrace==0.44.0",
33+
"ddtrace==0.45.0",
3434
"wrapt==1.11.2",
3535
"setuptools==42.0.2",
3636
],

tests/integration/input_events/sqs.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
"SenderId": "AIDAIENQZJOLO23YVJ4VO",
1111
"ApproximateFirstReceiveTimestamp": "1545082649185"
1212
},
13-
"messageAttributes": {},
13+
"messageAttributes": {
14+
"_datadog": {
15+
"StringValue": "{\"x-datadog-trace-id\":\"666\",\"x-datadog-parent-id\":\"777\",\"x-datadog-sampling-priority\":\"1\"}"
16+
}
17+
},
1418
"md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3",
1519
"eventSource": "aws:sqs",
1620
"eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:my-queue",

tests/integration/snapshots/logs/async-metrics_python27.log

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/var/runtime/botocore/vendored/requests/__init__.py:91: RequestsDependencyWarning: urllib3 (1.26.2) or chardet (3.0.4) doesn't match a supported version!
1+
/var/runtime/botocore/vendored/requests/__init__.py:91: RequestsDependencyWarning: urllib3 (1.26.2) or chardet (4.0.0) doesn't match a supported version!
22
RequestsDependencyWarning)
33
START RequestId: XXXX Version: $LATEST
44
{"e": XXXX, "m": "aws.lambda.enhanced.invocations", "t": ["region:us-east-1", "account_id:XXXX", "functionname:integration-dev-async-metrics_python27", "resource:integration-dev-async-metrics_python27", "cold_start:true", "memorysize:1024", "runtime:python2.7", "datadog_lambda:vXX", "dd_lambda_layer:datadog-python27_2.XX.0"], "v": 1}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
1-
/var/runtime/botocore/vendored/requests/__init__.py:91: RequestsDependencyWarning: urllib3 (1.26.2) or chardet (3.0.4) doesn't match a supported version!
1+
/var/runtime/botocore/vendored/requests/__init__.py:91: RequestsDependencyWarning: urllib3 (1.26.2) or chardet (4.0.0) doesn't match a supported version!
22
RequestsDependencyWarning)
33
START RequestId: XXXX Version: $LATEST
44
{"e": XXXX, "m": "aws.lambda.enhanced.invocations", "t": ["region:us-east-1", "account_id:XXXX", "functionname:integration-plugin-dev-async-metrics_python27_with_plugin", "resource:integration-plugin-dev-async-metrics_python27_with_plugin", "cold_start:true", "memorysize:1024", "runtime:python2.7", "datadog_lambda:vXX", "dd_lambda_layer:datadog-python27_2.XX.0"], "v": 1}
55
{"e": XXXX, "m": "hello.dog", "t": ["team:serverless", "role:hello", "dd_lambda_layer:datadog-python27_2.XX.0"], "v": 1}
66
{"e": XXXX, "m": "tests.integration.count", "t": ["test:integration", "role:hello", "dd_lambda_layer:datadog-python27_2.XX.0"], "v": 21}
7-
{"traces": [[{"resource": "integration-plugin-dev-async-metrics_python27_with_plugin", "name": "aws.lambda", "service": "aws.lambda", "start": XXXX, "trace_id": "XXXX", "metrics": {"_sampling_priority_v1": 2, "system.pid": XXXX}, "parent_id": "XXXX", "meta": {"runtime-id": "XXXX", "request_id": "XXXX", "cold_start": "true", "datadog_lambda": "2.24.0", "function_arn": "arn:aws:lambda:us-east-1:601427279990:function:integration-plugin-dev-async-metrics_python27_with_plugin", "dd_trace": "0.44.0", "_dd.origin": "lambda", "_dd.parent_source": "xray", "resource_names": "integration-plugin-dev-async-metrics_python27_with_plugin", "function_version": "$LATEST"}, "error": 0, "duration": XXXX, "type": "serverless", "span_id": "XXXX"}]]}
7+
{"traces": [[{"resource": "integration-plugin-dev-async-metrics_python27_with_plugin", "name": "aws.lambda", "service": "aws.lambda", "start": XXXX, "trace_id": "XXXX", "metrics": {"_sampling_priority_v1": 2, "system.pid": XXXX}, "parent_id": "XXXX", "meta": {"runtime-id": "XXXX", "request_id": "XXXX", "cold_start": "true", "datadog_lambda": "X.X.X", "function_arn": "arn:aws:lambda:us-east-1:601427279990:function:integration-plugin-dev-async-metrics_python27_with_plugin", "dd_trace": "X.X.X", "_dd.origin": "lambda", "_dd.parent_source": "xray", "resource_names": "integration-plugin-dev-async-metrics_python27_with_plugin", "function_version": "$LATEST"}, "error": 0, "duration": XXXX, "type": "serverless", "span_id": "XXXX"}]]}
88
END RequestId: XXXX
99
REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms
1010
XRAY TraceId: XXXX SegmentId: XXXX Sampled: true
1111
START RequestId: XXXX Version: $LATEST
1212
{"e": XXXX, "m": "aws.lambda.enhanced.invocations", "t": ["region:us-east-1", "account_id:XXXX", "functionname:integration-plugin-dev-async-metrics_python27_with_plugin", "resource:integration-plugin-dev-async-metrics_python27_with_plugin", "cold_start:false", "memorysize:1024", "runtime:python2.7", "datadog_lambda:vXX", "dd_lambda_layer:datadog-python27_2.XX.0"], "v": 1}
1313
{"e": XXXX, "m": "hello.dog", "t": ["team:serverless", "role:hello", "dd_lambda_layer:datadog-python27_2.XX.0"], "v": 1}
1414
{"e": XXXX, "m": "tests.integration.count", "t": ["test:integration", "role:hello", "dd_lambda_layer:datadog-python27_2.XX.0"], "v": 21}
15-
{"traces": [[{"resource": "integration-plugin-dev-async-metrics_python27_with_plugin", "name": "aws.lambda", "service": "aws.lambda", "start": XXXX, "trace_id": "XXXX", "metrics": {"_sampling_priority_v1": 2, "system.pid": XXXX}, "parent_id": "XXXX", "meta": {"runtime-id": "XXXX", "request_id": "XXXX", "cold_start": "false", "datadog_lambda": "2.24.0", "function_arn": "arn:aws:lambda:us-east-1:601427279990:function:integration-plugin-dev-async-metrics_python27_with_plugin", "dd_trace": "0.44.0", "_dd.origin": "lambda", "_dd.parent_source": "xray", "resource_names": "integration-plugin-dev-async-metrics_python27_with_plugin", "function_version": "$LATEST"}, "error": 0, "duration": XXXX, "type": "serverless", "span_id": "XXXX"}]]}
15+
{"traces": [[{"resource": "integration-plugin-dev-async-metrics_python27_with_plugin", "name": "aws.lambda", "service": "aws.lambda", "start": XXXX, "trace_id": "XXXX", "metrics": {"_sampling_priority_v1": 2, "system.pid": XXXX}, "parent_id": "XXXX", "meta": {"runtime-id": "XXXX", "request_id": "XXXX", "cold_start": "false", "datadog_lambda": "X.X.X", "function_arn": "arn:aws:lambda:us-east-1:601427279990:function:integration-plugin-dev-async-metrics_python27_with_plugin", "dd_trace": "X.X.X", "_dd.origin": "lambda", "_dd.parent_source": "xray", "resource_names": "integration-plugin-dev-async-metrics_python27_with_plugin", "function_version": "$LATEST"}, "error": 0, "duration": XXXX, "type": "serverless", "span_id": "XXXX"}]]}
1616
END RequestId: XXXX
1717
REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB
1818
XRAY TraceId: XXXX SegmentId: XXXX Sampled: true
1919
START RequestId: XXXX Version: $LATEST
2020
{"e": XXXX, "m": "aws.lambda.enhanced.invocations", "t": ["region:us-east-1", "account_id:XXXX", "functionname:integration-plugin-dev-async-metrics_python27_with_plugin", "resource:integration-plugin-dev-async-metrics_python27_with_plugin", "cold_start:false", "memorysize:1024", "runtime:python2.7", "datadog_lambda:vXX", "dd_lambda_layer:datadog-python27_2.XX.0"], "v": 1}
2121
{"e": XXXX, "m": "hello.dog", "t": ["team:serverless", "role:hello", "dd_lambda_layer:datadog-python27_2.XX.0"], "v": 1}
2222
{"e": XXXX, "m": "tests.integration.count", "t": ["test:integration", "role:hello", "dd_lambda_layer:datadog-python27_2.XX.0"], "v": 21}
23-
{"traces": [[{"resource": "integration-plugin-dev-async-metrics_python27_with_plugin", "name": "aws.lambda", "service": "aws.lambda", "start": XXXX, "trace_id": "XXXX", "metrics": {"_sampling_priority_v1": 2, "system.pid": XXXX}, "parent_id": "XXXX", "meta": {"runtime-id": "XXXX", "request_id": "XXXX", "cold_start": "false", "datadog_lambda": "2.24.0", "function_arn": "arn:aws:lambda:us-east-1:601427279990:function:integration-plugin-dev-async-metrics_python27_with_plugin", "dd_trace": "0.44.0", "_dd.origin": "lambda", "_dd.parent_source": "xray", "resource_names": "integration-plugin-dev-async-metrics_python27_with_plugin", "function_version": "$LATEST"}, "error": 0, "duration": XXXX, "type": "serverless", "span_id": "XXXX"}]]}
23+
{"traces": [[{"resource": "integration-plugin-dev-async-metrics_python27_with_plugin", "name": "aws.lambda", "service": "aws.lambda", "start": XXXX, "trace_id": "XXXX", "metrics": {"_sampling_priority_v1": 1, "system.pid": XXXX}, "parent_id": "XXXX", "meta": {"runtime-id": "XXXX", "request_id": "XXXX", "cold_start": "false", "datadog_lambda": "X.X.X", "function_arn": "arn:aws:lambda:us-east-1:601427279990:function:integration-plugin-dev-async-metrics_python27_with_plugin", "dd_trace": "X.X.X", "_dd.origin": "lambda", "resource_names": "integration-plugin-dev-async-metrics_python27_with_plugin", "function_version": "$LATEST"}, "error": 0, "duration": XXXX, "type": "serverless", "span_id": "XXXX"}]]}
2424
END RequestId: XXXX
2525
REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB
2626
XRAY TraceId: XXXX SegmentId: XXXX Sampled: true

tests/integration/snapshots/logs/async-metrics_python36.log

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/var/runtime/botocore/vendored/requests/__init__.py:91: RequestsDependencyWarning: urllib3 (1.26.2) or chardet (3.0.4) doesn't match a supported version!
1+
/var/runtime/botocore/vendored/requests/__init__.py:91: RequestsDependencyWarning: urllib3 (1.26.2) or chardet (4.0.0) doesn't match a supported version!
22
RequestsDependencyWarning)
33
START RequestId: XXXX Version: $LATEST
44
{"m": "aws.lambda.enhanced.invocations", "v": 1, "e": XXXX, "t": ["region:us-east-1", "account_id:XXXX", "functionname:integration-dev-async-metrics_python36", "resource:integration-dev-async-metrics_python36", "cold_start:true", "memorysize:1024", "runtime:python3.6", "datadog_lambda:vXX", "dd_lambda_layer:datadog-python36_2.XX.0"]}

0 commit comments

Comments
 (0)