Skip to content

Commit f09eaa6

Browse files
fix: honor upstream trace context in Step Functions Execution.Input._datadog (#830)
Legacy Step Functions invocations were ignoring upstream Datadog trace headers nested under Execution.Input._datadog and always hashing Execution.Id instead. Extract shared helper and read _datadog before falling back to deterministic trace id generation. APMSVLS-513 Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent f040916 commit f09eaa6

2 files changed

Lines changed: 124 additions & 7 deletions

File tree

datadog_lambda/tracing.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,21 @@ def _generate_sfn_parent_id(context: dict) -> int:
482482
)
483483

484484

485+
def _extract_dd_trace_id_from_dd_data(dd_data, meta):
486+
"""
487+
Read native Datadog trace headers from a `_datadog` dict.
488+
Returns the 64-bit trace_id and populates meta["_dd.p.tid"] when present.
489+
Returns None if no usable trace id is found.
490+
"""
491+
if not dd_data or "x-datadog-trace-id" not in dd_data:
492+
return None
493+
trace_id = int(dd_data.get("x-datadog-trace-id"))
494+
high_64_bit_trace_id = _parse_high_64_bits(dd_data.get("x-datadog-tags"))
495+
if high_64_bit_trace_id:
496+
meta["_dd.p.tid"] = high_64_bit_trace_id
497+
return trace_id
498+
499+
485500
def _generate_sfn_trace_id(execution_id: str, part: str):
486501
"""
487502
Take the SHA-256 hash of the execution_id to calculate the trace ID. If the high 64 bits are
@@ -517,10 +532,7 @@ def extract_context_from_step_functions(event, lambda_context):
517532

518533
if event.get("serverless-version") == "v1":
519534
if "x-datadog-trace-id" in event: # lambda root
520-
trace_id = int(event.get("x-datadog-trace-id"))
521-
high_64_bit_trace_id = _parse_high_64_bits(event.get("x-datadog-tags"))
522-
if high_64_bit_trace_id:
523-
meta["_dd.p.tid"] = high_64_bit_trace_id
535+
trace_id = _extract_dd_trace_id_from_dd_data(event, meta)
524536
else: # sfn root
525537
root_execution_id = event.get("RootExecutionId")
526538
trace_id = _generate_sfn_trace_id(root_execution_id, LOWER_64_BITS)
@@ -530,9 +542,13 @@ def extract_context_from_step_functions(event, lambda_context):
530542

531543
parent_id = _generate_sfn_parent_id(event)
532544
else:
533-
execution_id = event.get("Execution").get("Id")
534-
trace_id = _generate_sfn_trace_id(execution_id, LOWER_64_BITS)
535-
meta["_dd.p.tid"] = _generate_sfn_trace_id(execution_id, HIGHER_64_BITS)
545+
execution = event.get("Execution", {})
546+
dd_input = execution.get("Input", {}).get("_datadog")
547+
trace_id = _extract_dd_trace_id_from_dd_data(dd_input, meta)
548+
if trace_id is None:
549+
execution_id = execution.get("Id")
550+
trace_id = _generate_sfn_trace_id(execution_id, LOWER_64_BITS)
551+
meta["_dd.p.tid"] = _generate_sfn_trace_id(execution_id, HIGHER_64_BITS)
536552
parent_id = _generate_sfn_parent_id(event)
537553

538554
sampling_priority = SamplingPriority.AUTO_KEEP

tests/test_tracing.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,107 @@ def test_step_function_trace_data(self):
751751
sfn_event, 435175499815315247, 3929055471293792800, "3e7a89d1b7310603"
752752
)
753753

754+
@with_trace_propagation_style("datadog")
755+
def test_step_function_trace_data_with_input_datadog(self):
756+
"""Legacy SFN context with upstream trace in Execution.Input._datadog"""
757+
sfn_event = {
758+
"Execution": {
759+
"Id": "arn:aws:states:sa-east-1:425362996713:execution:rstrat-sfn-lambda-jsonpath-demo-dev-state-machine:7d67bd1e-57df-4021-8b52-f55d5cad1169",
760+
"Input": {
761+
"hello": "world",
762+
"_datadog": {
763+
"x-datadog-trace-id": "12766418539254701015",
764+
"x-datadog-parent-id": "5497030307431673011",
765+
"x-datadog-sampling-priority": "1",
766+
"x-datadog-tags": "_dd.p.dm=-0,_dd.p.tid=69f4ed3a00000000",
767+
"traceparent": "00-69f4ed3a00000000b12b6e05a63cdbd7-4c495ff0aa056cb3-01",
768+
"tracestate": "dd=p:4c495ff0aa056cb3;s:1;t.dm:-0;t.tid:69f4ed3a00000000",
769+
},
770+
},
771+
"StartTime": "2026-05-01T18:13:14.130Z",
772+
"Name": "7d67bd1e-57df-4021-8b52-f55d5cad1169",
773+
"RoleArn": "arn:aws:iam::425362996713:role/rstrat-sfn-lambda-jsonpat-StepFunctionsExecutionRol-mtfEz4KEzBEE",
774+
"RedriveCount": 0,
775+
},
776+
"State": {
777+
"Name": "InvokeDownstreamSecond",
778+
"EnteredTime": "2026-05-01T18:13:16.310Z",
779+
"RetryCount": 0,
780+
},
781+
"StateMachine": {
782+
"Id": "arn:aws:states:sa-east-1:425362996713:stateMachine:rstrat-sfn-lambda-jsonpath-demo-dev-state-machine",
783+
"Name": "rstrat-sfn-lambda-jsonpath-demo-dev-state-machine",
784+
},
785+
}
786+
self._test_step_function_trace_data_common(
787+
sfn_event, 12766418539254701015, 5718818197702795437, "69f4ed3a00000000"
788+
)
789+
790+
@with_trace_propagation_style("datadog")
791+
def test_step_function_trace_data_input_datadog_without_tid(self):
792+
"""Execution.Input._datadog without _dd.p.tid still preserves upstream trace id"""
793+
sfn_event = {
794+
"Execution": {
795+
"Id": "arn:aws:states:sa-east-1:425362996713:execution:abhinav-activity-state-machine:72a7ca3e-901c-41bb-b5a3-5f279b92a316",
796+
"Input": {
797+
"_datadog": {
798+
"x-datadog-trace-id": "5821803790426892636",
799+
},
800+
},
801+
"Name": "72a7ca3e-901c-41bb-b5a3-5f279b92a316",
802+
"RoleArn": "arn:aws:iam::425362996713:role/service-role/StepFunctions-abhinav-activity-state-machine-role-22jpbgl6j",
803+
"StartTime": "2024-12-04T19:38:04.069Z",
804+
"RedriveCount": 0,
805+
},
806+
"State": {
807+
"Name": "Lambda Invoke",
808+
"EnteredTime": "2024-12-04T19:38:04.118Z",
809+
"RetryCount": 0,
810+
},
811+
"StateMachine": {
812+
"Id": "arn:aws:states:sa-east-1:425362996713:stateMachine:abhinav-activity-state-machine",
813+
"Name": "abhinav-activity-state-machine",
814+
},
815+
}
816+
lambda_ctx = get_mock_context()
817+
expected_context = Context(
818+
trace_id=5821803790426892636,
819+
span_id=3929055471293792800,
820+
sampling_priority=1,
821+
meta={},
822+
)
823+
824+
ctx, source, _ = extract_dd_trace_context(sfn_event, lambda_ctx)
825+
826+
self.assertEqual(source, "event")
827+
self.assertEqual(ctx, expected_context)
828+
829+
@with_trace_propagation_style("datadog")
830+
def test_step_function_trace_data_input_without_datadog(self):
831+
"""Execution.Input without _datadog falls back to deterministic trace id"""
832+
sfn_event = {
833+
"Execution": {
834+
"Id": "arn:aws:states:sa-east-1:425362996713:execution:abhinav-activity-state-machine:72a7ca3e-901c-41bb-b5a3-5f279b92a316",
835+
"Input": {"hello": "world"},
836+
"Name": "72a7ca3e-901c-41bb-b5a3-5f279b92a316",
837+
"RoleArn": "arn:aws:iam::425362996713:role/service-role/StepFunctions-abhinav-activity-state-machine-role-22jpbgl6j",
838+
"StartTime": "2024-12-04T19:38:04.069Z",
839+
"RedriveCount": 0,
840+
},
841+
"State": {
842+
"Name": "Lambda Invoke",
843+
"EnteredTime": "2024-12-04T19:38:04.118Z",
844+
"RetryCount": 0,
845+
},
846+
"StateMachine": {
847+
"Id": "arn:aws:states:sa-east-1:425362996713:stateMachine:abhinav-activity-state-machine",
848+
"Name": "abhinav-activity-state-machine",
849+
},
850+
}
851+
self._test_step_function_trace_data_common(
852+
sfn_event, 435175499815315247, 3929055471293792800, "3e7a89d1b7310603"
853+
)
854+
754855
@with_trace_propagation_style("datadog")
755856
def test_step_function_trace_data_retry(self):
756857
"""Test step function trace data extraction with non-zero retry count"""

0 commit comments

Comments
 (0)