@@ -659,19 +659,19 @@ def is_durable_execution_replay(event):
659659 return is_replay
660660
661661
662+ _TRACE_CHECKPOINT_PREFIX = "_dd_trace_context_"
663+
664+
662665def extract_context_from_durable_execution (event , lambda_context ):
663666 """
664667 Extract Datadog trace context from AWS Lambda Durable Execution event.
665668
666- Looks for extra trace context checkpoints created by the dd-trace plugin.
667- These are STEP operations with Name="_dd_trace_context" that store trace
668- headers in their StepDetails.Result payload. Customer operation payloads
669- are never read or modified.
670-
671- Scans operations in reverse to find the LAST trace checkpoint, which
672- corresponds to the most recently completed customer operation. This gives
673- proper parent chaining: each invocation's root span is parented to the
674- last operation span from the previous invocation.
669+ Looks for trace-context checkpoints created by the dd-trace-py integration
670+ on previous invocations. Checkpoints are STEP operations named
671+ ``_dd_trace_context_{N}`` with trace headers stored as their StepDetails.Result
672+ payload. The one with the highest ``{N}`` wins — it corresponds to the
673+ latest trace-context state from the previous invocation. Customer
674+ operation payloads are never read or modified.
675675 """
676676 try :
677677 if not isinstance (event , dict ):
@@ -687,54 +687,59 @@ def extract_context_from_durable_execution(event, lambda_context):
687687
688688 print (f"[DD-DURABLE] Found { len (operations )} operations in InitialExecutionState" )
689689
690- # Scan in reverse to find the LAST trace context checkpoint
691- # (corresponds to the most recently completed customer operation)
692- for idx in range (len (operations ) - 1 , - 1 , - 1 ):
693- operation = operations [idx ]
690+ # Collect all _dd_trace_context_{N} checkpoints, then pick the highest N
691+ candidates = [] # list of (number, operation)
692+ for operation in operations :
694693 op_name = operation .get ("Name" )
695-
696- if op_name != "_dd_trace_context" :
694+ if not op_name or not op_name .startswith (_TRACE_CHECKPOINT_PREFIX ):
697695 continue
696+ suffix = op_name [len (_TRACE_CHECKPOINT_PREFIX ):]
697+ try :
698+ number = int (suffix )
699+ except ValueError :
700+ continue
701+ candidates .append ((number , operation ))
698702
699- operation_id = operation .get ("Id" )
700- print (f"[DD-DURABLE] Found trace checkpoint: id={ operation_id } , index={ idx } " )
703+ if not candidates :
704+ print ("[DD-DURABLE] No trace context checkpoints found in operations" )
705+ return None
701706
702- # Trace context is in StepDetails.Result (standard STEP format)
703- step_details = operation .get ("StepDetails" , {})
704- payload_str = step_details .get ("Result" )
707+ candidates .sort (key = lambda t : t [0 ])
708+ number , operation = candidates [- 1 ]
709+ operation_id = operation .get ("Id" )
710+ op_name = operation .get ("Name" )
711+ print (f"[DD-DURABLE] Using latest trace checkpoint: name={ op_name } , id={ operation_id } " )
705712
706- if not payload_str :
707- print (f"[DD-DURABLE] Trace checkpoint { operation_id } has no Result, skipping" )
708- continue
713+ step_details = operation .get ("StepDetails" , {})
714+ payload_str = step_details .get ("Result" )
715+ if not payload_str :
716+ print (f"[DD-DURABLE] Trace checkpoint { op_name } has no Result, skipping" )
717+ return None
709718
710- try :
711- payload = json .loads (payload_str )
712- if not isinstance (payload , dict ):
713- print (f"[DD-DURABLE] Trace checkpoint payload is not a dict: { type (payload )} " )
714- continue
715-
716- trace_id = payload .get ("x-datadog-trace-id" )
717- span_id = payload .get ("x-datadog-parent-id" )
718-
719- if trace_id and span_id :
720- # Use HTTPPropagator to restore full context including
721- # baggage, _dd.p.* tags, origin, and sampling priority
722- context = propagator .extract (payload )
723- if context and context .trace_id :
724- print (f"[DD-DURABLE] Extracted trace context from trace checkpoint { operation_id } " )
725- print (f"[DD-DURABLE] trace_id={ trace_id } , span_id={ span_id } , headers={ list (payload .keys ())} " )
726- logger .debug (
727- "Extracted Datadog trace context from trace checkpoint %s: %s" ,
728- operation_id ,
729- context ,
730- )
731- return context
732- except (json .JSONDecodeError , TypeError , ValueError ) as e :
733- print (f"[DD-DURABLE] Failed to parse trace checkpoint payload: { e } " )
734- logger .debug ("Failed to parse trace checkpoint payload: %s" , e )
735- continue
719+ try :
720+ payload = json .loads (payload_str )
721+ except (json .JSONDecodeError , TypeError , ValueError ) as e :
722+ print (f"[DD-DURABLE] Failed to parse trace checkpoint payload: { e } " )
723+ logger .debug ("Failed to parse trace checkpoint payload: %s" , e )
724+ return None
725+
726+ if not isinstance (payload , dict ):
727+ print (f"[DD-DURABLE] Trace checkpoint payload is not a dict: { type (payload )} " )
728+ return None
736729
737- print ("[DD-DURABLE] No trace context checkpoints found in operations" )
730+ context = propagator .extract (payload )
731+ if context and context .trace_id :
732+ print (
733+ f"[DD-DURABLE] Extracted trace context from { op_name } : "
734+ f"trace_id={ context .trace_id } , span_id={ context .span_id } , "
735+ f"headers={ list (payload .keys ())} "
736+ )
737+ logger .debug (
738+ "Extracted Datadog trace context from trace checkpoint %s: %s" ,
739+ op_name ,
740+ context ,
741+ )
742+ return context
738743 except Exception as e :
739744 logger .debug ("Failed to extract trace context from durable execution: %s" , e )
740745
@@ -1161,12 +1166,16 @@ def process_injected_data(event, request_time_epoch_ms, args, tags):
11611166 start_time_ns = int (
11621167 injected_authorizer_data .get (Headers .Parent_Span_Finish_Time )
11631168 )
1164- integration_latency = int (
1165- event ["requestContext" ]["authorizer" ].get ("integrationLatency" , 0 )
1166- )
1167- finish_time_ns = max (
1168- start_time_ns , (request_time_epoch_ms + integration_latency ) * 1e6
1169- )
1169+ finish_time_ns = (
1170+ request_time_epoch_ms
1171+ + (
1172+ int (
1173+ event ["requestContext" ]["authorizer" ].get (
1174+ "integrationLatency" , 0
1175+ )
1176+ )
1177+ )
1178+ ) * 1e6
11701179 upstream_authorizer_span = insert_upstream_authorizer_span (
11711180 args , tags , start_time_ns , finish_time_ns
11721181 )
@@ -1629,9 +1638,9 @@ def create_function_execution_span(
16291638 trace_context_source ,
16301639 merge_xray_traces ,
16311640 trigger_tags ,
1632- durable_function_tags = None ,
16331641 parent_span = None ,
16341642 span_pointers = None ,
1643+ durable_function_tags = None ,
16351644):
16361645 tags = None
16371646 if context :
@@ -1640,7 +1649,6 @@ def create_function_execution_span(
16401649 function_arn = ":" .join (tk [0 :7 ]) if len (tk ) > 7 else function_arn
16411650 function_version = tk [7 ] if len (tk ) > 7 else "$LATEST"
16421651 tags = {
1643- "span.kind" : "server" ,
16441652 "cold_start" : str (is_cold_start ).lower (),
16451653 "function_arn" : function_arn ,
16461654 "function_version" : function_version ,
0 commit comments