From d18a71d6633ead95dc2f48566702be8c11b4e47d Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Sun, 14 Aug 2022 10:51:44 +0200 Subject: [PATCH 1/7] fix: update decorator to use regular function --- packages/logger/src/Logger.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index ece63b3bd5..c55610a415 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -1,5 +1,5 @@ import { Console } from 'console'; -import type { Context } from 'aws-lambda'; +import type { Context, Handler } from 'aws-lambda'; import { Utility } from '@aws-lambda-powertools/commons'; import { LogFormatterInterface, PowertoolLogFormatter } from './formatter'; import { LogItem } from './log'; @@ -282,14 +282,18 @@ class Logger extends Utility implements ClassThatLogs { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const originalMethod = descriptor.value; - descriptor.value = (event, context, callback) => { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const loggerRef = this; + // Use a function() {} instead of an () => {} arrow function so that we can + // access `myClass` as `this` in a decorated `myClass.myMethod()`. + descriptor.value = (function (this: Handler, event, context, callback) { let initialPersistentAttributes = {}; if (options && options.clearState === true) { - initialPersistentAttributes = { ...this.getPersistentLogAttributes() }; + initialPersistentAttributes = { ...loggerRef.getPersistentLogAttributes() }; } - Logger.injectLambdaContextBefore(this, event, context, options); + Logger.injectLambdaContextBefore(loggerRef, event, context, options); /* eslint-disable @typescript-eslint/no-non-null-assertion */ let result: unknown; @@ -298,11 +302,11 @@ class Logger extends Utility implements ClassThatLogs { } catch (error) { throw error; } finally { - Logger.injectLambdaContextAfterOrOnError(this, initialPersistentAttributes, options); + Logger.injectLambdaContextAfterOrOnError(loggerRef, initialPersistentAttributes, options); } return result; - }; + }); }; } From 0fc29f0552f9c1f100dc9346bff0aa0765cef809 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Sun, 14 Aug 2022 10:52:07 +0200 Subject: [PATCH 2/7] docs: update docstrings --- packages/logger/src/Logger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index c55610a415..e01eba944a 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -99,8 +99,8 @@ import type { * } * } * - * export const myFunction = new Lambda(); - * export const handler = myFunction.handler; + * export const handlerClass = new Lambda(); + * export const handler = handlerClass.handler.bind(handlerClass); * ``` * * @class From 777b5251abe58ef106a97e80429ebdf68025d759 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Sun, 14 Aug 2022 11:03:48 +0200 Subject: [PATCH 3/7] chore: added unit test case --- packages/logger/src/Logger.ts | 3 +- packages/logger/tests/unit/Logger.test.ts | 51 +++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index e01eba944a..c98ab53749 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -279,7 +279,6 @@ class Logger extends Utility implements ClassThatLogs { /** * The descriptor.value is the method this decorator decorates, it cannot be undefined. */ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const originalMethod = descriptor.value; // eslint-disable-next-line @typescript-eslint/no-this-alias @@ -298,7 +297,7 @@ class Logger extends Utility implements ClassThatLogs { /* eslint-disable @typescript-eslint/no-non-null-assertion */ let result: unknown; try { - result = originalMethod!.apply(target, [ event, context, callback ]); + result = originalMethod!.apply(this, [ event, context, callback ]); } catch (error) { throw error; } finally { diff --git a/packages/logger/tests/unit/Logger.test.ts b/packages/logger/tests/unit/Logger.test.ts index 43cf6fc0c1..14efa09204 100644 --- a/packages/logger/tests/unit/Logger.test.ts +++ b/packages/logger/tests/unit/Logger.test.ts @@ -1115,6 +1115,57 @@ describe('Class: Logger', () => { }); + test('when used as decorator the value of `this` is preserved on the decorated method/class', async () => { + + // Prepare + const logger = new Logger({ + logLevel: 'DEBUG', + }); + const consoleSpy = jest.spyOn(logger['console'], 'info').mockImplementation(); + + class LambdaFunction implements LambdaInterface { + private readonly memberVariable: string; + + public constructor(memberVariable: string) { + this.memberVariable = memberVariable; + } + + @logger.injectLambdaContext() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public handler(_event: unknown, _context: Context, _callback: Callback): void | Promise { + this.dummyMethod(); + + return; + } + + private dummyMethod(): void { + logger.info({ message: `memberVariable:${this.memberVariable}` }); + } + } + + // Act + const lambda = new LambdaFunction('someValue'); + const handler = lambda.handler.bind(lambda); + await handler({}, dummyContext, () => console.log('Lambda invoked!')); + + // Assess + expect(consoleSpy).toBeCalledTimes(1); + expect(consoleSpy).toHaveBeenNthCalledWith(1, JSON.stringify({ + cold_start: true, + function_arn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', + function_memory_size: 128, + function_name: 'foo-bar-function', + function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', + level: 'INFO', + message: 'memberVariable:someValue', + service: 'hello-world', + timestamp: '2016-06-20T12:08:10.000Z', + xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793', + })); + + }); + }); describe('Method: refreshSampleRateCalculation', () => { From bfe1e8f98b79b879d16e50a5f29789a36ae8971b Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Sun, 14 Aug 2022 11:09:05 +0200 Subject: [PATCH 4/7] chore: updated e2e tests --- .../sampleRate.decorator.test.FunctionCode.ts | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts b/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts index bab978b23a..6d578716a2 100644 --- a/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts +++ b/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts @@ -2,27 +2,39 @@ import { Logger } from '../../src'; import { APIGatewayProxyEvent, Context } from 'aws-lambda'; import { LambdaInterface } from '@aws-lambda-powertools/commons'; -const SAMPLE_RATE = parseFloat(process.env.SAMPLE_RATE); -const LOG_MSG = process.env.LOG_MSG; +const SAMPLE_RATE = parseFloat(process.env.SAMPLE_RATE || '0.1'); +const LOG_MSG = process.env.LOG_MSG || 'Hello World'; const logger = new Logger({ sampleRateValue: SAMPLE_RATE, }); class Lambda implements LambdaInterface { + private readonly logMsg: string; + + public constructor() { + this.logMsg = LOG_MSG; + } + // Decorate your handler class method @logger.injectLambdaContext() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore public async handler(event: APIGatewayProxyEvent, context: Context): Promise<{requestId: string}> { - logger.debug(LOG_MSG); - logger.info(LOG_MSG); - logger.warn(LOG_MSG); - logger.error(LOG_MSG); - + this.dummyMethod(); + return { requestId: context.awsRequestId, }; } + + private dummyMethod() : void { + logger.debug(this.logMsg); + logger.info(this.logMsg); + logger.warn(this.logMsg); + logger.error(this.logMsg); + } } export const myFunction = new Lambda(); -export const handler = myFunction.handler; \ No newline at end of file +export const handler = myFunction.handler.bind(myFunction); \ No newline at end of file From 95a6bb646cc5f56eececd9fe64db0a5c5919b45c Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Sun, 14 Aug 2022 11:09:17 +0200 Subject: [PATCH 5/7] chore: updated docs --- docs/core/logger.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/core/logger.md b/docs/core/logger.md index 6883accf92..9e05e09590 100644 --- a/docs/core/logger.md +++ b/docs/core/logger.md @@ -156,8 +156,11 @@ Key | Example } export const myFunction = new Lambda(); - export const handler = myFunction.handler; + export const handler = myFunction.handler.bind(myFunction); // (1) ``` + + 1. Binding your handler method allows your handler to access `this` within the class methods. + === "Manual" ```typescript hl_lines="7" @@ -234,9 +237,11 @@ This is disabled by default to prevent sensitive info being logged } export const myFunction = new Lambda(); - export const handler = myFunction.handler; + export const handler = myFunction.handler.bind(myFunction); // (1) ``` + 1. Binding your handler method allows your handler to access `this` within the class methods. + ### Appending persistent additional log keys and values You can append additional persistent keys and values in the logs generated during a Lambda invocation using either mechanism: @@ -399,9 +404,11 @@ If you want to make sure that persistent attributes added **inside the handler f } export const myFunction = new Lambda(); - export const handler = myFunction.handler; + export const handler = myFunction.handler.bind(myFunction); // (1) ``` + 1. Binding your handler method allows your handler to access `this` within the class methods. + In each case, the printed log will look like this: === "First invocation" From de8478139a292fe72b64f5e7ec278ab0af89ccef Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 16 Aug 2022 16:16:41 +0200 Subject: [PATCH 6/7] chore: update method name in e2e tests --- .../tests/e2e/sampleRate.decorator.test.FunctionCode.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts b/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts index 6d578716a2..2a8b1aac76 100644 --- a/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts +++ b/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts @@ -21,14 +21,14 @@ class Lambda implements LambdaInterface { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore public async handler(event: APIGatewayProxyEvent, context: Context): Promise<{requestId: string}> { - this.dummyMethod(); + this.printLogInAllLevels(); return { requestId: context.awsRequestId, }; } - private dummyMethod() : void { + private printLogInAllLevels() : void { logger.debug(this.logMsg); logger.info(this.logMsg); logger.warn(this.logMsg); From 9052913263bbfa8d6e6518081c5ae395b13783e8 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 17 Aug 2022 15:01:58 +0200 Subject: [PATCH 7/7] chore: removed extra export in docs/examples --- docs/core/logger.md | 6 +++--- packages/logger/src/Logger.ts | 2 +- .../tests/e2e/sampleRate.decorator.test.FunctionCode.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/core/logger.md b/docs/core/logger.md index 9e05e09590..b304ba69a8 100644 --- a/docs/core/logger.md +++ b/docs/core/logger.md @@ -155,7 +155,7 @@ Key | Example } - export const myFunction = new Lambda(); + const myFunction = new Lambda(); export const handler = myFunction.handler.bind(myFunction); // (1) ``` @@ -236,7 +236,7 @@ This is disabled by default to prevent sensitive info being logged } - export const myFunction = new Lambda(); + const myFunction = new Lambda(); export const handler = myFunction.handler.bind(myFunction); // (1) ``` @@ -403,7 +403,7 @@ If you want to make sure that persistent attributes added **inside the handler f } - export const myFunction = new Lambda(); + const myFunction = new Lambda(); export const handler = myFunction.handler.bind(myFunction); // (1) ``` diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index c98ab53749..2bbdb3fcc1 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -99,7 +99,7 @@ import type { * } * } * - * export const handlerClass = new Lambda(); + * const handlerClass = new Lambda(); * export const handler = handlerClass.handler.bind(handlerClass); * ``` * diff --git a/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts b/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts index 2a8b1aac76..72c13f07b4 100644 --- a/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts +++ b/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts @@ -36,5 +36,5 @@ class Lambda implements LambdaInterface { } } -export const myFunction = new Lambda(); +const myFunction = new Lambda(); export const handler = myFunction.handler.bind(myFunction); \ No newline at end of file