From 7293cc14fdcfd93681cc8f7bf5496f42bcd6b92e Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Mon, 11 Nov 2024 08:21:11 +0100 Subject: [PATCH 01/10] fix(logger): prevent overwriting standard keys --- packages/logger/src/Logger.ts | 51 ++++++++-- packages/logger/src/constants.ts | 16 ++- packages/logger/src/formatter/LogItem.ts | 6 +- packages/logger/tests/unit/repro.test.ts | 119 +++++++++++++++++++++++ 4 files changed, 179 insertions(+), 13 deletions(-) create mode 100644 packages/logger/tests/unit/repro.test.ts diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index 69e84184c2..731fde81e1 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -9,7 +9,7 @@ import type { import type { Context, Handler } from 'aws-lambda'; import merge from 'lodash.merge'; import { EnvironmentVariablesService } from './config/EnvironmentVariablesService.js'; -import { LogJsonIndent, LogLevelThreshold } from './constants.js'; +import { LogJsonIndent, LogLevelThreshold, ReservedKeys } from './constants.js'; import type { LogFormatter } from './formatter/LogFormatter.js'; import type { LogItem } from './formatter/LogItem.js'; import { PowertoolsLogFormatter } from './formatter/PowertoolsLogFormatter.js'; @@ -221,10 +221,7 @@ class Logger extends Utility implements LoggerInterface { * @param attributes - The attributes to add to all log items. */ public appendKeys(attributes: LogAttributes): void { - for (const attributeKey of Object.keys(attributes)) { - this.#keys.set(attributeKey, 'temp'); - } - merge(this.temporaryLogAttributes, attributes); + this.#appendAnyKeys(attributes, 'temp'); } /** @@ -233,10 +230,7 @@ class Logger extends Utility implements LoggerInterface { * @param attributes - The attributes to add to all log items. */ public appendPersistentKeys(attributes: LogAttributes): void { - for (const attributeKey of Object.keys(attributes)) { - this.#keys.set(attributeKey, 'persistent'); - } - merge(this.persistentLogAttributes, attributes); + this.#appendAnyKeys(attributes, 'persistent'); } /** @@ -293,6 +287,13 @@ class Logger extends Utility implements LoggerInterface { * @param extraInput - The extra input to log. */ public debug(input: LogItemMessage, ...extraInput: LogItemExtraInput): void { + for (const extra of extraInput) { + if (!(extra instanceof Error) && !(typeof extra === 'string')) { + for (const key of Object.keys(extra)) { + this.#checkReservedKeyAndWarn(key); + } + } + } this.processLogItem(LogLevelThreshold.DEBUG, input, extraInput); } @@ -666,6 +667,25 @@ class Logger extends Utility implements LoggerInterface { merge(this.powertoolsLogData, attributes); } + /** + * Shared logic for adding keys to the logger instance. + * + * @param attributes - The attributes to add to the log item. + * @param type - The type of the attributes to add. + */ + #appendAnyKeys(attributes: LogAttributes, type: 'temp' | 'persistent'): void { + for (const attributeKey of Object.keys(attributes)) { + if (this.#checkReservedKeyAndWarn(attributeKey) === false) { + this.#keys.set(attributeKey, 'temp'); + } + } + if (type === 'temp') { + merge(this.temporaryLogAttributes, attributes); + } else { + merge(this.persistentLogAttributes, attributes); + } + } + private awsLogLevelShortCircuit(selectedLogLevel?: string): boolean { const awsLogLevel = this.getEnvVarsService().getAwsLogLevel(); if (this.isValidLogLevel(awsLogLevel)) { @@ -754,6 +774,19 @@ class Logger extends Utility implements LoggerInterface { ); } + /** + * Check if a given key is reserved and warn the user if it is. + * + * @param key - The key to check + */ + #checkReservedKeyAndWarn(key: string): boolean { + if (ReservedKeys.includes(key)) { + this.warn(`The key "${key}" is a reserved key and will be dropped.`); + return true; + } + return false; + } + /** * Get the custom config service, an abstraction used to fetch environment variables. */ diff --git a/packages/logger/src/constants.ts b/packages/logger/src/constants.ts index 9499687c56..307b6f254c 100644 --- a/packages/logger/src/constants.ts +++ b/packages/logger/src/constants.ts @@ -37,4 +37,18 @@ const LogLevelThreshold = { SILENT: 28, } as const; -export { LogJsonIndent, LogLevel, LogLevelThreshold }; +/** + * Reserved keys that are included in every log item when using the default log formatter. + * + * These keys are reserved and cannot be overwritten by custom log attributes. + */ +const ReservedKeys = [ + 'level', + 'message', + 'sampling_rate', + 'service', + 'timestamp', + 'xray_trace_id', +]; + +export { LogJsonIndent, LogLevel, LogLevelThreshold, ReservedKeys }; diff --git a/packages/logger/src/formatter/LogItem.ts b/packages/logger/src/formatter/LogItem.ts index 90516aae4f..fc7a27915f 100644 --- a/packages/logger/src/formatter/LogItem.ts +++ b/packages/logger/src/formatter/LogItem.ts @@ -23,7 +23,7 @@ class LogItem { * @param params - The parameters for the LogItem. */ public constructor(params: { attributes: LogAttributes }) { - this.addAttributes(params.attributes); + this.attributes = params.attributes; } /** @@ -32,7 +32,7 @@ class LogItem { * @param attributes - The attributes to add to the log item. */ public addAttributes(attributes: LogAttributes): this { - merge(this.attributes, attributes); + merge(attributes, this.attributes); return this; } @@ -50,7 +50,7 @@ class LogItem { * This operation removes empty keys from the log item, see {@link removeEmptyKeys | removeEmptyKeys()} for more information. */ public prepareForPrint(): void { - this.setAttributes(this.removeEmptyKeys(this.getAttributes())); + this.attributes = this.removeEmptyKeys(this.getAttributes()); } /** diff --git a/packages/logger/tests/unit/repro.test.ts b/packages/logger/tests/unit/repro.test.ts new file mode 100644 index 0000000000..f83dafa2aa --- /dev/null +++ b/packages/logger/tests/unit/repro.test.ts @@ -0,0 +1,119 @@ +import { expect, it, vi } from 'vitest'; +import { Logger } from '../../src/Logger.js'; + +vi.hoisted(() => { + process.env.POWERTOOLS_DEV = 'true'; +}); + +it('does not overwrite via appendKeys', () => { + const logger = new Logger({ + logLevel: 'DEBUG', + }); + const debugSpy = vi.spyOn(console, 'debug'); + const warnSpy = vi.spyOn(console, 'warn'); + + logger.appendKeys({ + level: 'Hello, World!', + }); + + logger.debug('stuff'); + + const log = JSON.parse(debugSpy.mock.calls[0][0]); + expect(log).toStrictEqual( + expect.objectContaining({ + level: 'DEBUG', + }) + ); + expect(warnSpy).toHaveBeenCalledTimes(1); + const warn = JSON.parse(warnSpy.mock.calls[0][0]); + expect(warn).toStrictEqual( + expect.objectContaining({ + message: 'The key "level" is a reserved key and will be dropped.', + }) + ); +}); + +it('does not overwrite via appendPersistentKeys', () => { + const logger = new Logger({ + logLevel: 'DEBUG', + }); + const debugSpy = vi.spyOn(console, 'debug'); + const warnSpy = vi.spyOn(console, 'warn'); + + logger.appendPersistentKeys({ + level: 'Hello, World!', + }); + + logger.debug('stuff'); + + const log = JSON.parse(debugSpy.mock.calls[0][0]); + expect(log).toStrictEqual( + expect.objectContaining({ + level: 'DEBUG', + }) + ); + expect(warnSpy).toHaveBeenCalledTimes(1); + const warn = JSON.parse(warnSpy.mock.calls[0][0]); + expect(warn).toStrictEqual( + expect.objectContaining({ + message: 'The key "level" is a reserved key and will be dropped.', + }) + ); +}); + +it('does not overwrite via constructor keys', () => { + const debugSpy = vi.spyOn(console, 'debug'); + const warnSpy = vi.spyOn(console, 'warn'); + const logger = new Logger({ + logLevel: 'DEBUG', + persistentKeys: { + level: 'Hello, World!', + }, + }); + + logger.debug('stuff'); + + const log = JSON.parse(debugSpy.mock.calls[0][0]); + expect(log).toStrictEqual( + expect.objectContaining({ + level: 'DEBUG', + }) + ); + expect(warnSpy).toHaveBeenCalledTimes(1); + const warn = JSON.parse(warnSpy.mock.calls[0][0]); + expect(warn).toStrictEqual( + expect.objectContaining({ + message: 'The key "level" is a reserved key and will be dropped.', + }) + ); +}); + +it('does not overwrite via extra keys', () => { + const debugSpy = vi.spyOn(console, 'debug'); + const warnSpy = vi.spyOn(console, 'warn'); + const logger = new Logger({ + logLevel: 'DEBUG', + }); + + logger.debug('stuff', { + level: 'Hello, World!', + timestamp: 'foo', + message: 'bar', + }); + + const log = JSON.parse(debugSpy.mock.calls[0][0]); + expect(log).toStrictEqual( + expect.objectContaining({ + level: 'DEBUG', + timestamp: expect.not.stringMatching('foo'), + message: 'stuff', + }) + ); + expect(warnSpy).toHaveBeenCalledTimes(3); + const warn = JSON.parse(warnSpy.mock.calls[0][0]); + expect(warn).toStrictEqual( + expect.objectContaining({ + message: 'The key "level" is a reserved key and will be dropped.', + }) + ); +}); From e2449d0b13372eec229fa355613cd8a5cc2a042d Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 4 Feb 2025 10:10:58 +0100 Subject: [PATCH 02/10] fix(logger): prevent overwriting service keys --- packages/logger/src/Logger.ts | 31 +++++++----- packages/logger/src/formatter/LogItem.ts | 2 +- packages/logger/tests/unit/repro.test.ts | 62 +++++++++++++++++++++++- 3 files changed, 80 insertions(+), 15 deletions(-) diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index 731fde81e1..e928ec4f5d 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -221,7 +221,7 @@ class Logger extends Utility implements LoggerInterface { * @param attributes - The attributes to add to all log items. */ public appendKeys(attributes: LogAttributes): void { - this.#appendAnyKeys(attributes, 'temp'); + this.#appendKeys(attributes, 'temp'); } /** @@ -230,7 +230,7 @@ class Logger extends Utility implements LoggerInterface { * @param attributes - The attributes to add to all log items. */ public appendPersistentKeys(attributes: LogAttributes): void { - this.#appendAnyKeys(attributes, 'persistent'); + this.#appendKeys(attributes, 'persistent'); } /** @@ -287,13 +287,6 @@ class Logger extends Utility implements LoggerInterface { * @param extraInput - The extra input to log. */ public debug(input: LogItemMessage, ...extraInput: LogItemExtraInput): void { - for (const extra of extraInput) { - if (!(extra instanceof Error) && !(typeof extra === 'string')) { - for (const key of Object.keys(extra)) { - this.#checkReservedKeyAndWarn(key); - } - } - } this.processLogItem(LogLevelThreshold.DEBUG, input, extraInput); } @@ -673,10 +666,10 @@ class Logger extends Utility implements LoggerInterface { * @param attributes - The attributes to add to the log item. * @param type - The type of the attributes to add. */ - #appendAnyKeys(attributes: LogAttributes, type: 'temp' | 'persistent'): void { + #appendKeys(attributes: LogAttributes, type: 'temp' | 'persistent'): void { for (const attributeKey of Object.keys(attributes)) { if (this.#checkReservedKeyAndWarn(attributeKey) === false) { - this.#keys.set(attributeKey, 'temp'); + this.#keys.set(attributeKey, type); } } if (type === 'temp') { @@ -732,6 +725,11 @@ class Logger extends Utility implements LoggerInterface { } else { const { message: inputMessage, ...rest } = input; message = inputMessage; + for (const key of Object.keys(rest)) { + if (this.#checkReservedKeyAndWarn(key)) { + delete rest[key]; + } + } otherInput = rest; } @@ -758,6 +756,13 @@ class Logger extends Utility implements LoggerInterface { merge(additionalAttributes, otherInput); // then we merge the extra input attributes (if any) for (const item of extraInput) { + if (!(item instanceof Error) && !(typeof item === 'string')) { + for (const key of Object.keys(item)) { + if (this.#checkReservedKeyAndWarn(key)) { + delete item[key]; + } + } + } const attributes: LogAttributes = item instanceof Error ? { error: item } @@ -1123,7 +1128,7 @@ class Logger extends Utility implements LoggerInterface { private setPowertoolsLogData( serviceName?: ConstructorOptions['serviceName'], environment?: ConstructorOptions['environment'], - persistentKeys: ConstructorOptions['persistentKeys'] = {} + persistentKeys?: ConstructorOptions['persistentKeys'] ): void { this.addToPowertoolsLogData({ awsRegion: this.getEnvVarsService().getAwsRegion(), @@ -1137,7 +1142,7 @@ class Logger extends Utility implements LoggerInterface { this.getEnvVarsService().getServiceName() || this.getDefaultServiceName(), }); - this.appendPersistentKeys(persistentKeys); + persistentKeys && this.appendPersistentKeys(persistentKeys); } } diff --git a/packages/logger/src/formatter/LogItem.ts b/packages/logger/src/formatter/LogItem.ts index fc7a27915f..68424b985f 100644 --- a/packages/logger/src/formatter/LogItem.ts +++ b/packages/logger/src/formatter/LogItem.ts @@ -32,7 +32,7 @@ class LogItem { * @param attributes - The attributes to add to the log item. */ public addAttributes(attributes: LogAttributes): this { - merge(attributes, this.attributes); + merge(this.attributes, attributes); return this; } diff --git a/packages/logger/tests/unit/repro.test.ts b/packages/logger/tests/unit/repro.test.ts index f83dafa2aa..b82db19d0a 100644 --- a/packages/logger/tests/unit/repro.test.ts +++ b/packages/logger/tests/unit/repro.test.ts @@ -88,7 +88,7 @@ it('does not overwrite via constructor keys', () => { ); }); -it('does not overwrite via extra keys', () => { +it('does not overwrite via extra keys debug', () => { const debugSpy = vi.spyOn(console, 'debug'); const warnSpy = vi.spyOn(console, 'warn'); const logger = new Logger({ @@ -117,3 +117,63 @@ it('does not overwrite via extra keys', () => { }) ); }); + +it('does not overwrite via main', () => { + const debugSpy = vi.spyOn(console, 'debug'); + const warnSpy = vi.spyOn(console, 'warn'); + const logger = new Logger({ + logLevel: 'DEBUG', + }); + + logger.debug({ + level: 'Hello, World!', + timestamp: 'foo', + message: 'bar', + }); + + const log = JSON.parse(debugSpy.mock.calls[0][0]); + expect(log).toStrictEqual( + expect.objectContaining({ + level: 'DEBUG', + timestamp: expect.not.stringMatching('foo'), + message: 'bar', + }) + ); + expect(warnSpy).toHaveBeenCalledTimes(2); + const warn = JSON.parse(warnSpy.mock.calls[0][0]); + expect(warn).toStrictEqual( + expect.objectContaining({ + message: 'The key "level" is a reserved key and will be dropped.', + }) + ); +}); + +it('does not overwrite via extra keys info', () => { + const logSpy = vi.spyOn(console, 'info'); + const warnSpy = vi.spyOn(console, 'warn'); + const logger = new Logger({ + logLevel: 'DEBUG', + }); + + logger.info('stuff', { + level: 'Hello, World!', + timestamp: 'foo', + message: 'bar', + }); + + const log = JSON.parse(logSpy.mock.calls[0][0]); + expect(log).toStrictEqual( + expect.objectContaining({ + level: 'INFO', + timestamp: expect.not.stringMatching('foo'), + message: 'stuff', + }) + ); + expect(warnSpy).toHaveBeenCalledTimes(3); + const warn = JSON.parse(warnSpy.mock.calls[0][0]); + expect(warn).toStrictEqual( + expect.objectContaining({ + message: 'The key "level" is a reserved key and will be dropped.', + }) + ); +}); From 52e270de7586dd28080f3488910b47709d665bed Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 4 Feb 2025 10:27:07 +0100 Subject: [PATCH 03/10] fix(logger): simplify log creation logic --- packages/logger/src/Logger.ts | 75 +++++++++++++++++------------------ 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index e928ec4f5d..df49fd9a72 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -25,7 +25,10 @@ import type { LogLevel, LoggerInterface, } from './types/Logger.js'; -import type { PowertoolsLogData } from './types/logKeys.js'; +import type { + PowertoolsLogData, + UnformattedAttributes, +} from './types/logKeys.js'; /** * The Logger utility provides an opinionated logger with output structured as JSON for AWS Lambda. @@ -718,59 +721,53 @@ class Logger extends Utility implements LoggerInterface { input: LogItemMessage, extraInput: LogItemExtraInput ): LogItem { - let message = ''; - let otherInput: { [key: string]: unknown } = {}; - if (typeof input === 'string') { - message = input; - } else { - const { message: inputMessage, ...rest } = input; - message = inputMessage; - for (const key of Object.keys(rest)) { - if (this.#checkReservedKeyAndWarn(key)) { - delete rest[key]; - } - } - otherInput = rest; - } - - // create base attributes - const unformattedBaseAttributes = { + const unformattedBaseAttributes: UnformattedAttributes = { logLevel: this.getLogLevelNameFromNumber(logLevel), timestamp: new Date(), - message, xRayTraceId: this.envVarsService.getXrayTraceId(), ...this.getPowertoolsLogData(), + message: '', }; const additionalAttributes: LogAttributes = {}; - // gradually add additional attributes picking only the last added for each key + // Add additional attributes from persistent and temporary keys for (const [key, type] of this.#keys) { - if (type === 'persistent') { - additionalAttributes[key] = this.persistentLogAttributes[key]; - } else { - additionalAttributes[key] = this.temporaryLogAttributes[key]; + if (!this.#checkReservedKeyAndWarn(key)) { + additionalAttributes[key] = + type === 'persistent' + ? this.persistentLogAttributes[key] + : this.temporaryLogAttributes[key]; } } - // if the main input is not a string, then it's an object with additional attributes, so we merge it - merge(additionalAttributes, otherInput); - // then we merge the extra input attributes (if any) + // Handle input message + if (typeof input === 'string') { + unformattedBaseAttributes.message = input; + } else { + const { message, ...rest } = input; + unformattedBaseAttributes.message = message; + + // Add remaining input properties if they're not reserved + for (const [key, value] of Object.entries(rest)) { + if (!this.#checkReservedKeyAndWarn(key)) { + additionalAttributes[key] = value; + } + } + } + + // Handle extra input attributes for (const item of extraInput) { - if (!(item instanceof Error) && !(typeof item === 'string')) { - for (const key of Object.keys(item)) { - if (this.#checkReservedKeyAndWarn(key)) { - delete item[key]; + if (item instanceof Error) { + additionalAttributes.error = item; + } else if (typeof item === 'string') { + additionalAttributes.extra = item; + } else { + for (const [key, value] of Object.entries(item)) { + if (!this.#checkReservedKeyAndWarn(key)) { + additionalAttributes[key] = value; } } } - const attributes: LogAttributes = - item instanceof Error - ? { error: item } - : typeof item === 'string' - ? { extra: item } - : item; - - merge(additionalAttributes, attributes); } return this.getLogFormatter().formatAttributes( From 73261f1a5a86b8af1c1b60ebfad7f65d5f84e9cf Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 4 Feb 2025 11:19:38 +0100 Subject: [PATCH 04/10] chore: coverage --- packages/logger/src/formatter/LogItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/logger/src/formatter/LogItem.ts b/packages/logger/src/formatter/LogItem.ts index 68424b985f..3e5587bc09 100644 --- a/packages/logger/src/formatter/LogItem.ts +++ b/packages/logger/src/formatter/LogItem.ts @@ -23,7 +23,7 @@ class LogItem { * @param params - The parameters for the LogItem. */ public constructor(params: { attributes: LogAttributes }) { - this.attributes = params.attributes; + this.setAttributes(params.attributes); } /** From 7fef6578ef8ef42ef0377387dc7bef0faa235173 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 4 Feb 2025 11:27:08 +0100 Subject: [PATCH 05/10] tests: add unit tests --- packages/logger/tests/unit/repro.test.ts | 179 ------------------ .../logger/tests/unit/workingWithkeys.test.ts | 115 +++++++++++ 2 files changed, 115 insertions(+), 179 deletions(-) delete mode 100644 packages/logger/tests/unit/repro.test.ts diff --git a/packages/logger/tests/unit/repro.test.ts b/packages/logger/tests/unit/repro.test.ts deleted file mode 100644 index b82db19d0a..0000000000 --- a/packages/logger/tests/unit/repro.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { expect, it, vi } from 'vitest'; -import { Logger } from '../../src/Logger.js'; - -vi.hoisted(() => { - process.env.POWERTOOLS_DEV = 'true'; -}); - -it('does not overwrite via appendKeys', () => { - const logger = new Logger({ - logLevel: 'DEBUG', - }); - const debugSpy = vi.spyOn(console, 'debug'); - const warnSpy = vi.spyOn(console, 'warn'); - - logger.appendKeys({ - level: 'Hello, World!', - }); - - logger.debug('stuff'); - - const log = JSON.parse(debugSpy.mock.calls[0][0]); - expect(log).toStrictEqual( - expect.objectContaining({ - level: 'DEBUG', - }) - ); - expect(warnSpy).toHaveBeenCalledTimes(1); - const warn = JSON.parse(warnSpy.mock.calls[0][0]); - expect(warn).toStrictEqual( - expect.objectContaining({ - message: 'The key "level" is a reserved key and will be dropped.', - }) - ); -}); - -it('does not overwrite via appendPersistentKeys', () => { - const logger = new Logger({ - logLevel: 'DEBUG', - }); - const debugSpy = vi.spyOn(console, 'debug'); - const warnSpy = vi.spyOn(console, 'warn'); - - logger.appendPersistentKeys({ - level: 'Hello, World!', - }); - - logger.debug('stuff'); - - const log = JSON.parse(debugSpy.mock.calls[0][0]); - expect(log).toStrictEqual( - expect.objectContaining({ - level: 'DEBUG', - }) - ); - expect(warnSpy).toHaveBeenCalledTimes(1); - const warn = JSON.parse(warnSpy.mock.calls[0][0]); - expect(warn).toStrictEqual( - expect.objectContaining({ - message: 'The key "level" is a reserved key and will be dropped.', - }) - ); -}); - -it('does not overwrite via constructor keys', () => { - const debugSpy = vi.spyOn(console, 'debug'); - const warnSpy = vi.spyOn(console, 'warn'); - const logger = new Logger({ - logLevel: 'DEBUG', - persistentKeys: { - level: 'Hello, World!', - }, - }); - - logger.debug('stuff'); - - const log = JSON.parse(debugSpy.mock.calls[0][0]); - expect(log).toStrictEqual( - expect.objectContaining({ - level: 'DEBUG', - }) - ); - expect(warnSpy).toHaveBeenCalledTimes(1); - const warn = JSON.parse(warnSpy.mock.calls[0][0]); - expect(warn).toStrictEqual( - expect.objectContaining({ - message: 'The key "level" is a reserved key and will be dropped.', - }) - ); -}); - -it('does not overwrite via extra keys debug', () => { - const debugSpy = vi.spyOn(console, 'debug'); - const warnSpy = vi.spyOn(console, 'warn'); - const logger = new Logger({ - logLevel: 'DEBUG', - }); - - logger.debug('stuff', { - level: 'Hello, World!', - timestamp: 'foo', - message: 'bar', - }); - - const log = JSON.parse(debugSpy.mock.calls[0][0]); - expect(log).toStrictEqual( - expect.objectContaining({ - level: 'DEBUG', - timestamp: expect.not.stringMatching('foo'), - message: 'stuff', - }) - ); - expect(warnSpy).toHaveBeenCalledTimes(3); - const warn = JSON.parse(warnSpy.mock.calls[0][0]); - expect(warn).toStrictEqual( - expect.objectContaining({ - message: 'The key "level" is a reserved key and will be dropped.', - }) - ); -}); - -it('does not overwrite via main', () => { - const debugSpy = vi.spyOn(console, 'debug'); - const warnSpy = vi.spyOn(console, 'warn'); - const logger = new Logger({ - logLevel: 'DEBUG', - }); - - logger.debug({ - level: 'Hello, World!', - timestamp: 'foo', - message: 'bar', - }); - - const log = JSON.parse(debugSpy.mock.calls[0][0]); - expect(log).toStrictEqual( - expect.objectContaining({ - level: 'DEBUG', - timestamp: expect.not.stringMatching('foo'), - message: 'bar', - }) - ); - expect(warnSpy).toHaveBeenCalledTimes(2); - const warn = JSON.parse(warnSpy.mock.calls[0][0]); - expect(warn).toStrictEqual( - expect.objectContaining({ - message: 'The key "level" is a reserved key and will be dropped.', - }) - ); -}); - -it('does not overwrite via extra keys info', () => { - const logSpy = vi.spyOn(console, 'info'); - const warnSpy = vi.spyOn(console, 'warn'); - const logger = new Logger({ - logLevel: 'DEBUG', - }); - - logger.info('stuff', { - level: 'Hello, World!', - timestamp: 'foo', - message: 'bar', - }); - - const log = JSON.parse(logSpy.mock.calls[0][0]); - expect(log).toStrictEqual( - expect.objectContaining({ - level: 'INFO', - timestamp: expect.not.stringMatching('foo'), - message: 'stuff', - }) - ); - expect(warnSpy).toHaveBeenCalledTimes(3); - const warn = JSON.parse(warnSpy.mock.calls[0][0]); - expect(warn).toStrictEqual( - expect.objectContaining({ - message: 'The key "level" is a reserved key and will be dropped.', - }) - ); -}); diff --git a/packages/logger/tests/unit/workingWithkeys.test.ts b/packages/logger/tests/unit/workingWithkeys.test.ts index 15c8da50d2..3f08910e9c 100644 --- a/packages/logger/tests/unit/workingWithkeys.test.ts +++ b/packages/logger/tests/unit/workingWithkeys.test.ts @@ -620,4 +620,119 @@ describe('Working with keys', () => { }) ); }); + + it("doesn't overwrite standard keys when appending keys", () => { + // Prepare + const logger = new Logger(); + + // Act + logger.appendKeys({ + level: 'Hello, World!', + }); + logger.info('foo'); + + // Assess + expect(console.info).toHaveLoggedNth( + 1, + expect.objectContaining({ + level: 'INFO', + }) + ); + expect(console.warn).toHaveLoggedNth( + 1, + expect.objectContaining({ + message: 'The key "level" is a reserved key and will be dropped.', + }) + ); + }); + + it("doesn't overwrite standard keys when appending persistent keys", () => { + // Prepare + const logger = new Logger(); + + // Act + logger.appendPersistentKeys({ + timestamp: 'Hello, World!', + }); + logger.info('foo'); + + // Assess + expect(console.info).toHaveLoggedNth( + 1, + expect.objectContaining({ + timestamp: expect.not.stringMatching('Hello, World!'), + }) + ); + expect(console.warn).toHaveLoggedNth( + 1, + expect.objectContaining({ + message: 'The key "timestamp" is a reserved key and will be dropped.', + }) + ); + }); + + it("doesn't overwrite standard keys when appending keys via constructor", () => { + // Prepare + const logger = new Logger({ + persistentKeys: { + sampling_rate: 'Hello, World!', + }, + }); + + // Act + logger.info('foo'); + + // Assess + expect(console.info).toHaveLoggedNth( + 1, + expect.objectContaining({ + sampling_rate: expect.not.stringMatching('Hello, World!'), + }) + ); + expect(console.warn).toHaveLoggedNth( + 1, + expect.objectContaining({ + message: + 'The key "sampling_rate" is a reserved key and will be dropped.', + }) + ); + }); + + it("doesn't overwrite standard keys when passing keys to log method", () => { + // Prepare + const logger = new Logger(); + + // Act + logger.info( + { + message: 'foo', + timestamp: 'Hello, World!', + }, + { + level: 'Hello, World!', + } + ); + + // Assess + expect(console.info).toHaveLoggedNth( + 1, + expect.objectContaining({ + message: 'foo', + timestamp: expect.not.stringMatching('Hello, World!'), + level: 'INFO', + }) + ); + expect(console.warn).toHaveLoggedNth( + 1, + expect.objectContaining({ + message: 'The key "timestamp" is a reserved key and will be dropped.', + }) + ); + expect(console.warn).toHaveLoggedNth( + 2, + expect.objectContaining({ + message: 'The key "level" is a reserved key and will be dropped.', + }) + ); + }); }); From 1997fc0ef9360b4493603c020ea20be00e25f291 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Tue, 4 Feb 2025 14:05:15 +0100 Subject: [PATCH 06/10] refactor: reduce cognitive complexity --- packages/logger/src/Logger.ts | 106 +++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 35 deletions(-) diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index df49fd9a72..d9b91ff056 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -704,76 +704,112 @@ class Logger extends Utility implements LoggerInterface { /** * Create a log item and populate it with the given log level, input, and extra input. - * - * We start with creating an object with base attributes managed by Powertools. - * Then we create a second object with persistent attributes provided by customers either - * directly to the log entry or through initial configuration and `appendKeys` method. - * - * Once we have the two objects, we pass them to the formatter that will apply the desired - * formatting to the log item. - * - * @param logLevel - The log level of the log item to be printed - * @param input - The main input of the log item, this can be a string or an object with additional attributes - * @param extraInput - Additional attributes to be added to the log item */ protected createAndPopulateLogItem( logLevel: number, input: LogItemMessage, extraInput: LogItemExtraInput ): LogItem { - const unformattedBaseAttributes: UnformattedAttributes = { + const unformattedBaseAttributes = this.#createBaseAttributes(logLevel); + const additionalAttributes = this.#createAdditionalAttributes(); + + this.#processMainInput( + input, + unformattedBaseAttributes, + additionalAttributes + ); + this.#processExtraInput(extraInput, additionalAttributes); + + return this.getLogFormatter().formatAttributes( + unformattedBaseAttributes, + additionalAttributes + ); + } + + /** + * Create the base attributes for a log item + */ + #createBaseAttributes(logLevel: number): UnformattedAttributes { + return { logLevel: this.getLogLevelNameFromNumber(logLevel), timestamp: new Date(), xRayTraceId: this.envVarsService.getXrayTraceId(), ...this.getPowertoolsLogData(), message: '', }; + } + + /** + * Create additional attributes from persistent and temporary keys + */ + #createAdditionalAttributes(): LogAttributes { + const attributes: LogAttributes = {}; - const additionalAttributes: LogAttributes = {}; - // Add additional attributes from persistent and temporary keys for (const [key, type] of this.#keys) { if (!this.#checkReservedKeyAndWarn(key)) { - additionalAttributes[key] = + attributes[key] = type === 'persistent' ? this.persistentLogAttributes[key] : this.temporaryLogAttributes[key]; } } - // Handle input message + return attributes; + } + + /** + * Process the main input message and add it to the attributes + */ + #processMainInput( + input: LogItemMessage, + baseAttributes: UnformattedAttributes, + additionalAttributes: LogAttributes + ): void { if (typeof input === 'string') { - unformattedBaseAttributes.message = input; - } else { - const { message, ...rest } = input; - unformattedBaseAttributes.message = message; + baseAttributes.message = input; + return; + } - // Add remaining input properties if they're not reserved - for (const [key, value] of Object.entries(rest)) { - if (!this.#checkReservedKeyAndWarn(key)) { - additionalAttributes[key] = value; - } + const { message, ...rest } = input; + baseAttributes.message = message; + + for (const [key, value] of Object.entries(rest)) { + if (!this.#checkReservedKeyAndWarn(key)) { + additionalAttributes[key] = value; } } + } - // Handle extra input attributes + /** + * Process extra input items and add them to additional attributes + */ + #processExtraInput( + extraInput: LogItemExtraInput, + additionalAttributes: LogAttributes + ): void { for (const item of extraInput) { if (item instanceof Error) { additionalAttributes.error = item; } else if (typeof item === 'string') { additionalAttributes.extra = item; } else { - for (const [key, value] of Object.entries(item)) { - if (!this.#checkReservedKeyAndWarn(key)) { - additionalAttributes[key] = value; - } - } + this.#processExtraObject(item, additionalAttributes); } } + } - return this.getLogFormatter().formatAttributes( - unformattedBaseAttributes, - additionalAttributes - ); + /** + * Process an extra input object and add its properties to additional attributes + */ + #processExtraObject( + item: Record, + additionalAttributes: LogAttributes + ): void { + for (const [key, value] of Object.entries(item)) { + if (!this.#checkReservedKeyAndWarn(key)) { + additionalAttributes[key] = value; + } + } } /** From d93fe14e4c21f09c8a1694ee2add3d4aa33b0f2e Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 5 Feb 2025 11:34:55 +0100 Subject: [PATCH 07/10] chore: remove xray_trace_id from reserved keys --- packages/logger/src/constants.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/logger/src/constants.ts b/packages/logger/src/constants.ts index 307b6f254c..a95ed293c7 100644 --- a/packages/logger/src/constants.ts +++ b/packages/logger/src/constants.ts @@ -48,7 +48,6 @@ const ReservedKeys = [ 'sampling_rate', 'service', 'timestamp', - 'xray_trace_id', ]; export { LogJsonIndent, LogLevel, LogLevelThreshold, ReservedKeys }; From cb63fbb260628d4a67d3c2c58cb17646810ae872 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 5 Feb 2025 12:31:07 +0100 Subject: [PATCH 08/10] docs: add info about the warning --- docs/core/logger.md | 9 ++++++-- packages/logger/src/Logger.ts | 42 +++++++++++++---------------------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/docs/core/logger.md b/docs/core/logger.md index 6e66a92787..2fc7516d8a 100644 --- a/docs/core/logger.md +++ b/docs/core/logger.md @@ -52,6 +52,9 @@ These settings will be used across all logs emitted: | **Logging level** | Sets how verbose Logger should be, from the most verbose to the least verbose (no logs) | `POWERTOOLS_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARN`, `ERROR`, `CRITICAL`, `SILENT` | `ERROR` | `logLevel` | | **Sample rate** | Probability that a Lambda invocation will print all the log items regardless of the log level setting | `POWERTOOLS_LOGGER_SAMPLE_RATE` | `0` | `0.0` to `1.0` | `0.1` | `sampleRateValue` | +???+ info + When `POWERTOOLS_DEV` environment variable is present and set to `"true"` or `"1"`, Logger will pretty-print log messages for easier readability. We recommend to use this setting only when debugging on local environments. + See all environment variables in the [Environment variables](../index.md/#environment-variables) section. Check API docs to learn more about [Logger constructor options](https://docs.powertools.aws.dev/lambda/typescript/latest/api/types/_aws_lambda_powertools_logger.types.ConstructorOptions.html){target="_blank"}. @@ -91,8 +94,8 @@ Your Logger will include the following keys to your structured logging (default | **xray_trace_id**: `string` | `1-5759e988-bd862e3fe1be46a994272793` | X-Ray Trace ID. This value is always presented in Lambda environment, whether [tracing is enabled](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html){target="_blank"} or not. Logger will always log this value. | | **error**: `Object` | `{ name: "Error", location: "/my-project/handler.ts:18", message: "Unexpected error #1", stack: "[stacktrace]"}` | Optional - An object containing information about the Error passed to the logger | -???+ info - When `POWERTOOLS_DEV` environment variable is present and set to `"true"` or `"1"`, Logger will pretty-print log messages for easier readability. We recommend to use this setting only when debugging on local environments. +???+ note + If you emit a log message with a key that matches one of `level`, `message`, `sampling_rate`, `service`, or `timestamp`, the Logger will log a warning message and ignore the key. ### Capturing Lambda context info @@ -211,6 +214,8 @@ You can append additional keys using either mechanism: * Append **temporary keys** to all future log messages via the `appendKeys()` method until `resetKeys()` is called * Set **Persistent keys** for the logger instance via the `persistentKeys` constructor option or the `appendPersistentKeys()` method +To prevent you from accidentally overwriting some of the [standard keys](#standard-structured-keys), we will log a warning message and ignore the key if you try to overwrite them. + #### Extra keys You can append additional data to a single log item by passing objects as additional parameters. diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index d9b91ff056..5a79da9afd 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -208,11 +208,7 @@ class Logger extends Utility implements LoggerInterface { } /** - * Add the given persistent attributes (key-value pairs) to all log items generated by this Logger instance. - * - * @deprecated This method is deprecated and will be removed in the future major versions, please use {@link appendPersistentKeys()} instead. - * - * @param attributes - The attributes to add to all log items. + * @deprecated This method is deprecated and will be removed in the future major versions, please use {@link appendPersistentKeys() `appendPersistentKeys()`} instead. */ public addPersistentLogAttributes(attributes: LogAttributes): void { this.appendPersistentKeys(attributes); @@ -221,6 +217,9 @@ class Logger extends Utility implements LoggerInterface { /** * Add the given temporary attributes (key-value pairs) to all log items generated by this Logger instance. * + * If the key already exists in the attributes, it will be overwritten. If the key is one of `level`, `message`, `sampling_rate`, + * `service`, or `timestamp` we will log a warning and drop the value. + * * @param attributes - The attributes to add to all log items. */ public appendKeys(attributes: LogAttributes): void { @@ -230,6 +229,9 @@ class Logger extends Utility implements LoggerInterface { /** * Add the given persistent attributes (key-value pairs) to all log items generated by this Logger instance. * + * If the key already exists in the attributes, it will be overwritten. If the key is one of `level`, `message`, `sampling_rate`, + * `service`, or `timestamp` we will log a warning and drop the value. + * * @param attributes - The attributes to add to all log items. */ public appendPersistentKeys(attributes: LogAttributes): void { @@ -511,8 +513,6 @@ class Logger extends Utility implements LoggerInterface { /** * @deprecated This method is deprecated and will be removed in the future major versions. Use {@link removePersistentKeys()} instead. - * - * @param keys - The keys to remove. */ public removePersistentLogAttributes(keys: string[]): void { this.removePersistentKeys(keys); @@ -550,12 +550,7 @@ class Logger extends Utility implements LoggerInterface { } /** - * Set the given attributes (key-value pairs) to all log items generated by this Logger instance. - * Note: this replaces the pre-existing value. - * - * @deprecated This method is deprecated and will be removed in the future major versions, please use {@link appendPersistentKeys()} instead. - * - * @param attributes - The attributes to set. + * @deprecated This method is deprecated and will be removed in the future major versions, please use {@link appendPersistentKeys() `appendPersistentKeys()`} instead. */ public setPersistentLogAttributes(attributes: LogAttributes): void { this.persistentLogAttributes = attributes; @@ -710,7 +705,13 @@ class Logger extends Utility implements LoggerInterface { input: LogItemMessage, extraInput: LogItemExtraInput ): LogItem { - const unformattedBaseAttributes = this.#createBaseAttributes(logLevel); + const unformattedBaseAttributes = { + logLevel: this.getLogLevelNameFromNumber(logLevel), + timestamp: new Date(), + xRayTraceId: this.envVarsService.getXrayTraceId(), + ...this.getPowertoolsLogData(), + message: '', + }; const additionalAttributes = this.#createAdditionalAttributes(); this.#processMainInput( @@ -726,19 +727,6 @@ class Logger extends Utility implements LoggerInterface { ); } - /** - * Create the base attributes for a log item - */ - #createBaseAttributes(logLevel: number): UnformattedAttributes { - return { - logLevel: this.getLogLevelNameFromNumber(logLevel), - timestamp: new Date(), - xRayTraceId: this.envVarsService.getXrayTraceId(), - ...this.getPowertoolsLogData(), - message: '', - }; - } - /** * Create additional attributes from persistent and temporary keys */ From 16710552a32c0d34ada1f0d1977ea57abc4ee559 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 5 Feb 2025 13:14:14 +0100 Subject: [PATCH 09/10] chore: improve types --- packages/logger/src/types/Logger.ts | 9 ++++++++- packages/logger/tests/unit/workingWithkeys.test.ts | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/logger/src/types/Logger.ts b/packages/logger/src/types/Logger.ts index a460b5c6e8..f1095b79d9 100644 --- a/packages/logger/src/types/Logger.ts +++ b/packages/logger/src/types/Logger.ts @@ -108,7 +108,14 @@ type PersistentKeysOption = { /** * Keys that will be added to all log items. */ - persistentKeys?: LogAttributes; + persistentKeys?: LogAttributes & { + message?: never; + level?: never; + sampling_rate?: never; + service?: never; + timestamp?: never; + error?: never; + }; /** * @deprecated Use `persistentKeys` instead. */ diff --git a/packages/logger/tests/unit/workingWithkeys.test.ts b/packages/logger/tests/unit/workingWithkeys.test.ts index 3f08910e9c..209507f4fa 100644 --- a/packages/logger/tests/unit/workingWithkeys.test.ts +++ b/packages/logger/tests/unit/workingWithkeys.test.ts @@ -675,6 +675,7 @@ describe('Working with keys', () => { // Prepare const logger = new Logger({ persistentKeys: { + // @ts-expect-error - testing invalid key at runtime sampling_rate: 'Hello, World!', }, }); From ce39d522f9d0991ecc2008487ee1039583f72f92 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 5 Feb 2025 13:26:17 +0100 Subject: [PATCH 10/10] improv: types --- packages/logger/src/Logger.ts | 15 ++++--- packages/logger/src/types/Logger.ts | 5 ++- packages/logger/src/types/logKeys.ts | 43 +++++++++++++++++++ .../logger/tests/unit/workingWithkeys.test.ts | 3 ++ 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index 5a79da9afd..ee1a240789 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -26,6 +26,7 @@ import type { LoggerInterface, } from './types/Logger.js'; import type { + LogKeys, PowertoolsLogData, UnformattedAttributes, } from './types/logKeys.js'; @@ -134,7 +135,7 @@ class Logger extends Utility implements LoggerInterface { /** * Temporary log attributes that can be appended with `appendKeys()` method. */ - private temporaryLogAttributes: LogAttributes = {}; + private temporaryLogAttributes: LogKeys = {}; /** * Buffer used to store logs until the logger is initialized. * @@ -210,7 +211,7 @@ class Logger extends Utility implements LoggerInterface { /** * @deprecated This method is deprecated and will be removed in the future major versions, please use {@link appendPersistentKeys() `appendPersistentKeys()`} instead. */ - public addPersistentLogAttributes(attributes: LogAttributes): void { + public addPersistentLogAttributes(attributes: LogKeys): void { this.appendPersistentKeys(attributes); } @@ -222,7 +223,7 @@ class Logger extends Utility implements LoggerInterface { * * @param attributes - The attributes to add to all log items. */ - public appendKeys(attributes: LogAttributes): void { + public appendKeys(attributes: LogKeys): void { this.#appendKeys(attributes, 'temp'); } @@ -234,7 +235,7 @@ class Logger extends Utility implements LoggerInterface { * * @param attributes - The attributes to add to all log items. */ - public appendPersistentKeys(attributes: LogAttributes): void { + public appendPersistentKeys(attributes: LogKeys): void { this.#appendKeys(attributes, 'persistent'); } @@ -519,7 +520,7 @@ class Logger extends Utility implements LoggerInterface { } /** - * Remove all temporary log attributes added with `appendKeys()` method. + * Remove all temporary log attributes added with {@link appendKeys() `appendKeys()`} method. */ public resetKeys(): void { for (const key of Object.keys(this.temporaryLogAttributes)) { @@ -552,7 +553,7 @@ class Logger extends Utility implements LoggerInterface { /** * @deprecated This method is deprecated and will be removed in the future major versions, please use {@link appendPersistentKeys() `appendPersistentKeys()`} instead. */ - public setPersistentLogAttributes(attributes: LogAttributes): void { + public setPersistentLogAttributes(attributes: LogKeys): void { this.persistentLogAttributes = attributes; } @@ -664,7 +665,7 @@ class Logger extends Utility implements LoggerInterface { * @param attributes - The attributes to add to the log item. * @param type - The type of the attributes to add. */ - #appendKeys(attributes: LogAttributes, type: 'temp' | 'persistent'): void { + #appendKeys(attributes: LogKeys, type: 'temp' | 'persistent'): void { for (const attributeKey of Object.keys(attributes)) { if (this.#checkReservedKeyAndWarn(attributeKey) === false) { this.#keys.set(attributeKey, type); diff --git a/packages/logger/src/types/Logger.ts b/packages/logger/src/types/Logger.ts index f1095b79d9..1e453f1ee5 100644 --- a/packages/logger/src/types/Logger.ts +++ b/packages/logger/src/types/Logger.ts @@ -8,6 +8,7 @@ import type { Environment, LogAttributes, LogAttributesWithMessage, + LogKeys, } from './logKeys.js'; /** @@ -187,8 +188,8 @@ type LogItemExtraInput = [Error | string] | LogAttributes[]; */ type LoggerInterface = { addContext(context: Context): void; - addPersistentLogAttributes(attributes?: LogAttributes): void; - appendKeys(attributes?: LogAttributes): void; + appendPersistentKeys(attributes?: LogKeys): void; + appendKeys(attributes?: LogKeys): void; createChild(options?: ConstructorOptions): LoggerInterface; critical(input: LogItemMessage, ...extraInput: LogItemExtraInput): void; debug(input: LogItemMessage, ...extraInput: LogItemExtraInput): void; diff --git a/packages/logger/src/types/logKeys.ts b/packages/logger/src/types/logKeys.ts index 1f2749445d..ad4966fd41 100644 --- a/packages/logger/src/types/logKeys.ts +++ b/packages/logger/src/types/logKeys.ts @@ -24,7 +24,26 @@ type LogAttributes = { [key: string]: unknown }; * This is the most common log attribute object and it's used as first argument in the logger methods. */ type LogAttributesWithMessage = LogAttributes & { + /** + * Your log message. + */ message: string; + /** + * This key is generated by the logger and cannot be set manually. + */ + level?: never; + /** + * This key is generated by the logger and cannot be set manually. + */ + sampling_rate?: never; + /** + * This key is generated by the logger and cannot be set manually. + */ + service?: never; + /** + * This key is generated by the logger and cannot be set manually. + */ + timestamp?: never; }; /** @@ -217,7 +236,31 @@ type LogKey = | keyof PowertoolsLambdaContextKeys | AutocompletableString; +type LogKeys = { [key: string]: unknown } & { + /** + * This key is passed when emitting a log message and cannot be set manually. + */ + message?: never; + /** + * This key is generated by the logger and cannot be added to the logger. + */ + level?: never; + /** + * This key is generated by the logger and cannot be added to the logger. + */ + sampling_rate?: never; + /** + * This key is generated by the logger and cannot be added to the logger. + */ + service?: never; + /** + * This key is generated by the logger and cannot be added to the logger. + */ + timestamp?: never; +}; + export type { + LogKeys, LogAttributes, LogAttributesWithMessage, Environment, diff --git a/packages/logger/tests/unit/workingWithkeys.test.ts b/packages/logger/tests/unit/workingWithkeys.test.ts index 209507f4fa..c2af93bcb9 100644 --- a/packages/logger/tests/unit/workingWithkeys.test.ts +++ b/packages/logger/tests/unit/workingWithkeys.test.ts @@ -627,6 +627,7 @@ describe('Working with keys', () => { // Act logger.appendKeys({ + // @ts-expect-error - testing invalid key at runtime level: 'Hello, World!', }); logger.info('foo'); @@ -652,6 +653,7 @@ describe('Working with keys', () => { // Act logger.appendPersistentKeys({ + // @ts-expect-error - testing invalid key at runtime timestamp: 'Hello, World!', }); logger.info('foo'); @@ -707,6 +709,7 @@ describe('Working with keys', () => { logger.info( { message: 'foo', + // @ts-expect-error - testing invalid key at runtime timestamp: 'Hello, World!', }, {