Skip to content

Commit f7aa59b

Browse files
committed
feat(sdk): add configureLogger method with modeAware option
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
1 parent efa419a commit f7aa59b

File tree

16 files changed

+719
-133
lines changed

16 files changed

+719
-133
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)