Skip to content

Commit eb0c30c

Browse files
authored
feat(sdk): add configureLogger method with modeAware option (#253)
Replace setCustomLogger() with configureLogger() that accepts LoggerConfig object with customLogger and modeAware options. The modeAware flag controls whether logs are suppressed during replay mode (default: true). BREAKING CHANGE: Removed setCustomLogger() method in favor of configureLogger(). This is acceptable as the SDK has not been launched yet and there are no existing users. Changes: - Add configureLogger(config: LoggerConfig) method to DurableContext - Add LoggerConfig interface with customLogger and modeAware properties - Update createModeAwareLogger to accept modeAwareEnabled parameter - Change default modeAware behavior to true (suppress logs during replay) - Fix replay mode switching for backend-completed operations (wait, invoke, createCallback, waitForCallback) by calling checkAndUpdateReplayMode() after completion - Update all tests to reflect new default behavior and mode switching fix - Add logger-test examples with file-based logging (local-only tests) - Update documentation: README, API_SPECIFICATION.md with new API - Add TSDoc comments for Logger types and configureLogger method - Remove old logger-example with outdated information **Limitation**: Integration tests are against local runner and we have a issue to add it to Cloud mode: #255 By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent efa419a commit eb0c30c

File tree

16 files changed

+722
-129
lines changed

16 files changed

+722
-129
lines changed

packages/aws-durable-execution-sdk-js-examples/src/examples/logger-example/logger-example.test.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

packages/aws-durable-execution-sdk-js-examples/src/examples/logger-example/logger-example.ts

Lines changed: 0 additions & 70 deletions
This file was deleted.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { handler } from "./logger-after-callback";
2+
import { createTests } from "../../../utils/test-helper";
3+
import {
4+
InvocationType,
5+
WaitingOperationStatus,
6+
} from "@aws/durable-execution-sdk-js-testing";
7+
import * as fs from "fs";
8+
import * as path from "path";
9+
import * as os from "os";
10+
import { randomUUID } from "crypto";
11+
12+
createTests({
13+
name: "logger-after-callback",
14+
functionName: "logger-after-callback",
15+
handler,
16+
invocationType: InvocationType.Event,
17+
tests: (runner, isCloud) => {
18+
if (!isCloud) {
19+
it("should log correctly with modeAware=true", async () => {
20+
const logFilePath = path.join(
21+
os.tmpdir(),
22+
`logger-test-${randomUUID()}.log`,
23+
);
24+
25+
if (fs.existsSync(logFilePath)) {
26+
fs.unlinkSync(logFilePath);
27+
}
28+
29+
try {
30+
const executionPromise = runner.run({
31+
payload: { logFilePath, modeAware: true },
32+
});
33+
34+
const callbackOp = runner.getOperationByIndex(0);
35+
await callbackOp.waitForData(WaitingOperationStatus.STARTED);
36+
await callbackOp.sendCallbackSuccess("test-result");
37+
38+
const execution = await executionPromise;
39+
40+
const result = execution.getResult() as any;
41+
expect(result.message).toBe("Success");
42+
expect(result.callbackId).toBeDefined();
43+
expect(result.result).toBe("test-result");
44+
45+
const logContent = fs.readFileSync(logFilePath, "utf-8");
46+
const logLines = logContent
47+
.trim()
48+
.split("\n")
49+
.map((line) => JSON.parse(line));
50+
51+
const beforeCallbackLogs = logLines.filter(
52+
(log) => log.message === "Before createCallback",
53+
);
54+
const afterCallbackLogs = logLines.filter(
55+
(log) => log.message === "After createCallback",
56+
);
57+
58+
// With modeAware: true:
59+
// - "Before createCallback" appears once (execution mode only, suppressed during replay)
60+
// - "After createCallback" appears once (after callback resolves, in execution mode)
61+
expect(beforeCallbackLogs.length).toBe(1);
62+
expect(afterCallbackLogs.length).toBe(1);
63+
} finally {
64+
if (fs.existsSync(logFilePath)) {
65+
fs.unlinkSync(logFilePath);
66+
}
67+
}
68+
});
69+
70+
it("should log correctly with modeAware=false", async () => {
71+
const logFilePath = path.join(
72+
os.tmpdir(),
73+
`logger-test-${randomUUID()}.log`,
74+
);
75+
76+
if (fs.existsSync(logFilePath)) {
77+
fs.unlinkSync(logFilePath);
78+
}
79+
80+
try {
81+
const executionPromise = runner.run({
82+
payload: { logFilePath, modeAware: false },
83+
});
84+
85+
const callbackOp = runner.getOperationByIndex(0);
86+
await callbackOp.waitForData(WaitingOperationStatus.STARTED);
87+
await callbackOp.sendCallbackSuccess("test-result");
88+
89+
const execution = await executionPromise;
90+
91+
const result = execution.getResult() as any;
92+
expect(result.message).toBe("Success");
93+
94+
const logContent = fs.readFileSync(logFilePath, "utf-8");
95+
const logLines = logContent
96+
.trim()
97+
.split("\n")
98+
.map((line) => JSON.parse(line));
99+
100+
const beforeCallbackLogs = logLines.filter(
101+
(log) => log.message === "Before createCallback",
102+
);
103+
const afterCallbackLogs = logLines.filter(
104+
(log) => log.message === "After createCallback",
105+
);
106+
107+
// With modeAware: false:
108+
// - "Before createCallback" appears twice (execution + replay)
109+
// - "After createCallback" appears once (after callback resolves)
110+
expect(beforeCallbackLogs.length).toBe(2);
111+
expect(afterCallbackLogs.length).toBe(1);
112+
} finally {
113+
if (fs.existsSync(logFilePath)) {
114+
fs.unlinkSync(logFilePath);
115+
}
116+
}
117+
});
118+
}
119+
120+
it("should execute successfully", async () => {
121+
const executionPromise = runner.run();
122+
123+
const callbackOp = runner.getOperationByIndex(0);
124+
await callbackOp.waitForData(WaitingOperationStatus.STARTED);
125+
await callbackOp.sendCallbackSuccess("test-result");
126+
127+
const execution = await executionPromise;
128+
const result = execution.getResult() as any;
129+
expect(result.message).toBe("Success");
130+
});
131+
},
132+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { withDurableExecution, Logger } from "@aws/durable-execution-sdk-js";
2+
import { ExampleConfig } from "../../../types";
3+
import * as fs from "fs";
4+
5+
export const config: ExampleConfig = {
6+
name: "Logger After Callback",
7+
description: "Test logger mode switching after createCallback operation",
8+
};
9+
10+
interface LoggerTestEvent {
11+
logFilePath?: string;
12+
modeAware?: boolean;
13+
}
14+
15+
export const handler = withDurableExecution(
16+
async (event: LoggerTestEvent, context) => {
17+
if (event.logFilePath) {
18+
const fileLogger: Logger = {
19+
log: (level, message, data) => {
20+
fs.appendFileSync(
21+
event.logFilePath!,
22+
JSON.stringify({ level, message, data }) + "\n",
23+
);
24+
},
25+
info: (message, data) => {
26+
fs.appendFileSync(
27+
event.logFilePath!,
28+
JSON.stringify({ level: "info", message, data }) + "\n",
29+
);
30+
},
31+
error: (message, error, data) => {
32+
fs.appendFileSync(
33+
event.logFilePath!,
34+
JSON.stringify({ level: "error", message, error, data }) + "\n",
35+
);
36+
},
37+
warn: (message, data) => {
38+
fs.appendFileSync(
39+
event.logFilePath!,
40+
JSON.stringify({ level: "warn", message, data }) + "\n",
41+
);
42+
},
43+
debug: (message, data) => {
44+
fs.appendFileSync(
45+
event.logFilePath!,
46+
JSON.stringify({ level: "debug", message, data }) + "\n",
47+
);
48+
},
49+
};
50+
51+
context.configureLogger({
52+
customLogger: fileLogger,
53+
modeAware: event.modeAware ?? true,
54+
});
55+
} else {
56+
context.configureLogger({ modeAware: event.modeAware ?? true });
57+
}
58+
59+
context.logger.info("Before createCallback");
60+
61+
const [callbackPromise, callbackId] =
62+
await context.createCallback<string>();
63+
64+
const result = await callbackPromise;
65+
66+
context.logger.info("After createCallback");
67+
68+
return { message: "Success", callbackId, result };
69+
},
70+
);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { handler } from "./logger-after-wait";
2+
import { createTests } from "../../../utils/test-helper";
3+
import * as fs from "fs";
4+
import * as path from "path";
5+
import * as os from "os";
6+
import { randomUUID } from "crypto";
7+
8+
createTests({
9+
name: "logger-after-wait",
10+
functionName: "logger-after-wait",
11+
handler,
12+
tests: (runner, isCloud) => {
13+
if (!isCloud) {
14+
it("should log after wait in execution mode with modeAware=true", async () => {
15+
const logFilePath = path.join(
16+
os.tmpdir(),
17+
`logger-test-${randomUUID()}.log`,
18+
);
19+
20+
if (fs.existsSync(logFilePath)) {
21+
fs.unlinkSync(logFilePath);
22+
}
23+
24+
try {
25+
const execution = await runner.run({
26+
payload: { logFilePath, modeAware: true },
27+
});
28+
29+
expect(execution.getResult()).toEqual({ message: "Success" });
30+
31+
const logContent = fs.readFileSync(logFilePath, "utf-8");
32+
const logLines = logContent
33+
.trim()
34+
.split("\n")
35+
.map((line) => JSON.parse(line));
36+
37+
const beforeWaitLogs = logLines.filter(
38+
(log) => log.message === "Before wait",
39+
);
40+
const afterWaitLogs = logLines.filter(
41+
(log) => log.message === "After wait",
42+
);
43+
44+
// With modeAware: true, both logs appear once (execution mode only)
45+
expect(beforeWaitLogs.length).toBe(1);
46+
expect(afterWaitLogs.length).toBe(1);
47+
} finally {
48+
if (fs.existsSync(logFilePath)) {
49+
fs.unlinkSync(logFilePath);
50+
}
51+
}
52+
});
53+
}
54+
55+
it("should execute successfully", async () => {
56+
const execution = await runner.run();
57+
expect(execution.getResult()).toEqual({ message: "Success" });
58+
});
59+
},
60+
});

0 commit comments

Comments
 (0)