Skip to content

fix(logger): warn only once on ALC log level mismatch #3816

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions packages/logger/src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, boolean>();

/**
* Log level used by the current instance of Logger.
*
Expand Down Expand Up @@ -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`,
Expand Down Expand Up @@ -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.`
);
}
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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';
Expand All @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions packages/logger/tests/unit/injectLambdaContext.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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(
Expand All @@ -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 () => {
Expand Down