Skip to content

Commit 50f8f6f

Browse files
[SVLS-8538] add durable function tags (#730)
* [SVLS-8538] add durable function tags * format * add a tiny comment to re-trigger the pipeline --------- Co-authored-by: Joey Zhao <5253430+joeyzhao2018@users.noreply.github.com>
1 parent 7dc7ad7 commit 50f8f6f

3 files changed

Lines changed: 145 additions & 0 deletions

File tree

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { parseDurableExecutionArn, extractDurableFunctionContext } from "./durable-function-context";
2+
3+
describe("durable-function-context", () => {
4+
describe("parseDurableExecutionArn", () => {
5+
it("returns execution name and ID for a valid ARN", () => {
6+
const arn =
7+
"arn:aws:lambda:us-east-1:123456789012:function:my-func:$LATEST/durable-execution/order-123/550e8400-e29b-41d4-a716-446655440001";
8+
const result = parseDurableExecutionArn(arn);
9+
10+
expect(result).toEqual({
11+
executionName: "order-123",
12+
executionId: "550e8400-e29b-41d4-a716-446655440001",
13+
});
14+
});
15+
16+
it("returns undefined for ARN without durable-execution marker", () => {
17+
const arn = "arn:aws:lambda:us-east-1:123456789012:function:my-func:$LATEST";
18+
const result = parseDurableExecutionArn(arn);
19+
20+
expect(result).toBeUndefined();
21+
});
22+
23+
it("returns undefined for malformed ARN with only execution name", () => {
24+
const arn = "arn:aws:lambda:us-east-1:123456789012:function:my-func:$LATEST/durable-execution/order-123";
25+
const result = parseDurableExecutionArn(arn);
26+
27+
expect(result).toBeUndefined();
28+
});
29+
30+
it("returns undefined for malformed ARN with empty execution name", () => {
31+
const arn =
32+
"arn:aws:lambda:us-east-1:123456789012:function:my-func:$LATEST/durable-execution//550e8400-e29b-41d4-a716-446655440002";
33+
const result = parseDurableExecutionArn(arn);
34+
35+
expect(result).toBeUndefined();
36+
});
37+
38+
it("returns undefined for malformed ARN with empty execution ID", () => {
39+
const arn = "arn:aws:lambda:us-east-1:123456789012:function:my-func:$LATEST/durable-execution/order-123/";
40+
const result = parseDurableExecutionArn(arn);
41+
42+
expect(result).toBeUndefined();
43+
});
44+
});
45+
46+
describe("extractDurableFunctionContext", () => {
47+
it("extracts context from event.DurableExecutionArn", () => {
48+
const event = {
49+
DurableExecutionArn:
50+
"arn:aws:lambda:us-east-1:123456789012:function:my-func:1/durable-execution/my-execution/550e8400-e29b-41d4-a716-446655440004",
51+
CheckpointToken: "some-token",
52+
InitialExecutionState: {
53+
Operations: [],
54+
},
55+
};
56+
const result = extractDurableFunctionContext(event);
57+
58+
expect(result).toEqual({
59+
durable_function_execution_name: "my-execution",
60+
durable_function_execution_id: "550e8400-e29b-41d4-a716-446655440004",
61+
});
62+
});
63+
64+
it("returns undefined for regular Lambda event without DurableExecutionArn", () => {
65+
const event = {
66+
body: '{"key": "value"}',
67+
headers: {
68+
"Content-Type": "application/json",
69+
},
70+
};
71+
const result = extractDurableFunctionContext(event);
72+
73+
expect(result).toBeUndefined();
74+
});
75+
76+
it("returns undefined when event is null", () => {
77+
const result = extractDurableFunctionContext(null);
78+
79+
expect(result).toBeUndefined();
80+
});
81+
82+
it("returns undefined when event is undefined", () => {
83+
const result = extractDurableFunctionContext(undefined);
84+
85+
expect(result).toBeUndefined();
86+
});
87+
88+
it("returns undefined when DurableExecutionArn cannot be parsed", () => {
89+
const event = {
90+
DurableExecutionArn: "invalid-arn-without-durable-execution-marker",
91+
};
92+
const result = extractDurableFunctionContext(event);
93+
94+
expect(result).toBeUndefined();
95+
});
96+
});
97+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { logDebug } from "../utils";
2+
3+
export interface DurableFunctionContext {
4+
durable_function_execution_name: string;
5+
durable_function_execution_id: string;
6+
}
7+
8+
export function extractDurableFunctionContext(event: any): DurableFunctionContext | undefined {
9+
const durableExecutionArn = event?.DurableExecutionArn;
10+
11+
if (typeof durableExecutionArn !== "string") {
12+
return undefined;
13+
}
14+
15+
const parsed = parseDurableExecutionArn(durableExecutionArn);
16+
if (!parsed) {
17+
logDebug("Failed to parse DurableExecutionArn", { arn: durableExecutionArn });
18+
return undefined;
19+
}
20+
21+
return {
22+
durable_function_execution_name: parsed.executionName,
23+
durable_function_execution_id: parsed.executionId,
24+
};
25+
}
26+
27+
/**
28+
* Parses a DurableExecutionArn to extract execution name and ID.
29+
* ARN format: arn:aws:lambda:{region}:{account}:function:{func}:{version}/durable-execution/{name}/{id}
30+
*/
31+
export function parseDurableExecutionArn(arn: string): { executionName: string; executionId: string } | undefined {
32+
// Match only the trailing durable execution segment.
33+
const match = arn.match(/\/durable-execution\/([^/]+)\/([^/]+)$/);
34+
if (!match) return undefined;
35+
const [, executionName, executionId] = match;
36+
return { executionName, executionId };
37+
}

src/trace/listener.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { SpanWrapper } from "./span-wrapper";
1515
import { getTraceTree, clearTraceTree } from "../runtime/index";
1616
import { TraceContext, TraceContextService, TraceSource } from "./trace-context-service";
1717
import { StepFunctionContext, StepFunctionContextService } from "./step-function-service";
18+
import { DurableFunctionContext, extractDurableFunctionContext } from "./durable-function-context";
1819
import { XrayService } from "./xray-service";
1920
import { AUTHORIZING_REQUEST_ID_HEADER } from "./context/extractors/http";
2021
import { getSpanPointerAttributes, SpanPointerAttributes } from "../utils/span-pointers";
@@ -85,6 +86,7 @@ export class TraceListener {
8586
private contextService: TraceContextService;
8687
private context?: Context;
8788
private stepFunctionContext?: StepFunctionContext;
89+
private durableFunctionContext?: DurableFunctionContext;
8890
private tracerWrapper: TracerWrapper;
8991
private inferrer: SpanInferrer;
9092
private inferredSpan?: SpanWrapper;
@@ -146,6 +148,7 @@ export class TraceListener {
146148
const eventSource = parseEventSource(event);
147149
this.triggerTags = extractTriggerTags(event, context, eventSource);
148150
this.stepFunctionContext = StepFunctionContextService.instance().context;
151+
this.durableFunctionContext = extractDurableFunctionContext(event);
149152

150153
if (this.config.addSpanPointers) {
151154
this.spanPointerAttributesList = getSpanPointerAttributes(eventSource, event);
@@ -288,6 +291,7 @@ export class TraceListener {
288291

289292
// Reset singletons and trace context
290293
this.stepFunctionContext = undefined;
294+
this.durableFunctionContext = undefined;
291295
StepFunctionContextService.reset();
292296
this.contextService.reset();
293297
}
@@ -328,6 +332,13 @@ export class TraceListener {
328332
...this.stepFunctionContext,
329333
};
330334
}
335+
if (this.durableFunctionContext) {
336+
logDebug("Applying durable function context to the aws.lambda span");
337+
options.tags = {
338+
...options.tags,
339+
...this.durableFunctionContext,
340+
};
341+
}
331342
if (this.lambdaSpanParentContext) {
332343
options.childOf = this.lambdaSpanParentContext;
333344
}

0 commit comments

Comments
 (0)