diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index f1217a1e6..358f735f6 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -236,6 +236,11 @@ class Logger extends Utility implements LoggerInterface { refreshedTimes: 0, }; + /** + * Map used to store the warning messages that have already been logged. + */ + readonly #warnOnceMap = new Map(); + /** * Log level used by the current instance of Logger. * @@ -714,6 +719,17 @@ class Logger extends Utility implements LoggerInterface { this.processLogItem(LogLevelThreshold.WARN, input, extraInput); } + /** + * Log a warning message once per unique message. + * + * @param message - The log message. + */ + #warnOnce(message: string): void { + if (this.#warnOnceMap.has(message)) return; + this.#warnOnceMap.set(message, true); + this.warn(message); + } + /** * Factory method for instantiating logger instances. Used by `createChild` method. * Important for customization and subclassing. It allows subclasses, like `MyOwnLogger`, @@ -811,7 +827,7 @@ class Logger extends Utility implements LoggerInterface { this.isValidLogLevel(selectedLogLevel) && this.logLevel > LogLevelThreshold[selectedLogLevel] ) { - this.warn( + this.#warnOnce( `Current log level (${selectedLogLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level (${awsLogLevel}). This can lead to data loss, consider adjusting them.` ); } @@ -1153,7 +1169,10 @@ class Logger extends Utility implements LoggerInterface { private setInitialLogLevel(logLevel?: ConstructorOptions['logLevel']): void { const constructorLogLevel = logLevel?.toUpperCase(); - if (this.awsLogLevelShortCircuit(constructorLogLevel)) return; + if (this.awsLogLevelShortCircuit(constructorLogLevel)) { + this.#initialLogLevel = this.logLevel; + return; + } if (this.isValidLogLevel(constructorLogLevel)) { this.logLevel = LogLevelThreshold[constructorLogLevel]; @@ -1469,6 +1488,7 @@ class Logger extends Utility implements LoggerInterface { * logger.setCorrelationId('my-correlation-id'); // sets the correlation ID directly with the first argument as value * ``` * + * @example * ```typescript * import { Logger } from '@aws-lambda-powertools/logger'; * import { search } from '@aws-lambda-powertools/logger/correlationId'; @@ -1483,7 +1503,7 @@ class Logger extends Utility implements LoggerInterface { public setCorrelationId(value: unknown, correlationIdPath?: string): void { if (typeof correlationIdPath === 'string') { if (!this.#correlationIdSearchFn) { - this.warn( + this.#warnOnce( 'correlationIdPath is set but no search function was provided. The correlation ID will not be added to the log attributes.' ); return; diff --git a/packages/logger/tests/unit/injectLambdaContext.test.ts b/packages/logger/tests/unit/injectLambdaContext.test.ts index 949969e96..b3861b74a 100644 --- a/packages/logger/tests/unit/injectLambdaContext.test.ts +++ b/packages/logger/tests/unit/injectLambdaContext.test.ts @@ -297,7 +297,7 @@ describe('Inject Lambda Context', () => { expect(logger.getCorrelationId()).toBe('12345-test-id'); }); - it('warns when correlationIdPath is provided but no search function is available', async () => { + it('warns once when correlationIdPath is provided but no search function is available', async () => { // Prepare const logger = new Logger(); // No search function provided const warnSpy = vi.spyOn(logger, 'warn'); @@ -306,7 +306,7 @@ describe('Inject Lambda Context', () => { 'x-correlation-id': '12345-test-id', }, }; - // Act - Use middleware which will internally call setCorrelationIdFromPath + // Act const handler = middy(async () => { logger.info('Hello, world!'); }).use( @@ -315,12 +315,14 @@ describe('Inject Lambda Context', () => { }) ); + await handler(testEvent, context); await handler(testEvent, context); // Assess expect(warnSpy).toHaveBeenCalledWith( 'correlationIdPath is set but no search function was provided. The correlation ID will not be added to the log attributes.' ); + expect(warnSpy).toHaveBeenCalledTimes(1); }); it('does not set correlation ID when search function returns falsy value', async () => {