Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
20949bb
Update trace propagation format for botocore direct invocations
Jul 12, 2021
3aca873
Update client context format in one other place
Jul 12, 2021
90e07ff
Add config for add_datadog_object
Jul 12, 2021
aac4b4f
Refactor modify_client_context and inject_trace_to_client_context
Jul 12, 2021
e27caa5
black
Jul 12, 2021
bde46be
default false
Jul 13, 2021
7ca2a40
Add a test for the config flag
Jul 13, 2021
4cbfa86
Document the config variable
Jul 13, 2021
8c9ac10
Add release notes
Jul 13, 2021
537ca55
add a bunch of newlines for flake8
Jul 13, 2021
81ffcfc
Defaut -> Default
Jul 13, 2021
0ac17e1
add js and clientContext to wordlist
Jul 13, 2021
8deb3b6
Merge branch 'master' into chris.agocs/update_botocore_direct_invocat…
agocs Jul 14, 2021
6dfb19f
Update ddtrace/contrib/botocore/patch.py
agocs Jul 15, 2021
b487f40
Update ddtrace/contrib/botocore/patch.py
agocs Jul 15, 2021
27bc779
Trimmed down on excess documentation
Jul 15, 2021
12115cb
rename config value to BOTOCORE_INVOKE_WITH_LEGACY_CONTEXT
Jul 15, 2021
98477b6
Merge branch 'chris.agocs/update_botocore_direct_invocation_trace_pro…
Jul 15, 2021
0306cfd
run black
Jul 15, 2021
7118727
Merge branch 'master' into chris.agocs/update_botocore_direct_invocat…
Jul 15, 2021
39e3197
Remove clientContext form wordlist
Jul 16, 2021
688bf70
Improve documentation
Jul 21, 2021
21dba2d
Merge branch 'master' into chris.agocs/update_botocore_direct_invocat…
agocs Jul 21, 2021
90f18a3
upgrades -> upgrade
Jul 21, 2021
f0af564
Merge branch 'chris.agocs/update_botocore_direct_invocation_trace_pro…
Jul 21, 2021
16b85e8
re-write release notes
Jul 21, 2021
9efdea9
Merge branch 'master' into chris.agocs/update_botocore_direct_invocat…
brettlangdon Jul 21, 2021
fcc11bf
fix spelling of fnuctions
Jul 21, 2021
7eca5fb
Merge branch 'chris.agocs/update_botocore_direct_invocation_trace_pro…
Jul 21, 2021
4e8b3fa
Merge branch 'master' into chris.agocs/update_botocore_direct_invocat…
mergify[bot] Jul 21, 2021
4830cdc
Merge branch 'master' into chris.agocs/update_botocore_direct_invocat…
mergify[bot] Jul 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ddtrace/contrib/botocore/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@

Default: ``True``

.. py:data:: ddtrace.config.botocore['invoke_with_legacy_context']

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I think I'm good with this name.

@DataDog/apm-python thoughts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻


This preserves legacy behavior when tracing directly invoked Python and Node Lambda
functions instrumented with datadog-lambda-python < v41 or datadog-lambda-js < v3.58.0.

Legacy support for older libraries is available with
``ddtrace.config.botocore.invoke_with_legacy_context = True`` or by setting the environment
variable ``DD_BOTOCORE_INVOKE_WITH_LEGACY_CONTEXT=true``.


Default: ``False``


Example::

Expand Down
42 changes: 21 additions & 21 deletions ddtrace/contrib/botocore/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"botocore",
{
"distributed_tracing": get_env("botocore", "distributed_tracing", default=True),
"invoke_with_legacy_context": get_env("botocore", "invoke_with_legacy_context", default=False),
},
)

Expand Down Expand Up @@ -68,36 +69,35 @@ def inject_trace_to_sqs_message(args, span):
inject_trace_data_to_message_attributes(trace_data, params)


def modify_client_context(client_context_base64, trace_headers):
try:
client_context_json = base64.b64decode(client_context_base64).decode("utf-8")
client_context_object = json.loads(client_context_json)

if "custom" in client_context_object:
client_context_object["custom"]["_datadog"] = trace_headers
else:
client_context_object["custom"] = {"_datadog": trace_headers}
def modify_client_context(client_context_object, trace_headers):
if config.botocore["invoke_with_legacy_context"]:
trace_headers = {"_datadog": trace_headers}

new_context = base64.b64encode(json.dumps(client_context_object).encode("utf-8")).decode("utf-8")
return new_context
except Exception:
log.warning("malformed client_context=%s", client_context_base64, exc_info=True)
return client_context_base64
if "custom" in client_context_object:
client_context_object["custom"].update(trace_headers)
else:
client_context_object["custom"] = trace_headers


def inject_trace_to_client_context(args, span):
trace_headers = {}
HTTPPropagator.inject(span.context, trace_headers)

client_context_object = {}
params = args[1]
if "ClientContext" in params:
params["ClientContext"] = modify_client_context(params["ClientContext"], trace_headers)
else:
trace_headers = {}
HTTPPropagator.inject(span.context, trace_headers)
client_context_object = {"custom": {"_datadog": trace_headers}}
try:
client_context_json = base64.b64decode(params["ClientContext"]).decode("utf-8")
client_context_object = json.loads(client_context_json)
except Exception:
log.warning("malformed client_context=%s", params["ClientContext"], exc_info=True)
return
modify_client_context(client_context_object, trace_headers)
try:
json_context = json.dumps(client_context_object).encode("utf-8")
params["ClientContext"] = base64.b64encode(json_context).decode("utf-8")
except Exception:
log.warning("unable to encode modified client context as json: %s", client_context_object, exc_info=True)
return
params["ClientContext"] = base64.b64encode(json_context).decode("utf-8")


def patch():
Expand Down
1 change: 1 addition & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ integration
integrations
JSON
jinja
js
kombu
kubernetes
kwarg
Expand Down
8 changes: 8 additions & 0 deletions releasenotes/notes/botocore-46f5bf04f64777b4.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
upgrade:
- |
botocore: Update trace propagation format for directly invoked Lambda functions.
This breaks compatibility with Lambda functions instrumented with datadog-lambda-python < v41 or datadog-lambda-js < v3.57.0.
Please upgrade datadog-lambda-* in invoked lambda functions, or engage legacy compatibility mode in one of two ways:
- ddtrace.config.botocore.invoke_with_legacy_context = True
- DD_BOTOCORE_INVOKE_WITH_LEGACY_CONTEXT=true
52 changes: 48 additions & 4 deletions tests/contrib/botocore/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,11 +526,55 @@ def test_lambda_invoke_no_context_client(self):
context_json = base64.b64decode(context_b64.encode()).decode()
context_obj = json.loads(context_json)

self.assertEqual(context_obj["custom"]["_datadog"][HTTP_HEADER_TRACE_ID], str(span.trace_id))
self.assertEqual(context_obj["custom"]["_datadog"][HTTP_HEADER_PARENT_ID], str(span.span_id))
self.assertEqual(context_obj["custom"][HTTP_HEADER_TRACE_ID], str(span.trace_id))
self.assertEqual(context_obj["custom"][HTTP_HEADER_PARENT_ID], str(span.span_id))

lamb.delete_function(FunctionName="ironmaiden")

@mock_lambda
def test_lambda_invoke_with_old_style_trace_propagation(self):
with self.override_config("botocore", dict(invoke_with_legacy_context=True)):
lamb = self.session.create_client("lambda", region_name="us-west-2", endpoint_url="http://localhost:4566")
lamb.create_function(
FunctionName="ironmaiden",
Runtime="python3.7",
Role="test-iam-role",
Handler="lambda_function.lambda_handler",
Code={
"ZipFile": get_zip_lambda(),
},
Publish=True,
Timeout=30,
MemorySize=128,
)

Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(lamb)

lamb.invoke(
FunctionName="ironmaiden",
Payload=json.dumps({}),
)

spans = self.get_spans()
assert spans
span = spans[0]

self.assertEqual(len(spans), 1)
self.assertEqual(span.get_tag("aws.region"), "us-west-2")
self.assertEqual(span.get_tag("aws.operation"), "Invoke")
assert_is_measured(span)
assert_span_http_status_code(span, 200)
self.assertEqual(span.service, "test-botocore-tracing.lambda")
self.assertEqual(span.resource, "lambda.invoke")
context_b64 = span.get_tag("params.ClientContext")
context_json = base64.b64decode(context_b64.encode()).decode()
context_obj = json.loads(context_json)

self.assertEqual(context_obj["custom"]["_datadog"][HTTP_HEADER_TRACE_ID], str(span.trace_id))
self.assertEqual(context_obj["custom"]["_datadog"][HTTP_HEADER_PARENT_ID], str(span.span_id))

lamb.delete_function(FunctionName="ironmaiden")

@mock_lambda
def test_lambda_invoke_distributed_tracing_off(self):
with self.override_config("botocore", dict(distributed_tracing=False)):
Expand Down Expand Up @@ -610,8 +654,8 @@ def test_lambda_invoke_with_context_client(self):
context_obj = json.loads(context_json)

self.assertEqual(context_obj["custom"]["foo"], "bar")
self.assertEqual(context_obj["custom"]["_datadog"][HTTP_HEADER_TRACE_ID], str(span.trace_id))
self.assertEqual(context_obj["custom"]["_datadog"][HTTP_HEADER_PARENT_ID], str(span.span_id))
self.assertEqual(context_obj["custom"][HTTP_HEADER_TRACE_ID], str(span.trace_id))
self.assertEqual(context_obj["custom"][HTTP_HEADER_PARENT_ID], str(span.span_id))

lamb.delete_function(FunctionName="megadeth")

Expand Down