Skip to content

Commit ff42108

Browse files
committed
fix: detect sqs carriers per record in eventbridge batches
1 parent a45866f commit ff42108

2 files changed

Lines changed: 90 additions & 28 deletions

File tree

datadog_lambda/tracing.py

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,7 @@ def extract_context_from_sqs_or_sns_event_or_context(
290290

291291
# EventBridge => SQS
292292
try:
293-
context, is_eventbridge_sqs = _extract_context_from_eventbridge_sqs_event(
294-
event
295-
)
293+
context, is_eventbridge_sqs = _extract_context_from_eventbridge_sqs_event(event)
296294
if is_eventbridge_sqs:
297295
if _is_context_complete(context):
298296
return context
@@ -400,37 +398,25 @@ def _extract_context_from_eventbridge_sqs_event(event):
400398
return None, False
401399

402400
first_record = records[0]
403-
body_str = first_record.get("body")
404-
body = json.loads(body_str)
405-
if not isinstance(body, dict):
406-
return None, False
407-
408-
detail = body.get("detail")
409-
if not (
410-
isinstance(detail, dict)
411-
and body.get("detail-type")
412-
and body.get("source")
413-
):
401+
dd_context, is_eventbridge_sqs = _extract_eventbridge_sqs_record_context(
402+
first_record
403+
)
404+
if not is_eventbridge_sqs:
414405
return None, False
415406

416-
dd_context = detail.get("_datadog")
417-
418407
# The event has been confirmed as EventBridge -> SQS. Set a consume
419408
# checkpoint for every record in the batch. The message is consumed from
420409
# the SQS queue, so it follows SQS conventions (type:sqs, topic:queue ARN).
421410
if config.data_streams_enabled:
422-
_dsm_set_checkpoint(dd_context, "sqs", first_record.get("eventSourceARN", ""))
423411
for record in records:
424-
if record is first_record:
425-
continue
426412
try:
427-
record_body = json.loads(record.get("body"))
428-
record_detail = record_body.get("detail")
429-
record_context = (
430-
record_detail.get("_datadog")
431-
if isinstance(record_detail, dict)
432-
else None
413+
record_context, is_eventbridge_record = (
414+
_extract_eventbridge_sqs_record_context(record)
433415
)
416+
if not is_eventbridge_record:
417+
record_context = _extract_sqs_record_message_attribute_context(
418+
record
419+
)
434420
_dsm_set_checkpoint(
435421
record_context, "sqs", record.get("eventSourceARN", "")
436422
)
@@ -450,6 +436,46 @@ def _extract_context_from_eventbridge_sqs_event(event):
450436
return propagator.extract(dd_context), True
451437

452438

439+
def _extract_eventbridge_sqs_record_context(record):
440+
body_str = record.get("body")
441+
body = json.loads(body_str)
442+
if not isinstance(body, dict):
443+
return None, False
444+
445+
detail = body.get("detail")
446+
if not (
447+
isinstance(detail, dict) and body.get("detail-type") and body.get("source")
448+
):
449+
return None, False
450+
451+
return detail.get("_datadog"), True
452+
453+
454+
def _extract_sqs_record_message_attribute_context(record):
455+
msg_attributes = record.get("messageAttributes") or {}
456+
dd_payload = msg_attributes.get("_datadog")
457+
if not dd_payload:
458+
return None
459+
460+
dd_json_data = None
461+
dd_json_data_type = dd_payload.get("Type") or dd_payload.get("dataType")
462+
if dd_json_data_type == "Binary":
463+
import base64
464+
465+
dd_json_data = dd_payload.get("binaryValue") or dd_payload.get("Value")
466+
if dd_json_data:
467+
dd_json_data = base64.b64decode(dd_json_data)
468+
elif dd_json_data_type == "String":
469+
dd_json_data = dd_payload.get("stringValue") or dd_payload.get("Value")
470+
else:
471+
logger.debug(
472+
"Datadog Lambda Python only supports extracting trace"
473+
"context from String or Binary SQS/SNS message attributes"
474+
)
475+
476+
return json.loads(dd_json_data) if dd_json_data else None
477+
478+
453479
def extract_context_from_eventbridge_event(event, lambda_context):
454480
"""
455481
Extract datadog trace context from an EventBridge message's Details.

tests/test_tracing.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3703,9 +3703,7 @@ def _eventbridge_sqs_record(queue_arn, pathway_ctx, include_trace_headers=True):
37033703
body = {
37043704
"detail-type": "MyDetailType",
37053705
"source": "my.event.source",
3706-
"detail": {
3707-
"_datadog": dd_context
3708-
},
3706+
"detail": {"_datadog": dd_context},
37093707
}
37103708
return {
37113709
"eventSourceARN": queue_arn,
@@ -3751,6 +3749,44 @@ def test_eventbridge_sqs_checkpoints_all_records(self):
37513749
self.assertEqual((second_args[0], second_args[1]), ("sqs", arn2))
37523750
self.assertEqual(second_args[2]("dd-pathway-ctx-base64"), "ctx-2")
37533751

3752+
def test_eventbridge_sqs_mixed_batch_uses_per_record_carriers(self):
3753+
arn1 = "arn:aws:sqs:us-east-1:123456789012:eb-queue"
3754+
arn2 = "arn:aws:sqs:us-east-1:123456789012:direct-queue"
3755+
second_dd_data = {
3756+
"x-datadog-trace-id": "12345",
3757+
"x-datadog-parent-id": "67890",
3758+
"x-datadog-sampling-priority": "1",
3759+
"dd-pathway-ctx-base64": "sqs-ctx",
3760+
}
3761+
event = {
3762+
"Records": [
3763+
self._eventbridge_sqs_record(arn1, "eb-ctx"),
3764+
{
3765+
"eventSourceARN": arn2,
3766+
"eventSource": "aws:sqs",
3767+
"body": json.dumps({"message": "direct sqs payload"}),
3768+
"messageAttributes": {
3769+
"_datadog": {
3770+
"dataType": "String",
3771+
"stringValue": json.dumps(second_dd_data),
3772+
}
3773+
},
3774+
},
3775+
]
3776+
}
3777+
3778+
extract_context_from_sqs_or_sns_event_or_context(
3779+
event, self.lambda_context, parse_event_source(event)
3780+
)
3781+
3782+
self.assertEqual(self.mock_checkpoint.call_count, 2)
3783+
first_args, _ = self.mock_checkpoint.call_args_list[0]
3784+
second_args, _ = self.mock_checkpoint.call_args_list[1]
3785+
self.assertEqual((first_args[0], first_args[1]), ("sqs", arn1))
3786+
self.assertEqual(first_args[2]("dd-pathway-ctx-base64"), "eb-ctx")
3787+
self.assertEqual((second_args[0], second_args[1]), ("sqs", arn2))
3788+
self.assertEqual(second_args[2]("dd-pathway-ctx-base64"), "sqs-ctx")
3789+
37543790
@patch(
37553791
"datadog_lambda.tracing.extract_context_from_lambda_context",
37563792
return_value=Context(trace_id=111, span_id=222, sampling_priority=1),

0 commit comments

Comments
 (0)