Skip to content

Commit 71b7244

Browse files
joeyzhao2018claude
andauthored
fix: parent lazily imported module spans to the function span (#856)
Modules imported during the invocation were parented to whatever span was active at import time. When that is a client span (e.g. botocore lazily importing helpers on the first DynamoDB call), the backend peer service pipeline learns the client's host -> the import span's service and sets peer.service=aws.lambda on DynamoDB spans (APMS-20524). Parent them to the function span instead. Imports during init are unchanged, and span service, names and metrics are not affected. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 9990e88 commit 71b7244

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

‎datadog_lambda/cold_start.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,11 @@ def trace(self, root_nodes: List[ImportNode] = root_nodes):
202202
cold_start_span = self.create_cold_start_span(cold_start_span_start_time_ns)
203203
while root_nodes:
204204
root_node = root_nodes.pop()
205-
parent = root_node.context or cold_start_span
205+
# Modules lazily imported during the invocation hang off the function
206+
# span, not the span active at import time: nesting them under a
207+
# client span (e.g. a DynamoDB call) makes the backend peer service
208+
# pipeline map that client's host to the import span's service.
209+
parent = self.trace_ctx if root_node.context else cold_start_span
206210
self.trace_tree(root_node, parent)
207211
self.finish_span(cold_start_span, cold_start_span_end_time_ns)
208212

‎tests/test_cold_start.py‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,45 @@ def handler(event, context):
325325
assert import_span.trace_id == function_span.trace_id
326326
assert load_span is not None
327327
assert load_span.trace_id == function_span.trace_id
328+
329+
330+
def test_lazy_loaded_package_imports_inside_client_span(monkeypatch):
331+
spans = []
332+
333+
def finish(span):
334+
spans.append(span)
335+
336+
monkeypatch.setattr(wrapper.tracer, "_on_span_finish", finish)
337+
monkeypatch.setattr(wrapper, "is_new_sandbox", lambda: True)
338+
monkeypatch.setattr("datadog_lambda.config.Config.trace_enabled", True)
339+
monkeypatch.setenv(
340+
"DD_COLD_START_TRACE_SKIP_LIB", "ddtrace.contrib.logging,datadog_lambda.wrapper"
341+
)
342+
monkeypatch.setenv("DD_MIN_COLD_START_DURATION", "0")
343+
# ensure the import below is not served from the module cache
344+
monkeypatch.delitem(modules, "colorsys", raising=False)
345+
346+
@wrapper.datadog_lambda_wrapper
347+
def handler(event, context):
348+
# e.g. botocore lazily importing modules on the first DynamoDB call
349+
with wrapper.tracer.trace("dynamodb.command", service="aws.dynamodb") as span:
350+
span.set_tag("span.kind", "client")
351+
import colorsys # noqa: F401
352+
353+
handler({}, get_mock_context())
354+
355+
function_span = client_span = import_span = None
356+
for span in spans:
357+
if span.resource == "colorsys":
358+
import_span = span
359+
elif span.name == "aws.lambda":
360+
function_span = span
361+
elif span.name == "dynamodb.command":
362+
client_span = span
363+
364+
assert function_span is not None
365+
assert client_span is not None
366+
assert import_span is not None
367+
assert import_span.parent_id == function_span.span_id
368+
assert import_span.parent_id != client_span.span_id
369+
assert import_span.trace_id == function_span.trace_id

0 commit comments

Comments
 (0)