Skip to content

Commit b6675b6

Browse files
authored
fix: move logging of input/output and req/res to serde (#1526)
* chore: move logging of input/request to serde * chore: move logging of output/response to serde * test: update loggerMiddleware tests * test: add tests for serializerMiddleware * test: add tests for deserializerMiddleware * test: prefix mock to loggerMiddleware tests
1 parent 31fbb75 commit b6675b6

File tree

7 files changed

+277
-76
lines changed

7 files changed

+277
-76
lines changed
Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BuildHandlerArguments, Logger, MiddlewareStack } from "@aws-sdk/types";
1+
import { Logger, MiddlewareStack } from "@aws-sdk/types";
22

33
import { getLoggerPlugin, loggerMiddleware, loggerMiddlewareOptions } from "./loggerMiddleware";
44

@@ -19,9 +19,9 @@ describe("getLoggerPlugin", () => {
1919
});
2020

2121
describe("loggerMiddleware", () => {
22-
const next = jest.fn();
22+
const mockNext = jest.fn();
2323

24-
const args = {
24+
const mockArgs = {
2525
input: {
2626
inputKey: "inputValue",
2727
},
@@ -32,10 +32,6 @@ describe("loggerMiddleware", () => {
3232
};
3333

3434
const mockResponse = {
35-
response: {
36-
statusCode: 200,
37-
headers: {},
38-
},
3935
output: {
4036
$metadata: {
4137
statusCode: 200,
@@ -46,67 +42,44 @@ describe("loggerMiddleware", () => {
4642
};
4743

4844
beforeEach(() => {
49-
next.mockResolvedValueOnce(mockResponse);
45+
mockNext.mockResolvedValueOnce(mockResponse);
5046
});
5147

5248
afterEach(() => {
5349
jest.clearAllMocks();
5450
});
5551

5652
it("returns without logging if context.logger is not defined", async () => {
57-
const response = await loggerMiddleware()(next, {})(args as BuildHandlerArguments<any>);
58-
expect(next).toHaveBeenCalledTimes(1);
53+
const response = await loggerMiddleware()(mockNext, {})(mockArgs);
54+
expect(mockNext).toHaveBeenCalledTimes(1);
5955
expect(response).toStrictEqual(mockResponse);
6056
});
6157

62-
it("returns without logging if context.logger doesn't have debug/info functions", async () => {
58+
it("returns without logging if context.logger doesn't have info function", async () => {
6359
const logger = {} as Logger;
64-
const response = await loggerMiddleware()(next, { logger })(args as BuildHandlerArguments<any>);
65-
expect(next).toHaveBeenCalledTimes(1);
60+
const response = await loggerMiddleware()(mockNext, { logger })(mockArgs);
61+
expect(mockNext).toHaveBeenCalledTimes(1);
6662
expect(response).toStrictEqual(mockResponse);
6763
});
6864

69-
it("logs $metadata, input, output if context.logger has info function", async () => {
65+
it("logs $metadata if context.logger has info function", async () => {
7066
const logger = ({ info: jest.fn() } as unknown) as Logger;
7167

72-
const inputFilterSensitiveLog = jest.fn().mockImplementationOnce((input) => input);
73-
const outputFilterSensitiveLog = jest.fn().mockImplementationOnce((output) => output);
7468
const context = {
7569
logger,
76-
inputFilterSensitiveLog,
77-
outputFilterSensitiveLog,
7870
};
7971

80-
const response = await loggerMiddleware()(next, context)(args as BuildHandlerArguments<any>);
81-
expect(next).toHaveBeenCalledTimes(1);
72+
const response = await loggerMiddleware()(mockNext, context)(mockArgs);
73+
expect(mockNext).toHaveBeenCalledTimes(1);
8274
expect(response).toStrictEqual(mockResponse);
8375

84-
expect(inputFilterSensitiveLog).toHaveBeenCalledTimes(1);
85-
expect(outputFilterSensitiveLog).toHaveBeenCalledTimes(1);
8676
expect(logger.info).toHaveBeenCalledTimes(1);
8777

8878
const {
89-
output: { $metadata, ...outputWithoutMetadata },
79+
output: { $metadata },
9080
} = mockResponse;
9181
expect(logger.info).toHaveBeenCalledWith({
9282
$metadata,
93-
input: args.input,
94-
output: outputWithoutMetadata,
95-
});
96-
});
97-
98-
it("logs httpRequest, httpResponse if context.logger has debug function", async () => {
99-
const logger = ({ debug: jest.fn() } as unknown) as Logger;
100-
const response = await loggerMiddleware()(next, { logger })(args as BuildHandlerArguments<any>);
101-
expect(next).toHaveBeenCalledTimes(1);
102-
expect(response).toStrictEqual(mockResponse);
103-
104-
expect(logger.debug).toHaveBeenCalledTimes(2);
105-
expect(logger.debug).toHaveBeenNthCalledWith(1, {
106-
httpRequest: args.request,
107-
});
108-
expect(logger.debug).toHaveBeenNthCalledWith(2, {
109-
httpResponse: mockResponse.response,
11083
});
11184
});
11285
});

packages/middleware-logger/src/loggerMiddleware.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const loggerMiddleware = () => <Output extends MetadataBearer = MetadataB
1313
next: BuildHandler<any, Output>,
1414
context: HandlerExecutionContext
1515
): BuildHandler<any, Output> => async (args: BuildHandlerArguments<any>): Promise<BuildHandlerOutput<Output>> => {
16-
const { logger, inputFilterSensitiveLog, outputFilterSensitiveLog } = context;
16+
const { logger } = context;
1717

1818
const response = await next(args);
1919

@@ -22,23 +22,14 @@ export const loggerMiddleware = () => <Output extends MetadataBearer = MetadataB
2222
}
2323

2424
const {
25-
output: { $metadata, ...outputWithoutMetadata },
25+
output: { $metadata },
2626
} = response;
2727

28-
if (typeof logger.debug === "function") {
29-
logger.debug({
30-
httpRequest: args.request,
31-
});
32-
logger.debug({
33-
httpResponse: response.response,
34-
});
35-
}
36-
28+
// TODO: Populate custom metadata in https://github.com/aws/aws-sdk-js-v3/issues/1491#issuecomment-692174256
29+
// $metadata will be removed in https://github.com/aws/aws-sdk-js-v3/issues/1490
3730
if (typeof logger.info === "function") {
3831
logger.info({
3932
$metadata,
40-
input: inputFilterSensitiveLog(args.input),
41-
output: outputFilterSensitiveLog(outputWithoutMetadata),
4233
});
4334
}
4435

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const base = require("../../jest.config.base.js");
2+
3+
module.exports = {
4+
...base,
5+
};
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { Logger } from "@aws-sdk/types";
2+
3+
import { deserializerMiddleware } from "./deserializerMiddleware";
4+
5+
describe("deserializerMiddleware", () => {
6+
const mockNext = jest.fn();
7+
const mockDeserializer = jest.fn();
8+
9+
const mockOptions = {
10+
endpoint: () =>
11+
Promise.resolve({
12+
protocol: "protocol",
13+
hostname: "hostname",
14+
path: "path",
15+
}),
16+
};
17+
18+
const mockArgs = {
19+
input: {
20+
inputKey: "inputValue",
21+
},
22+
request: {
23+
method: "GET",
24+
headers: {},
25+
},
26+
};
27+
28+
const mockOutput = {
29+
$metadata: {
30+
statusCode: 200,
31+
requestId: "requestId",
32+
},
33+
outputKey: "outputValue",
34+
};
35+
36+
const mockNextResponse = {
37+
response: {
38+
statusCode: 200,
39+
headers: {},
40+
},
41+
};
42+
43+
const mockResponse = {
44+
response: mockNextResponse.response,
45+
output: mockOutput,
46+
};
47+
48+
beforeEach(() => {
49+
mockNext.mockResolvedValueOnce(mockNextResponse);
50+
mockDeserializer.mockResolvedValueOnce(mockOutput);
51+
});
52+
53+
afterEach(() => {
54+
expect(mockNext).toHaveBeenCalledTimes(1);
55+
expect(mockNext).toHaveBeenCalledWith(mockArgs);
56+
expect(mockDeserializer).toHaveBeenCalledTimes(1);
57+
expect(mockDeserializer).toHaveBeenCalledWith(mockNextResponse.response, mockOptions);
58+
jest.clearAllMocks();
59+
});
60+
61+
it("returns without logging if context.logger is not defined", async () => {
62+
const response = await deserializerMiddleware(mockOptions, mockDeserializer)(mockNext, {})(mockArgs);
63+
expect(response).toStrictEqual(mockResponse);
64+
});
65+
66+
it("returns without logging if context.logger doesn't have debug/info function", async () => {
67+
const logger = {} as Logger;
68+
const response = await deserializerMiddleware(mockOptions, mockDeserializer)(mockNext, { logger })(mockArgs);
69+
expect(response).toStrictEqual(mockResponse);
70+
});
71+
72+
it("logs output if context.logger has info function", async () => {
73+
const logger = ({ info: jest.fn() } as unknown) as Logger;
74+
75+
const outputFilterSensitiveLog = jest.fn().mockImplementationOnce((output) => output);
76+
const response = await deserializerMiddleware(mockOptions, mockDeserializer)(mockNext, {
77+
logger,
78+
outputFilterSensitiveLog: outputFilterSensitiveLog,
79+
})(mockArgs);
80+
81+
const { $metadata, ...outputWithoutMetadata } = mockOutput;
82+
expect(response).toStrictEqual(mockResponse);
83+
expect(outputFilterSensitiveLog).toHaveBeenCalledTimes(1);
84+
expect(outputFilterSensitiveLog).toHaveBeenCalledWith(outputWithoutMetadata);
85+
expect(logger.info).toHaveBeenCalledTimes(1);
86+
expect(logger.info).toHaveBeenCalledWith({ output: outputWithoutMetadata });
87+
});
88+
89+
it("logs response if context.logger has debug function", async () => {
90+
const logger = ({ debug: jest.fn() } as unknown) as Logger;
91+
92+
const response = await deserializerMiddleware(mockOptions, mockDeserializer)(mockNext, { logger })(mockArgs);
93+
94+
expect(response).toStrictEqual(mockResponse);
95+
expect(logger.debug).toHaveBeenCalledTimes(1);
96+
expect(logger.debug).toHaveBeenCalledWith({ httpResponse: mockNextResponse.response });
97+
});
98+
});

packages/middleware-serde/src/deserializerMiddleware.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,42 @@ import {
33
DeserializeHandlerArguments,
44
DeserializeHandlerOutput,
55
DeserializeMiddleware,
6+
HandlerExecutionContext,
67
ResponseDeserializer,
78
} from "@aws-sdk/types";
89

9-
export function deserializerMiddleware<Input extends object, Output extends object, RuntimeUtils = any>(
10+
export const deserializerMiddleware = <Input extends object, Output extends object, RuntimeUtils = any>(
1011
options: RuntimeUtils,
1112
deserializer: ResponseDeserializer<any, any, RuntimeUtils>
12-
): DeserializeMiddleware<Input, Output> {
13-
return (next: DeserializeHandler<Input, Output>): DeserializeHandler<Input, Output> => async (
14-
args: DeserializeHandlerArguments<Input>
15-
): Promise<DeserializeHandlerOutput<Output>> => {
16-
const { response } = await next(args);
17-
const parsed = await deserializer(response, options);
18-
return {
19-
response,
20-
output: parsed as Output,
21-
};
13+
): DeserializeMiddleware<Input, Output> => (
14+
next: DeserializeHandler<Input, Output>,
15+
context: HandlerExecutionContext
16+
): DeserializeHandler<Input, Output> => async (
17+
args: DeserializeHandlerArguments<Input>
18+
): Promise<DeserializeHandlerOutput<Output>> => {
19+
const { logger, outputFilterSensitiveLog } = context;
20+
21+
const { response } = await next(args);
22+
23+
if (typeof logger?.debug === "function") {
24+
logger.debug({
25+
httpResponse: response,
26+
});
27+
}
28+
29+
const parsed = await deserializer(response, options);
30+
31+
// Log parsed after $metadata is removed in https://github.com/aws/aws-sdk-js-v3/issues/1490
32+
const { $metadata, ...outputWithoutMetadata } = parsed;
33+
34+
if (typeof logger?.info === "function") {
35+
logger.info({
36+
output: outputFilterSensitiveLog(outputWithoutMetadata),
37+
});
38+
}
39+
40+
return {
41+
response,
42+
output: parsed as Output,
2243
};
23-
}
44+
};
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Logger } from "@aws-sdk/types";
2+
3+
import { serializerMiddleware } from "./serializerMiddleware";
4+
5+
describe("serializerMiddleware", () => {
6+
const mockNext = jest.fn();
7+
const mockSerializer = jest.fn();
8+
9+
const mockOptions = {
10+
endpoint: () =>
11+
Promise.resolve({
12+
protocol: "protocol",
13+
hostname: "hostname",
14+
path: "path",
15+
}),
16+
};
17+
18+
const mockArgs = {
19+
input: {
20+
inputKey: "inputValue",
21+
},
22+
};
23+
24+
const mockRequest = {
25+
method: "GET",
26+
headers: {},
27+
};
28+
29+
const mockResponse = {
30+
statusCode: 200,
31+
headers: {},
32+
};
33+
34+
const mockOutput = {
35+
$metadata: {
36+
statusCode: 200,
37+
requestId: "requestId",
38+
},
39+
outputKey: "outputValue",
40+
};
41+
42+
const mockReturn = {
43+
response: mockResponse,
44+
output: mockOutput,
45+
};
46+
47+
beforeEach(() => {
48+
mockNext.mockResolvedValueOnce(mockReturn);
49+
mockSerializer.mockResolvedValueOnce(mockRequest);
50+
});
51+
52+
afterEach(() => {
53+
expect(mockSerializer).toHaveBeenCalledTimes(1);
54+
expect(mockSerializer).toHaveBeenCalledWith(mockArgs.input, mockOptions);
55+
expect(mockNext).toHaveBeenCalledTimes(1);
56+
expect(mockNext).toHaveBeenCalledWith({ ...mockArgs, request: mockRequest });
57+
jest.clearAllMocks();
58+
});
59+
60+
it("returns without logging if context.logger is not defined", async () => {
61+
const response = await serializerMiddleware(mockOptions, mockSerializer)(mockNext, {})(mockArgs);
62+
expect(response).toStrictEqual(mockReturn);
63+
});
64+
65+
it("returns without logging if context.logger doesn't have debug/info function", async () => {
66+
const logger = {} as Logger;
67+
const response = await serializerMiddleware(mockOptions, mockSerializer)(mockNext, { logger })(mockArgs);
68+
expect(response).toStrictEqual(mockReturn);
69+
});
70+
71+
it("logs input if context.logger has info function", async () => {
72+
const logger = ({ info: jest.fn() } as unknown) as Logger;
73+
74+
const inputFilterSensitiveLog = jest.fn().mockImplementationOnce((input) => input);
75+
const response = await serializerMiddleware(mockOptions, mockSerializer)(mockNext, {
76+
logger,
77+
inputFilterSensitiveLog,
78+
})(mockArgs);
79+
80+
expect(response).toStrictEqual(mockReturn);
81+
expect(inputFilterSensitiveLog).toHaveBeenCalledTimes(1);
82+
expect(inputFilterSensitiveLog).toHaveBeenCalledWith(mockArgs.input);
83+
expect(logger.info).toHaveBeenCalledTimes(1);
84+
expect(logger.info).toHaveBeenCalledWith({ input: mockArgs.input });
85+
});
86+
87+
it("logs request if context.logger has debug function", async () => {
88+
const logger = ({ debug: jest.fn() } as unknown) as Logger;
89+
90+
const response = await serializerMiddleware(mockOptions, mockSerializer)(mockNext, { logger })(mockArgs);
91+
92+
expect(response).toStrictEqual(mockReturn);
93+
expect(logger.debug).toHaveBeenCalledTimes(1);
94+
expect(logger.debug).toHaveBeenCalledWith({ httpRequest: mockRequest });
95+
});
96+
});

0 commit comments

Comments
 (0)