From ef4c8c45fc28001983ae77fcf09c4a4415657f1d Mon Sep 17 00:00:00 2001 From: Yiming Luo <10097700+lym953@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:39:35 -0400 Subject: [PATCH 1/4] feat: [SVLS-8583] add execution_status tag to aws.lambda span for durable functions Co-Authored-By: Claude Sonnet 4.6 --- src/trace/durable-function-context.spec.ts | 39 +++++++++++++++++++++- src/trace/durable-function-context.ts | 19 +++++++++++ src/trace/listener.ts | 6 +++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/trace/durable-function-context.spec.ts b/src/trace/durable-function-context.spec.ts index 689caa5d..e9915b8b 100644 --- a/src/trace/durable-function-context.spec.ts +++ b/src/trace/durable-function-context.spec.ts @@ -1,4 +1,4 @@ -import { parseDurableExecutionArn, extractDurableFunctionContext } from "./durable-function-context"; +import { parseDurableExecutionArn, extractDurableFunctionContext, extractDurableExecutionStatus } from "./durable-function-context"; describe("durable-function-context", () => { describe("parseDurableExecutionArn", () => { @@ -132,4 +132,41 @@ describe("durable-function-context", () => { expect(result).toBeUndefined(); }); }); + + describe("extractDurableExecutionStatus", () => { + const durableEvent = { + DurableExecutionArn: + "arn:aws:lambda:us-east-1:123456789012:function:my-func:1/durable-execution/my-execution/550e8400-e29b-41d4-a716-446655440004", + }; + + it.each(["SUCCEEDED", "FAILED", "STOPPED", "TIMED_OUT"])("returns %s when result.Status is %s", (status) => { + const result = extractDurableExecutionStatus(durableEvent, { Status: status }); + expect(result).toBe(status); + }); + + it("returns undefined when result.Status is not a valid status", () => { + const result = extractDurableExecutionStatus(durableEvent, { Status: "UNKNOWN" }); + expect(result).toBeUndefined(); + }); + + it("returns undefined when result has no Status field", () => { + const result = extractDurableExecutionStatus(durableEvent, {}); + expect(result).toBeUndefined(); + }); + + it("returns undefined when result is null", () => { + const result = extractDurableExecutionStatus(durableEvent, null); + expect(result).toBeUndefined(); + }); + + it("returns undefined when event has no DurableExecutionArn", () => { + const result = extractDurableExecutionStatus({ body: "{}" }, { Status: "SUCCEEDED" }); + expect(result).toBeUndefined(); + }); + + it("returns undefined when event is null", () => { + const result = extractDurableExecutionStatus(null, { Status: "SUCCEEDED" }); + expect(result).toBeUndefined(); + }); + }); }); diff --git a/src/trace/durable-function-context.ts b/src/trace/durable-function-context.ts index 29d4c53c..d3d03a7d 100644 --- a/src/trace/durable-function-context.ts +++ b/src/trace/durable-function-context.ts @@ -6,6 +6,8 @@ export interface DurableFunctionContext { "aws_lambda.durable_function.first_invocation"?: string; } +const VALID_DURABLE_EXECUTION_STATUSES = new Set(["SUCCEEDED", "FAILED", "STOPPED", "TIMED_OUT"]); + export function extractDurableFunctionContext(event: any): DurableFunctionContext | undefined { const durableExecutionArn = event?.DurableExecutionArn; @@ -33,6 +35,23 @@ export function extractDurableFunctionContext(event: any): DurableFunctionContex return context; } +/** + * Extracts the durable function execution status from the handler result. + * Only applies when the event contains a DurableExecutionArn. + */ +export function extractDurableExecutionStatus(event: any, result: any): string | undefined { + if (!event?.DurableExecutionArn) { + return undefined; + } + + const status = result?.Status; + if (typeof status !== "string" || !VALID_DURABLE_EXECUTION_STATUSES.has(status)) { + return undefined; + } + + return status; +} + /** * Parses a DurableExecutionArn to extract execution name and ID. * ARN format: arn:aws:lambda:{region}:{account}:function:{func}:{version}/durable-execution/{name}/{id} diff --git a/src/trace/listener.ts b/src/trace/listener.ts index 217ba7a8..4047233b 100644 --- a/src/trace/listener.ts +++ b/src/trace/listener.ts @@ -20,7 +20,7 @@ import { SpanWrapper } from "./span-wrapper"; import { getTraceTree, clearTraceTree } from "../runtime/index"; import { TraceContext, TraceContextService, TraceSource } from "./trace-context-service"; import { StepFunctionContext, StepFunctionContextService } from "./step-function-service"; -import { DurableFunctionContext, extractDurableFunctionContext } from "./durable-function-context"; +import { DurableFunctionContext, extractDurableFunctionContext, extractDurableExecutionStatus } from "./durable-function-context"; import { XrayService } from "./xray-service"; import { AUTHORIZING_REQUEST_ID_HEADER } from "./context/extractors/http"; import { getSpanPointerAttributes, SpanPointerAttributes } from "../utils/span-pointers"; @@ -233,6 +233,10 @@ export class TraceListener { this.tracerWrapper.currentSpan.setTag(key, value); } } + const executionStatus = extractDurableExecutionStatus(event, result); + if (executionStatus !== undefined) { + this.tracerWrapper.currentSpan.setTag("aws_lambda.durable_function.execution_status", executionStatus); + } } let rootSpan = this.inferredSpan; From 16b8bf67c982c37ca8eb99047fe5d84cf7cbcd47 Mon Sep 17 00:00:00 2001 From: Yiming Luo <10097700+lym953@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:08:37 -0400 Subject: [PATCH 2/4] chore: fix formatting Co-Authored-By: Claude Sonnet 4.6 --- src/trace/durable-function-context.spec.ts | 6 +++++- src/trace/listener.ts | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/trace/durable-function-context.spec.ts b/src/trace/durable-function-context.spec.ts index e9915b8b..38803d21 100644 --- a/src/trace/durable-function-context.spec.ts +++ b/src/trace/durable-function-context.spec.ts @@ -1,4 +1,8 @@ -import { parseDurableExecutionArn, extractDurableFunctionContext, extractDurableExecutionStatus } from "./durable-function-context"; +import { + parseDurableExecutionArn, + extractDurableFunctionContext, + extractDurableExecutionStatus, +} from "./durable-function-context"; describe("durable-function-context", () => { describe("parseDurableExecutionArn", () => { diff --git a/src/trace/listener.ts b/src/trace/listener.ts index 4047233b..b3f50810 100644 --- a/src/trace/listener.ts +++ b/src/trace/listener.ts @@ -20,7 +20,11 @@ import { SpanWrapper } from "./span-wrapper"; import { getTraceTree, clearTraceTree } from "../runtime/index"; import { TraceContext, TraceContextService, TraceSource } from "./trace-context-service"; import { StepFunctionContext, StepFunctionContextService } from "./step-function-service"; -import { DurableFunctionContext, extractDurableFunctionContext, extractDurableExecutionStatus } from "./durable-function-context"; +import { + DurableFunctionContext, + extractDurableFunctionContext, + extractDurableExecutionStatus, +} from "./durable-function-context"; import { XrayService } from "./xray-service"; import { AUTHORIZING_REQUEST_ID_HEADER } from "./context/extractors/http"; import { getSpanPointerAttributes, SpanPointerAttributes } from "../utils/span-pointers"; From 7d8ee09919ef76a51295502faab110c9debe6374 Mon Sep 17 00:00:00 2001 From: Yiming Luo <10097700+lym953@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:04:02 -0400 Subject: [PATCH 3/4] fix: [SVLS-8583] correct valid execution statuses to match InvocationStatus enum Co-Authored-By: Claude Sonnet 4.6 --- src/trace/durable-function-context.spec.ts | 2 +- src/trace/durable-function-context.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/trace/durable-function-context.spec.ts b/src/trace/durable-function-context.spec.ts index 38803d21..81939e99 100644 --- a/src/trace/durable-function-context.spec.ts +++ b/src/trace/durable-function-context.spec.ts @@ -143,7 +143,7 @@ describe("durable-function-context", () => { "arn:aws:lambda:us-east-1:123456789012:function:my-func:1/durable-execution/my-execution/550e8400-e29b-41d4-a716-446655440004", }; - it.each(["SUCCEEDED", "FAILED", "STOPPED", "TIMED_OUT"])("returns %s when result.Status is %s", (status) => { + it.each(["SUCCEEDED", "FAILED", "PENDING"])("returns %s when result.Status is %s", (status) => { const result = extractDurableExecutionStatus(durableEvent, { Status: status }); expect(result).toBe(status); }); diff --git a/src/trace/durable-function-context.ts b/src/trace/durable-function-context.ts index d3d03a7d..6b4d543e 100644 --- a/src/trace/durable-function-context.ts +++ b/src/trace/durable-function-context.ts @@ -6,7 +6,7 @@ export interface DurableFunctionContext { "aws_lambda.durable_function.first_invocation"?: string; } -const VALID_DURABLE_EXECUTION_STATUSES = new Set(["SUCCEEDED", "FAILED", "STOPPED", "TIMED_OUT"]); +const VALID_DURABLE_EXECUTION_STATUSES = new Set(["SUCCEEDED", "FAILED", "PENDING"]); export function extractDurableFunctionContext(event: any): DurableFunctionContext | undefined { const durableExecutionArn = event?.DurableExecutionArn; From 9a70b05740fde6152ee20b00744238e5ec1dbe39 Mon Sep 17 00:00:00 2001 From: Yiming Luo <10097700+lym953@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:14:02 -0400 Subject: [PATCH 4/4] fix: [SVLS-8583] address code review comments - Use typeof string guard in extractDurableExecutionStatus for consistency - Move execution_status tagging outside durableFunctionContext block so it applies even when ARN parsing fails - Add listener.spec.ts tests for execution_status tag being set and not set Co-Authored-By: Claude Sonnet 4.6 --- src/trace/durable-function-context.ts | 2 +- src/trace/listener.spec.ts | 40 +++++++++++++++++++++++++++ src/trace/listener.ts | 8 +++--- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/trace/durable-function-context.ts b/src/trace/durable-function-context.ts index 6b4d543e..19630c81 100644 --- a/src/trace/durable-function-context.ts +++ b/src/trace/durable-function-context.ts @@ -40,7 +40,7 @@ export function extractDurableFunctionContext(event: any): DurableFunctionContex * Only applies when the event contains a DurableExecutionArn. */ export function extractDurableExecutionStatus(event: any, result: any): string | undefined { - if (!event?.DurableExecutionArn) { + if (typeof event?.DurableExecutionArn !== "string") { return undefined; } diff --git a/src/trace/listener.spec.ts b/src/trace/listener.spec.ts index f70865fa..246a907f 100644 --- a/src/trace/listener.spec.ts +++ b/src/trace/listener.spec.ts @@ -568,4 +568,44 @@ describe("TraceListener", () => { currentSpanSpy.mockRestore(); } }); + + it("sets execution_status tag on the aws.lambda span when result.Status is valid", async () => { + const mockSetTag = jest.fn(); + const mockSpan = { setTag: mockSetTag }; + const currentSpanSpy = jest.spyOn(TracerWrapper.prototype, "currentSpan", "get").mockReturnValue(mockSpan); + + try { + const listener = new TraceListener(defaultConfig); + const durableEvent = { + DurableExecutionArn: + "arn:aws:lambda:us-east-1:123456789012:function:my-func:1/durable-execution/my-execution/550e8400-e29b-41d4-a716-446655440004", + }; + await listener.onStartInvocation(durableEvent, context as any); + listener.onEndingInvocation(durableEvent, { Status: "SUCCEEDED" }, false); + + expect(mockSetTag).toHaveBeenCalledWith("aws_lambda.durable_function.execution_status", "SUCCEEDED"); + } finally { + currentSpanSpy.mockRestore(); + } + }); + + it("does not set execution_status tag when result.Status is invalid", async () => { + const mockSetTag = jest.fn(); + const mockSpan = { setTag: mockSetTag }; + const currentSpanSpy = jest.spyOn(TracerWrapper.prototype, "currentSpan", "get").mockReturnValue(mockSpan); + + try { + const listener = new TraceListener(defaultConfig); + const durableEvent = { + DurableExecutionArn: + "arn:aws:lambda:us-east-1:123456789012:function:my-func:1/durable-execution/my-execution/550e8400-e29b-41d4-a716-446655440004", + }; + await listener.onStartInvocation(durableEvent, context as any); + listener.onEndingInvocation(durableEvent, { Status: "UNKNOWN" }, false); + + expect(mockSetTag).not.toHaveBeenCalledWith("aws_lambda.durable_function.execution_status", expect.anything()); + } finally { + currentSpanSpy.mockRestore(); + } + }); }); diff --git a/src/trace/listener.ts b/src/trace/listener.ts index b3f50810..f068c535 100644 --- a/src/trace/listener.ts +++ b/src/trace/listener.ts @@ -237,10 +237,10 @@ export class TraceListener { this.tracerWrapper.currentSpan.setTag(key, value); } } - const executionStatus = extractDurableExecutionStatus(event, result); - if (executionStatus !== undefined) { - this.tracerWrapper.currentSpan.setTag("aws_lambda.durable_function.execution_status", executionStatus); - } + } + const executionStatus = extractDurableExecutionStatus(event, result); + if (executionStatus !== undefined) { + this.tracerWrapper.currentSpan.setTag("aws_lambda.durable_function.execution_status", executionStatus); } let rootSpan = this.inferredSpan;