Skip to content

Commit 14dca12

Browse files
authored
feat(sdk): log AWS Lambda backend errors to developer logger (#250)
Add optional logger parameter to storage layer methods (getStepData and checkpoint) to enable logging of AWS Lambda backend errors to the developer's context logger in addition to internal debug logs. Changes: - Updated ExecutionState interface to accept optional Logger parameter - Modified ApiStorage to log errors with requestId to developer logger - Created initialization logger in execution-context using existing createContextLoggerFactory for early error logging - Updated CheckpointHandler to pass logger through to storage layer - Fixed test assertions to expect new logger parameter - Fixed TSDoc lint warning by escaping @ symbol
1 parent aa36da9 commit 14dca12

File tree

9 files changed

+161
-17
lines changed

9 files changed

+161
-17
lines changed

packages/aws-durable-execution-sdk-js/src/context/durable-context/durable-context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class DurableContextImpl implements DurableContext {
7373
executionContext,
7474
checkpointToken || "",
7575
this.operationsEmitter,
76+
this.contextLogger || undefined,
7677
);
7778
this.durableExecutionMode = durableExecutionMode;
7879

packages/aws-durable-execution-sdk-js/src/context/execution-context/execution-context.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ jest.mock("../../utils/logger/logger");
1414
jest.mock("../../termination-manager/termination-manager");
1515

1616
describe("initializeExecutionContext", () => {
17+
// Matcher for Logger interface
18+
const expectLogger = expect.objectContaining({
19+
log: expect.any(Function),
20+
info: expect.any(Function),
21+
error: expect.any(Function),
22+
warn: expect.any(Function),
23+
debug: expect.any(Function),
24+
});
25+
1726
// Setup common test variables
1827
const mockCheckpointToken = "test-checkpoint-token";
1928
const mockDurableExecutionArn = "test-durable-execution-arn";
@@ -195,6 +204,7 @@ describe("initializeExecutionContext", () => {
195204
mockCheckpointToken,
196205
"test-durable-execution-arn",
197206
"token1",
207+
expectLogger,
198208
);
199209
expect(result.executionContext._stepData).toEqual({
200210
step1: mockInitialOperations[1],
@@ -250,11 +260,13 @@ describe("initializeExecutionContext", () => {
250260
mockCheckpointToken,
251261
"test-durable-execution-arn",
252262
"token1",
263+
expectLogger,
253264
);
254265
expect(mockExecutionState.getStepData).toHaveBeenCalledWith(
255266
mockCheckpointToken,
256267
"test-durable-execution-arn",
257268
"token2",
269+
expectLogger,
258270
);
259271
expect(result.executionContext._stepData).toEqual({
260272
step1: mockInitialOperations[1],
@@ -300,12 +312,14 @@ describe("initializeExecutionContext", () => {
300312
mockCheckpointToken,
301313
"test-durable-execution-arn",
302314
"token1",
315+
expectLogger,
303316
);
304317

305318
expect(mockExecutionState.getStepData).toHaveBeenCalledWith(
306319
mockCheckpointToken,
307320
"test-durable-execution-arn",
308321
"token2",
322+
expectLogger,
309323
);
310324

311325
expect(result.executionContext._stepData).toEqual({
@@ -337,6 +351,7 @@ describe("initializeExecutionContext", () => {
337351
mockCheckpointToken,
338352
"test-durable-execution-arn",
339353
"token1",
354+
expectLogger,
340355
);
341356
expect(mockExecutionState.getStepData).toHaveBeenCalledTimes(1); // Should only be called once
342357
expect(result.executionContext._stepData).toEqual({
@@ -372,6 +387,7 @@ describe("initializeExecutionContext", () => {
372387
mockCheckpointToken,
373388
"test-durable-execution-arn",
374389
"token1",
390+
expectLogger,
375391
);
376392
expect(result.executionContext._stepData).toEqual({});
377393
});
@@ -413,6 +429,7 @@ describe("initializeExecutionContext", () => {
413429
mockCheckpointToken,
414430
"test-durable-execution-arn",
415431
"token1",
432+
expectLogger,
416433
);
417434
expect(result.executionContext._stepData).toEqual({
418435
step1: mockInitialOperations[1], // Only the initial operations should be present

packages/aws-durable-execution-sdk-js/src/context/execution-context/execution-context.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
} from "../../types";
99
import { log } from "../../utils/logger/logger";
1010
import { getStepData as getStepDataUtil } from "../../utils/step-id-utils/step-id-utils";
11+
import { createContextLoggerFactory } from "../../utils/logger/context-logger";
12+
import { createDefaultLogger } from "../../utils/logger/default-logger";
1113

1214
export const initializeExecutionContext = async (
1315
event: DurableExecutionInvocationInput,
@@ -24,6 +26,13 @@ export const initializeExecutionContext = async (
2426

2527
const state = getExecutionState();
2628

29+
// Create logger for initialization errors using existing logger factory
30+
const tempContext = { durableExecutionArn } as ExecutionContext;
31+
const initLogger = createContextLoggerFactory(
32+
tempContext,
33+
createDefaultLogger,
34+
)("");
35+
2736
const operationsArray = [...(event.InitialExecutionState.Operations || [])];
2837
let nextMarker = event.InitialExecutionState.NextMarker;
2938

@@ -32,6 +41,7 @@ export const initializeExecutionContext = async (
3241
checkpointToken,
3342
durableExecutionArn,
3443
nextMarker,
44+
initLogger,
3545
);
3646
operationsArray.push(...(response.Operations || []));
3747
nextMarker = response.NextMarker || "";

packages/aws-durable-execution-sdk-js/src/storage/api-storage.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,4 +243,75 @@ describe("ApiStorage", () => {
243243
}),
244244
);
245245
});
246+
247+
test("should log getStepData errors to developer logger when provided", async () => {
248+
const mockError = {
249+
message: "GetDurableExecutionState failed",
250+
$metadata: { requestId: "test-request-id-789" },
251+
};
252+
mockLambdaClient.send.mockRejectedValue(mockError);
253+
254+
const mockLogger = {
255+
error: jest.fn(),
256+
info: jest.fn(),
257+
warn: jest.fn(),
258+
debug: jest.fn(),
259+
log: jest.fn(),
260+
};
261+
262+
try {
263+
await apiStorage.getStepData(
264+
"checkpoint-token",
265+
"test-execution-arn",
266+
"next-marker",
267+
mockLogger,
268+
);
269+
} catch (_error) {
270+
// Expected to throw
271+
}
272+
273+
expect(mockLogger.error).toHaveBeenCalledWith(
274+
"Failed to get durable execution state",
275+
mockError,
276+
{ requestId: "test-request-id-789" },
277+
);
278+
});
279+
280+
test("should log checkpoint errors to developer logger when provided", async () => {
281+
const mockError = {
282+
message: "CheckpointDurableExecution failed",
283+
$metadata: { requestId: "test-request-id-999" },
284+
};
285+
mockLambdaClient.send.mockRejectedValue(mockError);
286+
287+
const mockLogger = {
288+
error: jest.fn(),
289+
info: jest.fn(),
290+
warn: jest.fn(),
291+
debug: jest.fn(),
292+
log: jest.fn(),
293+
};
294+
295+
const checkpointData: CheckpointDurableExecutionRequest = {
296+
DurableExecutionArn: "test-execution-arn",
297+
CheckpointToken: "",
298+
Updates: [],
299+
};
300+
301+
try {
302+
await apiStorage.checkpoint(
303+
"checkpoint-token",
304+
checkpointData,
305+
mockLogger,
306+
);
307+
} catch (_error) {
308+
// Expected to throw
309+
}
310+
311+
expect(mockLogger.error).toHaveBeenCalledWith(
312+
"Failed to checkpoint durable execution",
313+
mockError,
314+
{ requestId: "test-request-id-999" },
315+
);
316+
});
246317
});

packages/aws-durable-execution-sdk-js/src/storage/api-storage.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from "@aws-sdk/client-lambda";
99
import { ExecutionState } from "./storage";
1010
import { log } from "../utils/logger/logger";
11+
import { Logger } from "../types";
1112

1213
/**
1314
* Implementation of ExecutionState that uses the new \@aws-sdk/client-lambda
@@ -23,12 +24,14 @@ export class ApiStorage implements ExecutionState {
2324
* Gets step data from the durable execution
2425
* @param checkpointToken - The checkpoint token
2526
* @param nextMarker - The pagination token
27+
* @param logger - Optional developer logger for error reporting
2628
* @returns Response with operations data
2729
*/
2830
async getStepData(
2931
checkpointToken: string,
3032
durableExecutionArn: string,
3133
nextMarker: string,
34+
logger?: Logger,
3235
): Promise<GetDurableExecutionStateResponse> {
3336
try {
3437
const response = await this.client.send(
@@ -42,6 +45,7 @@ export class ApiStorage implements ExecutionState {
4245

4346
return response;
4447
} catch (error) {
48+
// Internal debug logging
4549
log("❌", "GetDurableExecutionState failed", {
4650
error,
4751
requestId: (error as { $metadata?: { requestId?: string } })?.$metadata
@@ -50,6 +54,15 @@ export class ApiStorage implements ExecutionState {
5054
CheckpointToken: checkpointToken,
5155
Marker: nextMarker,
5256
});
57+
58+
// Developer logging if logger provided
59+
if (logger) {
60+
logger.error("Failed to get durable execution state", error as Error, {
61+
requestId: (error as { $metadata?: { requestId?: string } })
62+
?.$metadata?.requestId,
63+
});
64+
}
65+
5366
throw error;
5467
}
5568
}
@@ -58,11 +71,13 @@ export class ApiStorage implements ExecutionState {
5871
* Checkpoints the durable execution with operation updates
5972
* @param checkpointToken - The checkpoint token
6073
* @param data - The checkpoint data
74+
* @param logger - Optional developer logger for error reporting
6175
* @returns Checkpoint response
6276
*/
6377
async checkpoint(
6478
checkpointToken: string,
6579
data: CheckpointDurableExecutionRequest,
80+
logger?: Logger,
6681
): Promise<CheckpointDurableExecutionResponse> {
6782
try {
6883
const response = await this.client.send(
@@ -75,6 +90,7 @@ export class ApiStorage implements ExecutionState {
7590
);
7691
return response;
7792
} catch (error) {
93+
// Internal debug logging
7894
log("❌", "CheckpointDurableExecution failed", {
7995
error,
8096
requestId: (error as { $metadata?: { requestId?: string } })?.$metadata
@@ -83,6 +99,15 @@ export class ApiStorage implements ExecutionState {
8399
CheckpointToken: checkpointToken,
84100
ClientToken: data.ClientToken,
85101
});
102+
103+
// Developer logging if logger provided
104+
if (logger) {
105+
logger.error("Failed to checkpoint durable execution", error as Error, {
106+
requestId: (error as { $metadata?: { requestId?: string } })
107+
?.$metadata?.requestId,
108+
});
109+
}
110+
86111
throw error;
87112
}
88113
}

packages/aws-durable-execution-sdk-js/src/storage/storage.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@ import {
44
GetDurableExecutionStateResponse,
55
} from "@aws-sdk/client-lambda";
66
import { ApiStorage } from "./api-storage";
7+
import { Logger } from "../types";
78

89
export interface ExecutionState {
910
getStepData(
1011
taskToken: string,
1112
durableExecutionArn: string,
1213
nextToken: string,
14+
logger?: Logger,
1315
): Promise<GetDurableExecutionStateResponse>;
1416
checkpoint(
1517
taskToken: string,
1618
data: CheckpointDurableExecutionRequest,
19+
logger?: Logger,
1720
): Promise<CheckpointDurableExecutionResponse>;
1821
}
1922

packages/aws-durable-execution-sdk-js/src/utils/checkpoint/checkpoint-integration.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ describe("Checkpoint Integration Tests", () => {
145145
}),
146146
]),
147147
}),
148+
undefined, // logger parameter
148149
);
149150
});
150151

@@ -178,6 +179,7 @@ describe("Checkpoint Integration Tests", () => {
178179
),
179180
),
180181
}),
182+
undefined, // logger parameter
181183
);
182184
});
183185

packages/aws-durable-execution-sdk-js/src/utils/checkpoint/checkpoint.test.ts

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ describe("CheckpointHandler", () => {
8787
},
8888
],
8989
},
90+
undefined, // logger parameter
9091
);
9192
});
9293
});
@@ -598,6 +599,7 @@ describe("CheckpointHandler", () => {
598599
},
599600
],
600601
},
602+
undefined, // logger parameter
601603
);
602604
});
603605
});
@@ -819,11 +821,15 @@ describe("deleteCheckpointHandler", () => {
819821

820822
await checkpoint.force();
821823

822-
expect(mockState1.checkpoint).toHaveBeenCalledWith("test-token", {
823-
DurableExecutionArn: "test-durable-execution-arn-1",
824-
CheckpointToken: "test-token",
825-
Updates: [],
826-
});
824+
expect(mockState1.checkpoint).toHaveBeenCalledWith(
825+
"test-token",
826+
{
827+
DurableExecutionArn: "test-durable-execution-arn-1",
828+
CheckpointToken: "test-token",
829+
Updates: [],
830+
},
831+
undefined,
832+
);
827833
});
828834

829835
it("should not make additional checkpoint call when force is called during ongoing checkpoint", async () => {
@@ -873,17 +879,21 @@ describe("deleteCheckpointHandler", () => {
873879

874880
// Should still only have made one API call total (the force request piggybacked)
875881
expect(mockState1.checkpoint).toHaveBeenCalledTimes(1);
876-
expect(mockState1.checkpoint).toHaveBeenCalledWith("test-token", {
877-
DurableExecutionArn: "test-durable-execution-arn-1",
878-
CheckpointToken: "test-token",
879-
Updates: [
880-
{
881-
Type: OperationType.STEP,
882-
Action: OperationAction.START,
883-
Id: hashId("step1"),
884-
},
885-
],
886-
});
882+
expect(mockState1.checkpoint).toHaveBeenCalledWith(
883+
"test-token",
884+
{
885+
DurableExecutionArn: "test-durable-execution-arn-1",
886+
CheckpointToken: "test-token",
887+
Updates: [
888+
{
889+
Type: OperationType.STEP,
890+
Action: OperationAction.START,
891+
Id: hashId("step1"),
892+
},
893+
],
894+
},
895+
undefined,
896+
);
887897
});
888898

889899
it("should terminate execution when force checkpoint fails", async () => {
@@ -979,6 +989,7 @@ describe("createCheckpointHandler", () => {
979989
}),
980990
]),
981991
}),
992+
undefined, // logger parameter
982993
);
983994
});
984995

0 commit comments

Comments
 (0)