|
61 | 61 | DD_TRACE_JAVA_TRACE_ID_PADDING = "00000000" |
62 | 62 | HIGHER_64_BITS = "HIGHER_64_BITS" |
63 | 63 | LOWER_64_BITS = "LOWER_64_BITS" |
| 64 | +_TRACE_CHECKPOINT_PREFIX = "_datadog_" |
64 | 65 |
|
65 | 66 |
|
66 | 67 | def _dsm_set_checkpoint(context_json, event_type, arn): |
@@ -546,6 +547,121 @@ def extract_context_from_step_functions(event, lambda_context): |
546 | 547 | return extract_context_from_lambda_context(lambda_context) |
547 | 548 |
|
548 | 549 |
|
| 550 | +def _durable_operations(event): |
| 551 | + if not isinstance(event, dict): |
| 552 | + return [] |
| 553 | + |
| 554 | + operations = event.get("InitialExecutionState", {}).get("Operations") |
| 555 | + if isinstance(operations, list): |
| 556 | + return operations |
| 557 | + if not isinstance(operations, dict): |
| 558 | + return [] |
| 559 | + |
| 560 | + numeric_keys = [] |
| 561 | + other_keys = [] |
| 562 | + for key, value in operations.items(): |
| 563 | + if not isinstance(value, dict): |
| 564 | + continue |
| 565 | + try: |
| 566 | + numeric_keys.append((int(key), value)) |
| 567 | + except (TypeError, ValueError): |
| 568 | + other_keys.append((str(key), value)) |
| 569 | + |
| 570 | + numeric_keys.sort(key=lambda item: item[0]) |
| 571 | + other_keys.sort(key=lambda item: item[0]) |
| 572 | + return [value for _, value in numeric_keys + other_keys] |
| 573 | + |
| 574 | + |
| 575 | +def _extract_context_from_durable_checkpoint(operation): |
| 576 | + if not isinstance(operation, dict): |
| 577 | + return None |
| 578 | + |
| 579 | + step_details = operation.get("StepDetails") |
| 580 | + if not isinstance(step_details, dict): |
| 581 | + return None |
| 582 | + |
| 583 | + result = step_details.get("Result") |
| 584 | + if isinstance(result, str): |
| 585 | + try: |
| 586 | + result = json.loads(result) |
| 587 | + except Exception: |
| 588 | + return None |
| 589 | + |
| 590 | + if not isinstance(result, dict): |
| 591 | + return None |
| 592 | + |
| 593 | + return propagator.extract(result) |
| 594 | + |
| 595 | + |
| 596 | +def _extract_context_from_durable_input_payload(operation): |
| 597 | + if not isinstance(operation, dict): |
| 598 | + return None |
| 599 | + |
| 600 | + execution_details = operation.get("ExecutionDetails") |
| 601 | + if not isinstance(execution_details, dict): |
| 602 | + return None |
| 603 | + |
| 604 | + input_payload = execution_details.get("InputPayload") |
| 605 | + if isinstance(input_payload, str): |
| 606 | + try: |
| 607 | + input_payload = json.loads(input_payload) |
| 608 | + except Exception: |
| 609 | + return None |
| 610 | + |
| 611 | + if not isinstance(input_payload, dict): |
| 612 | + return None |
| 613 | + |
| 614 | + headers = input_payload.get("headers") |
| 615 | + if isinstance(headers, dict): |
| 616 | + return propagator.extract(headers) |
| 617 | + |
| 618 | + dd_data = input_payload.get("_datadog") |
| 619 | + if isinstance(dd_data, dict): |
| 620 | + return propagator.extract(dd_data) |
| 621 | + |
| 622 | + return None |
| 623 | + |
| 624 | + |
| 625 | +def extract_context_from_durable_execution(event): |
| 626 | + if not isinstance(event, dict): |
| 627 | + return None |
| 628 | + if not isinstance(event.get("DurableExecutionArn"), str): |
| 629 | + return None |
| 630 | + |
| 631 | + operations = _durable_operations(event) |
| 632 | + if not operations: |
| 633 | + return None |
| 634 | + |
| 635 | + best_context = None |
| 636 | + best_number = -1 |
| 637 | + for operation in operations: |
| 638 | + if not isinstance(operation, dict): |
| 639 | + continue |
| 640 | + name = operation.get("Name") |
| 641 | + if not isinstance(name, str) or not name.startswith(_TRACE_CHECKPOINT_PREFIX): |
| 642 | + continue |
| 643 | + suffix = name[len(_TRACE_CHECKPOINT_PREFIX) :] |
| 644 | + try: |
| 645 | + number = int(suffix) |
| 646 | + except (TypeError, ValueError): |
| 647 | + continue |
| 648 | + if number < best_number: |
| 649 | + continue |
| 650 | + context = _extract_context_from_durable_checkpoint(operation) |
| 651 | + if _is_context_complete(context): |
| 652 | + best_context = context |
| 653 | + best_number = number |
| 654 | + |
| 655 | + if best_context is not None: |
| 656 | + return best_context |
| 657 | + |
| 658 | + upstream_context = _extract_context_from_durable_input_payload(operations[0]) |
| 659 | + if _is_context_complete(upstream_context): |
| 660 | + return upstream_context |
| 661 | + |
| 662 | + return None |
| 663 | + |
| 664 | + |
549 | 665 | def extract_context_custom_extractor(extractor, event, lambda_context): |
550 | 666 | """ |
551 | 667 | Extract Datadog trace context using a custom trace extractor function |
@@ -633,29 +749,34 @@ def extract_dd_trace_context( |
633 | 749 | global dd_trace_context |
634 | 750 | trace_context_source = None |
635 | 751 | event_source = parse_event_source(event) |
| 752 | + context = None |
636 | 753 |
|
637 | 754 | if extractor is not None: |
638 | 755 | context = extract_context_custom_extractor(extractor, event, lambda_context) |
639 | | - elif isinstance(event, (set, dict)) and "request" in event: |
640 | | - context = extract_context_from_request_header_or_context( |
641 | | - event, lambda_context, event_source |
642 | | - ) |
643 | | - elif isinstance(event, (set, dict)) and "headers" in event: |
644 | | - context = extract_context_from_http_event_or_context( |
645 | | - event, lambda_context, event_source, decode_authorizer_context |
646 | | - ) |
647 | | - elif event_source.equals(EventTypes.SNS) or event_source.equals(EventTypes.SQS): |
648 | | - context = extract_context_from_sqs_or_sns_event_or_context( |
649 | | - event, lambda_context, event_source |
650 | | - ) |
651 | | - elif event_source.equals(EventTypes.EVENTBRIDGE): |
652 | | - context = extract_context_from_eventbridge_event(event, lambda_context) |
653 | | - elif event_source.equals(EventTypes.KINESIS): |
654 | | - context = extract_context_from_kinesis_event(event, lambda_context) |
655 | | - elif event_source.equals(EventTypes.STEPFUNCTIONS): |
656 | | - context = extract_context_from_step_functions(event, lambda_context) |
657 | | - else: |
658 | | - context = extract_context_from_lambda_context(lambda_context) |
| 756 | + elif isinstance(event, (set, dict)) and "DurableExecutionArn" in event: |
| 757 | + context = extract_context_from_durable_execution(event) |
| 758 | + |
| 759 | + if context is None: |
| 760 | + if isinstance(event, (set, dict)) and "request" in event: |
| 761 | + context = extract_context_from_request_header_or_context( |
| 762 | + event, lambda_context, event_source |
| 763 | + ) |
| 764 | + elif isinstance(event, (set, dict)) and "headers" in event: |
| 765 | + context = extract_context_from_http_event_or_context( |
| 766 | + event, lambda_context, event_source, decode_authorizer_context |
| 767 | + ) |
| 768 | + elif event_source.equals(EventTypes.SNS) or event_source.equals(EventTypes.SQS): |
| 769 | + context = extract_context_from_sqs_or_sns_event_or_context( |
| 770 | + event, lambda_context, event_source |
| 771 | + ) |
| 772 | + elif event_source.equals(EventTypes.EVENTBRIDGE): |
| 773 | + context = extract_context_from_eventbridge_event(event, lambda_context) |
| 774 | + elif event_source.equals(EventTypes.KINESIS): |
| 775 | + context = extract_context_from_kinesis_event(event, lambda_context) |
| 776 | + elif event_source.equals(EventTypes.STEPFUNCTIONS): |
| 777 | + context = extract_context_from_step_functions(event, lambda_context) |
| 778 | + else: |
| 779 | + context = extract_context_from_lambda_context(lambda_context) |
659 | 780 |
|
660 | 781 | if _is_context_complete(context): |
661 | 782 | logger.debug("Extracted Datadog trace context from event or context") |
|
0 commit comments