Skip to content

Commit 01799b4

Browse files
committed
feat(middleware-logger): log request errors
1 parent a2e5bdb commit 01799b4

File tree

2 files changed

+142
-20
lines changed

2 files changed

+142
-20
lines changed

packages/middleware-logger/src/loggerMiddleware.spec.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ describe("loggerMiddleware", () => {
7272
expect(response).toStrictEqual(mockResponse);
7373
});
7474

75+
it("rejects without logging if context.logger doesn't have error function", async () => {
76+
const nextError = new Error("next error");
77+
mockNext.mockRejectedValueOnce(nextError);
78+
const logger = {} as Logger;
79+
const response = await expect(loggerMiddleware()(mockNext, { logger })(mockArgs)).rejects.toThrow(nextError);
80+
expect(mockNext).toHaveBeenCalledTimes(1);
81+
});
82+
7583
describe("logs if context.logger has info function", () => {
7684
it("success case with clientName, commandName, input, metadata", async () => {
7785
mockNext.mockResolvedValueOnce(mockResponse);
@@ -198,4 +206,120 @@ describe("loggerMiddleware", () => {
198206
});
199207
});
200208
});
209+
210+
describe("logs if context.logger has error function", () => {
211+
it("should reject if next throws synchronously", async () => {
212+
const { $metadata } = mockOutput;
213+
const nextError = new Error("example error");
214+
Object.assign(nextError, { $metadata });
215+
mockNext.mockImplementationOnce(() => {
216+
throw nextError;
217+
});
218+
219+
const clientName = "mockClientName";
220+
const commandName = "mockCommandName";
221+
222+
const logger = { error: jest.fn() } as unknown as Logger;
223+
const inputFilterSensitiveLog = jest.fn().mockImplementationOnce((input) => input);
224+
const outputFilterSensitiveLog = jest.fn().mockImplementationOnce((output) => output);
225+
226+
const context = {
227+
clientName,
228+
commandName,
229+
logger,
230+
inputFilterSensitiveLog,
231+
outputFilterSensitiveLog,
232+
};
233+
234+
await expect(loggerMiddleware()(mockNext, context)(mockArgs)).rejects.toThrow(nextError);
235+
expect(mockNext).toHaveBeenCalledTimes(1);
236+
237+
expect(logger.error).toHaveBeenCalledTimes(1);
238+
expect(logger.error).toHaveBeenCalledWith({
239+
clientName,
240+
commandName,
241+
input: mockArgs.input,
242+
error: nextError,
243+
metadata: $metadata,
244+
});
245+
});
246+
247+
it("should reject if next rejects", async () => {
248+
const { $metadata } = mockOutput;
249+
const nextError = new Error("example error");
250+
Object.assign(nextError, { $metadata });
251+
mockNext.mockRejectedValueOnce(nextError);
252+
253+
const clientName = "mockClientName";
254+
const commandName = "mockCommandName";
255+
256+
const logger = { error: jest.fn() } as unknown as Logger;
257+
const inputFilterSensitiveLog = jest.fn().mockImplementationOnce((input) => input);
258+
const outputFilterSensitiveLog = jest.fn().mockImplementationOnce((output) => output);
259+
260+
const context = {
261+
clientName,
262+
commandName,
263+
logger,
264+
inputFilterSensitiveLog,
265+
outputFilterSensitiveLog,
266+
};
267+
268+
await expect(loggerMiddleware()(mockNext, context)(mockArgs)).rejects.toThrow(nextError);
269+
expect(mockNext).toHaveBeenCalledTimes(1);
270+
271+
expect(logger.error).toHaveBeenCalledTimes(1);
272+
expect(logger.error).toHaveBeenCalledWith({
273+
clientName,
274+
commandName,
275+
input: mockArgs.input,
276+
error: nextError,
277+
metadata: $metadata,
278+
});
279+
});
280+
281+
it("should use override log filters for DynamoDBDocumentClient if present", async () => {
282+
const { $metadata } = mockOutput;
283+
const nextError = new Error("example error");
284+
Object.assign(nextError, { $metadata });
285+
mockNext.mockRejectedValueOnce(nextError);
286+
287+
const logger = { error: jest.fn() } as unknown as Logger;
288+
const clientName = "mockClientName";
289+
const commandName = "mockCommandName";
290+
291+
const mockInputLog = { inputKey: "inputKey", inputSensitiveKey: "SENSITIVE_VALUE" };
292+
const inputFilterSensitiveLog = jest.fn().mockReturnValueOnce(mockInputLog);
293+
const mockOutputLog = { outputKey: "outputKey", outputSensitiveKey: "SENSITIVE_VALUE" };
294+
const outputFilterSensitiveLog = jest.fn().mockReturnValueOnce(mockOutputLog);
295+
296+
const context: HandlerExecutionContext = {
297+
logger,
298+
clientName,
299+
commandName,
300+
dynamoDbDocumentClientOptions: {
301+
overrideInputFilterSensitiveLog: inputFilterSensitiveLog,
302+
overrideOutputFilterSensitiveLog: outputFilterSensitiveLog,
303+
},
304+
inputFilterSensitiveLog() {
305+
throw new Error("should not be called");
306+
},
307+
outputFilterSensitiveLog() {
308+
throw new Error("should not be called");
309+
},
310+
};
311+
312+
await expect(loggerMiddleware()(mockNext, context)(mockArgs)).rejects.toThrow(nextError);
313+
expect(mockNext).toHaveBeenCalledTimes(1);
314+
315+
expect(logger.error).toHaveBeenCalledTimes(1);
316+
expect(logger.error).toHaveBeenCalledWith({
317+
clientName,
318+
commandName,
319+
input: mockInputLog,
320+
error: nextError,
321+
metadata: $metadata,
322+
});
323+
});
324+
});
201325
});

packages/middleware-logger/src/loggerMiddleware.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,35 +16,33 @@ export const loggerMiddleware =
1616
context: HandlerExecutionContext
1717
): InitializeHandler<any, Output> =>
1818
async (args: InitializeHandlerArguments<any>): Promise<InitializeHandlerOutput<Output>> => {
19-
const response = await next(args);
20-
const {
21-
clientName,
22-
commandName,
23-
logger,
24-
inputFilterSensitiveLog,
25-
outputFilterSensitiveLog,
26-
dynamoDbDocumentClientOptions = {},
27-
} = context;
19+
const { clientName, commandName, logger, dynamoDbDocumentClientOptions = {} } = context;
2820

2921
const { overrideInputFilterSensitiveLog, overrideOutputFilterSensitiveLog } = dynamoDbDocumentClientOptions;
22+
const inputFilterSensitiveLog = overrideInputFilterSensitiveLog ?? context.inputFilterSensitiveLog;
23+
const outputFilterSensitiveLog = overrideOutputFilterSensitiveLog ?? context.outputFilterSensitiveLog;
3024

31-
if (!logger) {
32-
return response;
33-
}
34-
35-
if (typeof logger.info === "function") {
25+
try {
26+
const response = await next(args);
3627
const { $metadata, ...outputWithoutMetadata } = response.output;
37-
38-
logger.info({
28+
logger?.info?.({
3929
clientName,
4030
commandName,
41-
input: (overrideInputFilterSensitiveLog ?? inputFilterSensitiveLog)(args.input),
42-
output: (overrideOutputFilterSensitiveLog ?? outputFilterSensitiveLog)(outputWithoutMetadata),
31+
input: inputFilterSensitiveLog(args.input),
32+
output: outputFilterSensitiveLog(outputWithoutMetadata),
4333
metadata: $metadata,
4434
});
35+
return response;
36+
} catch (error) {
37+
logger?.error?.({
38+
clientName,
39+
commandName,
40+
input: inputFilterSensitiveLog(args.input),
41+
error,
42+
metadata: (error as any).$metadata,
43+
});
44+
throw error;
4545
}
46-
47-
return response;
4846
};
4947

5048
export const loggerMiddlewareOptions: InitializeHandlerOptions & AbsoluteLocation = {

0 commit comments

Comments
 (0)