Skip to content

Commit 133ed3c

Browse files
authored
fix(logger): wait for decorated method return before clearing out state (#1087)
* fix: await decorated method * chore: housekeeping * chore: add unit test case
1 parent d2df854 commit 133ed3c

File tree

2 files changed

+45
-3
lines changed

2 files changed

+45
-3
lines changed

Diff for: packages/logger/src/Logger.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ class Logger extends Utility implements ClassThatLogs {
275275
* @returns {HandlerMethodDecorator}
276276
*/
277277
public injectLambdaContext(options?: HandlerOptions): HandlerMethodDecorator {
278-
return (target, _propertyKey, descriptor) => {
278+
return (_target, _propertyKey, descriptor) => {
279279
/**
280280
* The descriptor.value is the method this decorator decorates, it cannot be undefined.
281281
*/
@@ -285,7 +285,7 @@ class Logger extends Utility implements ClassThatLogs {
285285
const loggerRef = this;
286286
// Use a function() {} instead of an () => {} arrow function so that we can
287287
// access `myClass` as `this` in a decorated `myClass.myMethod()`.
288-
descriptor.value = (function (this: Handler, event, context, callback) {
288+
descriptor.value = (async function (this: Handler, event, context, callback) {
289289

290290
let initialPersistentAttributes = {};
291291
if (options && options.clearState === true) {
@@ -297,7 +297,7 @@ class Logger extends Utility implements ClassThatLogs {
297297
/* eslint-disable @typescript-eslint/no-non-null-assertion */
298298
let result: unknown;
299299
try {
300-
result = originalMethod!.apply(this, [ event, context, callback ]);
300+
result = await originalMethod!.apply(this, [ event, context, callback ]);
301301
} catch (error) {
302302
throw error;
303303
} finally {

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

+42
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,48 @@ describe('Class: Logger', () => {
11661166

11671167
});
11681168

1169+
test('when used as decorator on an async method, the method is awaited correctly', async () => {
1170+
1171+
// Prepare
1172+
const injectLambdaContextAfterOrOnErrorMock = jest.fn().mockReturnValue('worked');
1173+
// Temporarily override the cleanup static method so that we can "spy" on it.
1174+
// This method is always called after the handler has returned in the decorator
1175+
// implementation.
1176+
Logger.injectLambdaContextAfterOrOnError = injectLambdaContextAfterOrOnErrorMock;
1177+
const logger = new Logger({
1178+
logLevel: 'DEBUG',
1179+
});
1180+
const consoleSpy = jest.spyOn(logger['console'], 'info').mockImplementation();
1181+
class LambdaFunction implements LambdaInterface {
1182+
@logger.injectLambdaContext()
1183+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
1184+
// @ts-ignore
1185+
public async handler<TResult>(_event: unknown, _context: Context, _callback: Callback<TResult>): void | Promise<TResult> {
1186+
await this.dummyMethod();
1187+
logger.info('This is a DEBUG log');
1188+
1189+
return;
1190+
}
1191+
1192+
private async dummyMethod(): Promise<void> {
1193+
return;
1194+
}
1195+
}
1196+
1197+
// Act
1198+
const lambda = new LambdaFunction();
1199+
const handler = lambda.handler.bind(lambda);
1200+
await handler({}, dummyContext, () => console.log('Lambda invoked!'));
1201+
1202+
// Assess
1203+
expect(consoleSpy).toBeCalledTimes(1);
1204+
// Here we assert that the logger.info method is called before the cleanup function that should awlays
1205+
// be called after the handler has returned. If logger.info is called after it means the decorator is
1206+
// NOT awaiting the handler which would cause the test to fail.
1207+
expect(consoleSpy.mock.invocationCallOrder[0]).toBeLessThan(injectLambdaContextAfterOrOnErrorMock.mock.invocationCallOrder[0]);
1208+
1209+
});
1210+
11691211
});
11701212

11711213
describe('Method: refreshSampleRateCalculation', () => {

0 commit comments

Comments
 (0)