Skip to content

Commit c149e81

Browse files
lym953claude
andcommitted
feat: emit durable execution log to stdout at invocation start
Emits a structured JSON log to stdout at the start of each durable function invocation, mapping the Lambda request_id to the durable execution name and ID. The Lambda extension layer uses this to correlate request IDs with durable executions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f349c57 commit c149e81

File tree

3 files changed

+67
-1
lines changed

3 files changed

+67
-1
lines changed

datadog_lambda/durable.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22
# under the Apache License Version 2.0.
33
# This product includes software developed at Datadog (https://www.datadoghq.com/).
44
# Copyright 2019 Datadog, Inc.
5+
import json
56
import logging
67
import re
8+
import sys
79

810
logger = logging.getLogger(__name__)
911

12+
# When changing the schema of the durable invocation log, bump this version so
13+
# the extension can handle compatibility between old and new schemas.
14+
DURABLE_INVOCATION_LOG_SCHEMA_VERSION = "1.0.0"
15+
1016

1117
def _parse_durable_execution_arn(arn):
1218
"""
@@ -47,3 +53,18 @@ def extract_durable_function_tags(event):
4753
"durable_function_execution_name": execution_name,
4854
"durable_function_execution_id": execution_id,
4955
}
56+
57+
58+
def emit_durable_execution_log(request_id, execution_name, execution_id):
59+
"""
60+
Emits a structured JSON log to stdout mapping the Lambda request_id to the
61+
durable execution name and ID. This is consumed by the Lambda extension layer
62+
to correlate request IDs with durable executions.
63+
"""
64+
log = {
65+
"request_id": request_id,
66+
"durable_execution_name": execution_name,
67+
"durable_execution_id": execution_id,
68+
"schema_version": DURABLE_INVOCATION_LOG_SCHEMA_VERSION,
69+
}
70+
print(json.dumps(log), file=sys.stdout, flush=True)

datadog_lambda/wrapper.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
tracer,
4343
propagator,
4444
)
45-
from datadog_lambda.durable import extract_durable_function_tags
45+
from datadog_lambda.durable import extract_durable_function_tags, emit_durable_execution_log
4646
from datadog_lambda.trigger import (
4747
extract_trigger_tags,
4848
extract_http_status_code_tag,
@@ -245,6 +245,12 @@ def _before(self, event, context):
245245

246246
self.trigger_tags = extract_trigger_tags(event, context)
247247
self.durable_function_tags = extract_durable_function_tags(event)
248+
if self.durable_function_tags:
249+
emit_durable_execution_log(
250+
context.aws_request_id,
251+
self.durable_function_tags["durable_function_execution_name"],
252+
self.durable_function_tags["durable_function_execution_id"],
253+
)
248254
# Extract Datadog trace context and source from incoming requests
249255
dd_context, trace_context_source, event_source = extract_dd_trace_context(
250256
event,

tests/test_durable.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
# under the Apache License Version 2.0.
33
# This product includes software developed at Datadog (https://www.datadoghq.com/).
44
# Copyright 2019 Datadog, Inc.
5+
import io
6+
import json
7+
import sys
58
import unittest
69

710
from datadog_lambda.durable import (
811
_parse_durable_execution_arn,
912
extract_durable_function_tags,
13+
emit_durable_execution_log,
14+
DURABLE_INVOCATION_LOG_SCHEMA_VERSION,
1015
)
1116

1217

@@ -89,3 +94,37 @@ def test_returns_empty_dict_when_durable_execution_arn_cannot_be_parsed(self):
8994
def test_returns_empty_dict_when_event_is_empty(self):
9095
result = extract_durable_function_tags({})
9196
self.assertEqual(result, {})
97+
98+
99+
class TestEmitDurableExecutionLog(unittest.TestCase):
100+
def _capture_stdout(self, fn):
101+
captured = io.StringIO()
102+
original = sys.stdout
103+
sys.stdout = captured
104+
try:
105+
fn()
106+
finally:
107+
sys.stdout = original
108+
return captured.getvalue()
109+
110+
def test_emits_json_with_all_fields(self):
111+
output = self._capture_stdout(
112+
lambda: emit_durable_execution_log(
113+
"req-123", "my-execution", "exec-id-456"
114+
)
115+
)
116+
data = json.loads(output.strip())
117+
self.assertEqual(data["request_id"], "req-123")
118+
self.assertEqual(data["durable_execution_name"], "my-execution")
119+
self.assertEqual(data["durable_execution_id"], "exec-id-456")
120+
self.assertEqual(data["schema_version"], DURABLE_INVOCATION_LOG_SCHEMA_VERSION)
121+
122+
def test_emits_single_json_line(self):
123+
output = self._capture_stdout(
124+
lambda: emit_durable_execution_log("req-1", "name", "id")
125+
)
126+
lines = [l for l in output.splitlines() if l.strip()]
127+
self.assertEqual(len(lines), 1)
128+
129+
def test_schema_version_is_correct(self):
130+
self.assertEqual(DURABLE_INVOCATION_LOG_SCHEMA_VERSION, "1.0.0")

0 commit comments

Comments
 (0)