Skip to content

Commit f9bab03

Browse files
agocsKyle-Verhoogbrettlangdonmergify[bot]
authored
Update trace propagation format for botocore direct invocations (#2639)
* Update trace propagation format for botocore direct invocations * Update client context format in one other place * Add config for add_datadog_object * Refactor modify_client_context and inject_trace_to_client_context * black * default false * Add a test for the config flag * Document the config variable * Add release notes * add a bunch of newlines for flake8 * Defaut -> Default * add js and clientContext to wordlist * Update ddtrace/contrib/botocore/patch.py Mutate client_context_object instead of returning Co-authored-by: Kyle Verhoog <[email protected]> * Update ddtrace/contrib/botocore/patch.py Co-authored-by: Kyle Verhoog <[email protected]> * Trimmed down on excess documentation * rename config value to BOTOCORE_INVOKE_WITH_LEGACY_CONTEXT * run black * Remove clientContext form wordlist * Improve documentation * upgrades -> upgrade * re-write release notes * fix spelling of fnuctions Co-authored-by: Kyle Verhoog <[email protected]> Co-authored-by: Brett Langdon <[email protected]> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 642d0b8 commit f9bab03

File tree

5 files changed

+90
-25
lines changed

5 files changed

+90
-25
lines changed

ddtrace/contrib/botocore/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@
2424
2525
Default: ``True``
2626
27+
.. py:data:: ddtrace.config.botocore['invoke_with_legacy_context']
28+
29+
This preserves legacy behavior when tracing directly invoked Python and Node Lambda
30+
functions instrumented with datadog-lambda-python < v41 or datadog-lambda-js < v3.58.0.
31+
32+
Legacy support for older libraries is available with
33+
``ddtrace.config.botocore.invoke_with_legacy_context = True`` or by setting the environment
34+
variable ``DD_BOTOCORE_INVOKE_WITH_LEGACY_CONTEXT=true``.
35+
36+
37+
Default: ``False``
38+
2739
2840
Example::
2941

ddtrace/contrib/botocore/patch.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"botocore",
3838
{
3939
"distributed_tracing": get_env("botocore", "distributed_tracing", default=True),
40+
"invoke_with_legacy_context": get_env("botocore", "invoke_with_legacy_context", default=False),
4041
},
4142
)
4243

@@ -68,36 +69,35 @@ def inject_trace_to_sqs_message(args, span):
6869
inject_trace_data_to_message_attributes(trace_data, params)
6970

7071

71-
def modify_client_context(client_context_base64, trace_headers):
72-
try:
73-
client_context_json = base64.b64decode(client_context_base64).decode("utf-8")
74-
client_context_object = json.loads(client_context_json)
75-
76-
if "custom" in client_context_object:
77-
client_context_object["custom"]["_datadog"] = trace_headers
78-
else:
79-
client_context_object["custom"] = {"_datadog": trace_headers}
72+
def modify_client_context(client_context_object, trace_headers):
73+
if config.botocore["invoke_with_legacy_context"]:
74+
trace_headers = {"_datadog": trace_headers}
8075

81-
new_context = base64.b64encode(json.dumps(client_context_object).encode("utf-8")).decode("utf-8")
82-
return new_context
83-
except Exception:
84-
log.warning("malformed client_context=%s", client_context_base64, exc_info=True)
85-
return client_context_base64
76+
if "custom" in client_context_object:
77+
client_context_object["custom"].update(trace_headers)
78+
else:
79+
client_context_object["custom"] = trace_headers
8680

8781

8882
def inject_trace_to_client_context(args, span):
8983
trace_headers = {}
9084
HTTPPropagator.inject(span.context, trace_headers)
91-
85+
client_context_object = {}
9286
params = args[1]
9387
if "ClientContext" in params:
94-
params["ClientContext"] = modify_client_context(params["ClientContext"], trace_headers)
95-
else:
96-
trace_headers = {}
97-
HTTPPropagator.inject(span.context, trace_headers)
98-
client_context_object = {"custom": {"_datadog": trace_headers}}
88+
try:
89+
client_context_json = base64.b64decode(params["ClientContext"]).decode("utf-8")
90+
client_context_object = json.loads(client_context_json)
91+
except Exception:
92+
log.warning("malformed client_context=%s", params["ClientContext"], exc_info=True)
93+
return
94+
modify_client_context(client_context_object, trace_headers)
95+
try:
9996
json_context = json.dumps(client_context_object).encode("utf-8")
100-
params["ClientContext"] = base64.b64encode(json_context).decode("utf-8")
97+
except Exception:
98+
log.warning("unable to encode modified client context as json: %s", client_context_object, exc_info=True)
99+
return
100+
params["ClientContext"] = base64.b64encode(json_context).decode("utf-8")
101101

102102

103103
def patch():

docs/spelling_wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ integration
6666
integrations
6767
JSON
6868
jinja
69+
js
6970
kombu
7071
kubernetes
7172
kwarg
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
upgrade:
3+
- |
4+
botocore: Update trace propagation format for directly invoked Lambda functions.
5+
This breaks compatibility with Lambda functions instrumented with datadog-lambda-python < v41 or datadog-lambda-js < v3.57.0.
6+
Please upgrade datadog-lambda-* in invoked lambda functions, or engage legacy compatibility mode in one of two ways:
7+
- ddtrace.config.botocore.invoke_with_legacy_context = True
8+
- DD_BOTOCORE_INVOKE_WITH_LEGACY_CONTEXT=true

tests/contrib/botocore/test.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -526,11 +526,55 @@ def test_lambda_invoke_no_context_client(self):
526526
context_json = base64.b64decode(context_b64.encode()).decode()
527527
context_obj = json.loads(context_json)
528528

529-
self.assertEqual(context_obj["custom"]["_datadog"][HTTP_HEADER_TRACE_ID], str(span.trace_id))
530-
self.assertEqual(context_obj["custom"]["_datadog"][HTTP_HEADER_PARENT_ID], str(span.span_id))
529+
self.assertEqual(context_obj["custom"][HTTP_HEADER_TRACE_ID], str(span.trace_id))
530+
self.assertEqual(context_obj["custom"][HTTP_HEADER_PARENT_ID], str(span.span_id))
531531

532532
lamb.delete_function(FunctionName="ironmaiden")
533533

534+
@mock_lambda
535+
def test_lambda_invoke_with_old_style_trace_propagation(self):
536+
with self.override_config("botocore", dict(invoke_with_legacy_context=True)):
537+
lamb = self.session.create_client("lambda", region_name="us-west-2", endpoint_url="http://localhost:4566")
538+
lamb.create_function(
539+
FunctionName="ironmaiden",
540+
Runtime="python3.7",
541+
Role="test-iam-role",
542+
Handler="lambda_function.lambda_handler",
543+
Code={
544+
"ZipFile": get_zip_lambda(),
545+
},
546+
Publish=True,
547+
Timeout=30,
548+
MemorySize=128,
549+
)
550+
551+
Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(lamb)
552+
553+
lamb.invoke(
554+
FunctionName="ironmaiden",
555+
Payload=json.dumps({}),
556+
)
557+
558+
spans = self.get_spans()
559+
assert spans
560+
span = spans[0]
561+
562+
self.assertEqual(len(spans), 1)
563+
self.assertEqual(span.get_tag("aws.region"), "us-west-2")
564+
self.assertEqual(span.get_tag("aws.operation"), "Invoke")
565+
assert_is_measured(span)
566+
assert_span_http_status_code(span, 200)
567+
self.assertEqual(span.service, "test-botocore-tracing.lambda")
568+
self.assertEqual(span.resource, "lambda.invoke")
569+
context_b64 = span.get_tag("params.ClientContext")
570+
context_json = base64.b64decode(context_b64.encode()).decode()
571+
context_obj = json.loads(context_json)
572+
573+
self.assertEqual(context_obj["custom"]["_datadog"][HTTP_HEADER_TRACE_ID], str(span.trace_id))
574+
self.assertEqual(context_obj["custom"]["_datadog"][HTTP_HEADER_PARENT_ID], str(span.span_id))
575+
576+
lamb.delete_function(FunctionName="ironmaiden")
577+
534578
@mock_lambda
535579
def test_lambda_invoke_distributed_tracing_off(self):
536580
with self.override_config("botocore", dict(distributed_tracing=False)):
@@ -610,8 +654,8 @@ def test_lambda_invoke_with_context_client(self):
610654
context_obj = json.loads(context_json)
611655

612656
self.assertEqual(context_obj["custom"]["foo"], "bar")
613-
self.assertEqual(context_obj["custom"]["_datadog"][HTTP_HEADER_TRACE_ID], str(span.trace_id))
614-
self.assertEqual(context_obj["custom"]["_datadog"][HTTP_HEADER_PARENT_ID], str(span.span_id))
657+
self.assertEqual(context_obj["custom"][HTTP_HEADER_TRACE_ID], str(span.trace_id))
658+
self.assertEqual(context_obj["custom"][HTTP_HEADER_PARENT_ID], str(span.span_id))
615659

616660
lamb.delete_function(FunctionName="megadeth")
617661

0 commit comments

Comments
 (0)