Skip to content

Commit cc7d7c7

Browse files
authored
[SVLS-518] fix: fix broken SFN -> lambda link (#779)
datadog-lambda-js ignores upstream trace context propagated via Execution.Input._datadog when a Lambda is invoked from a Step Function, breaking trace continuity for the pattern: upstream Lambda → StartExecution → Step Function → downstream Lambda. This commit extracts datadog headers from Execution.Input._datadog when it is present, linking a lambda span to its upstream step function.
1 parent 45f99fa commit cc7d7c7

3 files changed

Lines changed: 127 additions & 8 deletions

File tree

src/trace/context/extractors/step-function.spec.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,33 @@ describe("StepFunctionEventTraceExtractor", () => {
4545
Name: "abhinav-activity-state-machine",
4646
},
4747
};
48+
49+
const legacyLambdaRootPayload = {
50+
Execution: {
51+
Id: "arn:aws:states:sa-east-1:425362996713:execution:abhinav-activity-state-machine:72a7ca3e-901c-41bb-b5a3-5f279b92a316",
52+
Name: "72a7ca3e-901c-41bb-b5a3-5f279b92a316",
53+
RoleArn:
54+
"arn:aws:iam::425362996713:role/service-role/StepFunctions-abhinav-activity-state-machine-role-22jpbgl6j",
55+
StartTime: "2024-12-04T19:38:04.069Z",
56+
RedriveCount: 1,
57+
Input: {
58+
MyInput: "MyValue",
59+
_datadog: {
60+
"x-datadog-trace-id": "10593586103637578129",
61+
"x-datadog-tags": "_dd.p.dm=-0,_dd.p.tid=6734e7c300000000",
62+
},
63+
},
64+
},
65+
State: {
66+
Name: "Lambda Invoke",
67+
EnteredTime: "2024-12-04T19:38:04.118Z",
68+
RetryCount: 0,
69+
},
70+
StateMachine: {
71+
Id: "arn:aws:states:sa-east-1:425362996713:stateMachine:abhinav-activity-state-machine",
72+
Name: "abhinav-activity-state-machine",
73+
},
74+
};
4875
it("extracts trace context with valid payload", () => {
4976
// Mimick TraceContextService.extract initialization
5077
StepFunctionContextService.instance(payload);
@@ -61,7 +88,21 @@ describe("StepFunctionEventTraceExtractor", () => {
6188
expect(traceContext?.source).toBe("event");
6289
});
6390

64-
// https://github.com/DataDog/logs-backend/blob/c17618cb552fc369ca40282bae0a65803f82f694/domains/serverless/apps/logs-to-traces-reducer/src/test/resources/test-json-files/stepfunctions/RedriveTest/snapshots/RedriveLambdaSuccessTraceMerging.json#L46
91+
it("extracts trace context with valid legacy lambda root payload", () => {
92+
// Mimick TraceContextService.extract initialization
93+
StepFunctionContextService.instance(legacyLambdaRootPayload);
94+
95+
const extractor = new StepFunctionEventTraceExtractor();
96+
97+
// Payload is sent again for safety in case the instance wasn't previously initialized
98+
const traceContext = extractor.extract(legacyLambdaRootPayload);
99+
expect(traceContext).not.toBeNull();
100+
101+
expect(traceContext?.toTraceId()).toBe("10593586103637578129");
102+
expect(traceContext?.sampleMode()).toBe("1");
103+
expect(traceContext?.source).toBe("event");
104+
});
105+
65106
it("extracts trace context with valid redriven payload", () => {
66107
// Mimick TraceContextService.extract initialization
67108
StepFunctionContextService.instance(redrivePayload);

src/trace/step-function-service.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,33 @@ describe("StepFunctionContextService", () => {
2222
Name: "my-state-machine",
2323
},
2424
} as const;
25+
const legacyLambdaRootStepFunctionEvent = {
26+
_datadog: {
27+
Execution: {
28+
Id: "arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:85a9933e-9e11-83dc-6a61-b92367b6c3be:3f7ef5c7-c8b8-4c88-90a1-d54aa7e7e2bf",
29+
Input: {
30+
MyInput: "MyValue",
31+
_datadog: {
32+
"x-datadog-trace-id": "10593586103637578129",
33+
"x-datadog-tags": "_dd.p.dm=-0,_dd.p.tid=6734e7c300000000",
34+
},
35+
},
36+
Name: "85a9933e-9e11-83dc-6a61-b92367b6c3be",
37+
RoleArn: "arn:aws:iam::425362996713:role/service-role/StepFunctions-logs-to-traces-sequential-role-ccd69c03",
38+
RedriveCount: 0,
39+
StartTime: "2022-12-08T21:08:17.924Z",
40+
},
41+
State: {
42+
Name: "step-one",
43+
EnteredTime: "2022-12-08T21:08:19.224Z",
44+
RetryCount: 2,
45+
},
46+
StateMachine: {
47+
Id: "arn:aws:states:sa-east-1:425362996713:stateMachine:logs-to-traces-sequential",
48+
Name: "my-state-machine",
49+
},
50+
},
51+
} as const;
2552
const lambdaRootStepFunctionEvent = {
2653
_datadog: {
2754
Execution: {
@@ -156,6 +183,23 @@ describe("StepFunctionContextService", () => {
156183
});
157184
});
158185

186+
it("sets context from valid legacy lambda root step function event", () => {
187+
const instance = StepFunctionContextService.instance();
188+
// Force setting event
189+
instance["setContext"](legacyLambdaRootStepFunctionEvent);
190+
expect(instance.context).toEqual({
191+
execution_id:
192+
"arn:aws:states:sa-east-1:425362996713:express:logs-to-traces-sequential:85a9933e-9e11-83dc-6a61-b92367b6c3be:3f7ef5c7-c8b8-4c88-90a1-d54aa7e7e2bf",
193+
redrive_count: "0",
194+
retry_count: "2",
195+
state_entered_time: "2022-12-08T21:08:19.224Z",
196+
state_name: "step-one",
197+
trace_id: "10593586103637578129",
198+
dd_p_tid: "6734e7c300000000",
199+
serverless_version: "legacy-lambda-root",
200+
});
201+
});
202+
159203
it("sets context from valid nested event", () => {
160204
const instance = StepFunctionContextService.instance();
161205
// Force setting event
@@ -211,6 +255,21 @@ describe("StepFunctionContextService", () => {
211255
expect(spanContext?.source).toBe("event");
212256
});
213257

258+
it("returns a SpanContextWrapper when legacy lambda root step function event is valid", () => {
259+
const instance = StepFunctionContextService.instance();
260+
// Force setting event
261+
instance["setContext"](legacyLambdaRootStepFunctionEvent);
262+
263+
const spanContext = instance.spanContext;
264+
265+
expect(spanContext).not.toBeNull();
266+
267+
expect(spanContext?.toTraceId()).toBe("10593586103637578129");
268+
expect(spanContext?.toSpanId()).toBe("7747304477664363642");
269+
expect(spanContext?.sampleMode()).toBe("1");
270+
expect(spanContext?.source).toBe("event");
271+
});
272+
214273
it("returns a SpanContextWrapper when nested event is valid", () => {
215274
const instance = StepFunctionContextService.instance();
216275
// Force setting event

src/trace/step-function-service.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,32 @@ export class StepFunctionContextService {
120120
} as LambdaRootStepFunctionContext;
121121
}
122122
} else {
123-
this.context = {
124-
execution_id,
125-
redrive_count,
126-
retry_count,
127-
state_entered_time,
128-
state_name,
129-
} as LegacyStepFunctionContext;
123+
const datadogContext = event.Execution?.Input?._datadog;
124+
if (
125+
typeof datadogContext === "object" &&
126+
datadogContext !== null &&
127+
typeof datadogContext["x-datadog-trace-id"] === "string" &&
128+
typeof datadogContext["x-datadog-tags"] === "string"
129+
) {
130+
this.context = {
131+
execution_id,
132+
redrive_count,
133+
retry_count,
134+
state_entered_time,
135+
state_name,
136+
trace_id: datadogContext["x-datadog-trace-id"],
137+
dd_p_tid: this.parsePTid(datadogContext["x-datadog-tags"]),
138+
serverless_version: "legacy-lambda-root", // dummy value
139+
} as LambdaRootStepFunctionContext;
140+
} else {
141+
this.context = {
142+
execution_id,
143+
redrive_count,
144+
retry_count,
145+
state_entered_time,
146+
state_name,
147+
} as LegacyStepFunctionContext;
148+
}
130149
}
131150
}
132151

0 commit comments

Comments
 (0)