Skip to content

Commit 6b32304

Browse files
authored
fix(logger): middleware stores initial persistent attributes correctly (#1329)
1 parent 907a1f0 commit 6b32304

File tree

3 files changed

+123
-15
lines changed

3 files changed

+123
-15
lines changed

Diff for: packages/logger/src/middleware/middy.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ const injectLambdaContext = (target: Logger | Logger[], options?: HandlerOptions
3535
const persistentAttributes: LogAttributes[] = [];
3636

3737
const injectLambdaContextBefore = async (request: MiddyLikeRequest): Promise<void> => {
38-
loggers.forEach((logger: Logger) => {
38+
loggers.forEach((logger: Logger, index: number) => {
3939
if (options && options.clearState === true) {
40-
persistentAttributes.push({ ...logger.getPersistentLogAttributes() });
40+
persistentAttributes[index] = ({ ...logger.getPersistentLogAttributes() });
4141
}
4242
Logger.injectLambdaContextBefore(logger, request.event, request.context, options);
4343
});

Diff for: packages/logger/tests/unit/Logger.test.ts

+72-13
Original file line numberDiff line numberDiff line change
@@ -1235,11 +1235,10 @@ describe('Class: Logger', () => {
12351235
test('it awaits the decorated method correctly', async () => {
12361236

12371237
// Prepare
1238-
const injectLambdaContextAfterOrOnErrorMock = jest.fn().mockReturnValue('worked');
1239-
// Temporarily override the cleanup static method so that we can "spy" on it.
1240-
// This method is always called after the handler has returned in the decorator
1241-
// implementation.
1242-
Logger.injectLambdaContextAfterOrOnError = injectLambdaContextAfterOrOnErrorMock;
1238+
const injectLambdaContextAfterOrOnErrorSpy = jest.spyOn(
1239+
Logger,
1240+
'injectLambdaContextAfterOrOnError'
1241+
);
12431242
const logger = new Logger({
12441243
logLevel: 'DEBUG',
12451244
});
@@ -1248,7 +1247,7 @@ describe('Class: Logger', () => {
12481247
@logger.injectLambdaContext()
12491248
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
12501249
// @ts-ignore
1251-
public async handler<TResult>(_event: unknown, _context: Context, _callback: Callback<TResult>): void | Promise<TResult> {
1250+
public async handler(_event: unknown, _context: unknown): Promise<unknown> {
12521251
await this.dummyMethod();
12531252
logger.info('This is a DEBUG log');
12541253

@@ -1259,18 +1258,78 @@ describe('Class: Logger', () => {
12591258
return;
12601259
}
12611260
}
1262-
1263-
// Act
12641261
const lambda = new LambdaFunction();
12651262
const handler = lambda.handler.bind(lambda);
1266-
await handler({}, context, () => console.log('Lambda invoked!'));
1263+
1264+
// Act
1265+
await handler({}, context);
12671266

12681267
// Assess
12691268
expect(consoleSpy).toBeCalledTimes(1);
1270-
// Here we assert that the logger.info method is called before the cleanup function that should awlays
1271-
// be called after the handler has returned. If logger.info is called after it means the decorator is
1272-
// NOT awaiting the handler which would cause the test to fail.
1273-
expect(consoleSpy.mock.invocationCallOrder[0]).toBeLessThan(injectLambdaContextAfterOrOnErrorMock.mock.invocationCallOrder[0]);
1269+
// Here we assert that the logger.info method is called before the cleanup function that should always
1270+
// be called ONLY after the handler has returned. If logger.info is called after the cleanup function
1271+
// it means the decorator is NOT awaiting the handler which would cause the test to fail.
1272+
expect(consoleSpy.mock.invocationCallOrder[0])
1273+
.toBeLessThan(injectLambdaContextAfterOrOnErrorSpy.mock.invocationCallOrder[0]);
1274+
1275+
});
1276+
1277+
test('when logEvent and clearState are both TRUE, and the logger has persistent attributes, any key added in the handler is cleared properly', async () => {
1278+
1279+
// Prepare
1280+
const logger = new Logger({
1281+
persistentLogAttributes: {
1282+
version: '1.0.0',
1283+
}
1284+
});
1285+
const consoleSpy = jest.spyOn(logger['console'], 'info').mockImplementation();
1286+
class LambdaFunction implements LambdaInterface {
1287+
@logger.injectLambdaContext({ clearState: true, logEvent: true })
1288+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
1289+
// @ts-ignore
1290+
public async handler(event: { foo: string }, _context: unknown): Promise<unknown> {
1291+
logger.appendKeys({ foo: event.foo });
1292+
1293+
return;
1294+
}
1295+
}
1296+
const lambda = new LambdaFunction();
1297+
const handler = lambda.handler.bind(lambda);
1298+
1299+
// Act
1300+
await handler({ foo: 'bar' }, {} as Context);
1301+
await handler({ foo: 'baz' }, {} as Context);
1302+
await handler({ foo: 'biz' }, {} as Context);
1303+
await handler({ foo: 'buz' }, {} as Context);
1304+
await handler({ foo: 'boz' }, {} as Context);
1305+
1306+
expect(consoleSpy).toBeCalledTimes(5);
1307+
for (let i = 1; i === 5; i++) {
1308+
expect(consoleSpy).toHaveBeenNthCalledWith(
1309+
i,
1310+
expect.stringContaining('\"message\":\"Lambda invocation event\"'),
1311+
);
1312+
expect(consoleSpy).toHaveBeenNthCalledWith(
1313+
i,
1314+
expect.stringContaining('\"version\":\"1.0.0\"'),
1315+
);
1316+
}
1317+
expect(consoleSpy).toHaveBeenNthCalledWith(
1318+
2,
1319+
expect.not.stringContaining('\"foo\":\"bar\"')
1320+
);
1321+
expect(consoleSpy).toHaveBeenNthCalledWith(
1322+
3,
1323+
expect.not.stringContaining('\"foo\":\"baz\"')
1324+
);
1325+
expect(consoleSpy).toHaveBeenNthCalledWith(
1326+
4,
1327+
expect.not.stringContaining('\"foo\":\"biz\"')
1328+
);
1329+
expect(consoleSpy).toHaveBeenNthCalledWith(
1330+
5,
1331+
expect.not.stringContaining('\"foo\":\"buz\"')
1332+
);
12741333

12751334
});
12761335

Diff for: packages/logger/tests/unit/middleware/middy.test.ts

+49
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Logger } from './../../../src';
1111
import middy from '@middy/core';
1212
import { PowertoolLogFormatter } from '../../../src/formatter';
1313
import { Console } from 'console';
14+
import { Context } from 'aws-lambda';
1415

1516
const mockDate = new Date(1466424490000);
1617
const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
@@ -354,6 +355,54 @@ describe('Middy middleware', () => {
354355
}));
355356
});
356357

358+
test('when logEvent and clearState are both TRUE, and the logger has persistent attributes, any key added in the handler is cleared properly', async () => {
359+
360+
const logger = new Logger({
361+
persistentLogAttributes: {
362+
version: '1.0.0',
363+
}
364+
});
365+
const consoleSpy = jest.spyOn(logger['console'], 'info').mockImplementation();
366+
const handler = middy(async (event: { foo: string }, _context: Context) => {
367+
logger.appendKeys({ foo: event.foo });
368+
}).use(injectLambdaContext(logger, { logEvent: true, clearState: true }));
369+
370+
await handler({ foo: 'bar' }, {} as Context);
371+
await handler({ foo: 'baz' }, {} as Context);
372+
await handler({ foo: 'biz' }, {} as Context);
373+
await handler({ foo: 'buz' }, {} as Context);
374+
await handler({ foo: 'boz' }, {} as Context);
375+
376+
expect(consoleSpy).toBeCalledTimes(5);
377+
for (let i = 1; i === 5; i++) {
378+
expect(consoleSpy).toHaveBeenNthCalledWith(
379+
i,
380+
expect.stringContaining('\"message\":\"Lambda invocation event\"'),
381+
);
382+
expect(consoleSpy).toHaveBeenNthCalledWith(
383+
i,
384+
expect.stringContaining('\"version\":\"1.0.0\"'),
385+
);
386+
}
387+
expect(consoleSpy).toHaveBeenNthCalledWith(
388+
2,
389+
expect.not.stringContaining('\"foo\":\"bar\"')
390+
);
391+
expect(consoleSpy).toHaveBeenNthCalledWith(
392+
3,
393+
expect.not.stringContaining('\"foo\":\"baz\"')
394+
);
395+
expect(consoleSpy).toHaveBeenNthCalledWith(
396+
4,
397+
expect.not.stringContaining('\"foo\":\"biz\"')
398+
);
399+
expect(consoleSpy).toHaveBeenNthCalledWith(
400+
5,
401+
expect.not.stringContaining('\"foo\":\"buz\"')
402+
);
403+
404+
});
405+
357406
});
358407

359408
});

0 commit comments

Comments
 (0)