diff --git a/package-lock.json b/package-lock.json index bf8d48f6a7..1747c139e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2439,6 +2439,7 @@ "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", >>>>>>> 50aa3e7d (chore(maintenance): remove `createLogger` and `createTracer` helpers (#1722)) "dev": true, +<<<<<<< HEAD "bin": { "parser": "bin/babel-parser.js" }, @@ -2655,6 +2656,214 @@ "@babel/code-frame": "^7.22.13", "@babel/generator": "^7.23.0", >>>>>>> 50aa3e7d (chore(maintenance): remove `createLogger` and `createTracer` helpers (#1722)) +======= + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", +>>>>>>> 13b1d13a (chore(logger): refactor types and interfaces (#1758)) "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index 529fbb7a58..b878782ec4 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -1,31 +1,32 @@ -import { randomInt } from 'node:crypto'; -import { Console } from 'node:console'; -import { format } from 'node:util'; -import type { Context, Handler } from 'aws-lambda'; import { Utility } from '@aws-lambda-powertools/commons'; -import { PowertoolsLogFormatter } from './formatter/PowertoolsLogFormatter.js'; -import { LogFormatterInterface } from './formatter/LogFormatterInterface.js'; -import { LogItem } from './log/LogItem.js'; +import type { HandlerMethodDecorator } from '@aws-lambda-powertools/commons/types'; +import type { Context, Handler } from 'aws-lambda'; import merge from 'lodash.merge'; -import { ConfigServiceInterface } from './config/ConfigServiceInterface.js'; +import { Console } from 'node:console'; +import { format } from 'node:util'; +import { randomInt } from 'node:crypto'; import { EnvironmentVariablesService } from './config/EnvironmentVariablesService.js'; -import { LogJsonIndent } from './types/Logger.js'; +import { LogJsonIndent } from './constants.js'; +import { LogItem } from './formatter/LogItem.js'; +import { PowertoolsLogFormatter } from './formatter/PowertoolsLogFormatter.js'; +import type { ConfigServiceInterface } from './types/ConfigServiceInterface.js'; import type { Environment, LogAttributes, LogLevel, LogLevelThresholds, + LogFormatterInterface, } from './types/Log.js'; import type { - ClassThatLogs, - HandlerMethodDecorator, - LambdaFunctionContext, + LogFunction, ConstructorOptions, + InjectLambdaContextOptions, LogItemExtraInput, LogItemMessage, - PowertoolLogData, - HandlerOptions, + LoggerInterface, + PowertoolsLogData, } from './types/Logger.js'; + /** * ## Intro * The Logger utility provides an opinionated logger with output structured as JSON. @@ -112,7 +113,7 @@ import type { * @implements {ClassThatLogs} * @see https://docs.powertools.aws.dev/lambda/typescript/latest/core/logger/ */ -class Logger extends Utility implements ClassThatLogs { +class Logger extends Utility implements LoggerInterface { /** * Console instance used to print logs. * @@ -156,9 +157,9 @@ class Logger extends Utility implements ClassThatLogs { SILENT: 28, }; - private persistentLogAttributes?: LogAttributes = {}; + private persistentLogAttributes: LogAttributes = {}; - private powertoolLogData: PowertoolLogData = {}; + private powertoolsLogData: PowertoolsLogData = {}; /** * Log level used by the current instance of Logger. @@ -188,17 +189,15 @@ class Logger extends Utility implements ClassThatLogs { * @returns {void} */ public addContext(context: Context): void { - const lambdaContext: Partial = { - invokedFunctionArn: context.invokedFunctionArn, - coldStart: this.getColdStart(), - awsRequestId: context.awsRequestId, - memoryLimitInMB: Number(context.memoryLimitInMB), - functionName: context.functionName, - functionVersion: context.functionVersion, - }; - - this.addToPowertoolLogData({ - lambdaContext, + this.addToPowertoolsLogData({ + lambdaContext: { + invokedFunctionArn: context.invokedFunctionArn, + coldStart: this.getColdStart(), + awsRequestId: context.awsRequestId, + memoryLimitInMB: context.memoryLimitInMB, + functionName: context.functionName, + functionVersion: context.functionVersion, + }, }); } @@ -230,23 +229,27 @@ class Logger extends Utility implements ClassThatLogs { * @returns {Logger} */ public createChild(options: ConstructorOptions = {}): Logger { - const parentsOptions = { - logLevel: this.getLevelName(), - customConfigService: this.getCustomConfigService(), - logFormatter: this.getLogFormatter(), - sampleRateValue: this.powertoolLogData.sampleRateValue, - }; - const parentsPowertoolsLogData = this.getPowertoolLogData(); const childLogger = this.createLogger( - merge(parentsOptions, parentsPowertoolsLogData, options) + // Merge parent logger options with options passed to createChild, + // the latter having precedence. + merge( + {}, + { + logLevel: this.getLevelName(), + serviceName: this.powertoolsLogData.serviceName, + sampleRateValue: this.powertoolsLogData.sampleRateValue, + logFormatter: this.getLogFormatter(), + customConfigService: this.getCustomConfigService(), + environment: this.powertoolsLogData.environment, + persistentLogAttributes: this.persistentLogAttributes, + }, + options + ) ); - - const parentsPersistentLogAttributes = this.getPersistentLogAttributes(); - childLogger.addPersistentLogAttributes(parentsPersistentLogAttributes); - - if (parentsPowertoolsLogData.lambdaContext) { - childLogger.addContext(parentsPowertoolsLogData.lambdaContext as Context); - } + if (this.powertoolsLogData.lambdaContext) + childLogger.addContext( + this.powertoolsLogData.lambdaContext as unknown as Context + ); return childLogger; } @@ -316,7 +319,7 @@ class Logger extends Utility implements ClassThatLogs { * @returns {LogAttributes} */ public getPersistentLogAttributes(): LogAttributes { - return this.persistentLogAttributes as LogAttributes; + return this.persistentLogAttributes; } /** @@ -362,7 +365,9 @@ class Logger extends Utility implements ClassThatLogs { * @see https://www.typescriptlang.org/docs/handbook/decorators.html#method-decorators * @returns {HandlerMethodDecorator} */ - public injectLambdaContext(options?: HandlerOptions): HandlerMethodDecorator { + public injectLambdaContext( + options?: InjectLambdaContextOptions + ): HandlerMethodDecorator { return (_target, _propertyKey, descriptor) => { /** * The descriptor.value is the method this decorator decorates, it cannot be undefined. @@ -410,7 +415,7 @@ class Logger extends Utility implements ClassThatLogs { public static injectLambdaContextAfterOrOnError( logger: Logger, initialPersistentAttributes: LogAttributes, - options?: HandlerOptions + options?: InjectLambdaContextOptions ): void { if (options && options.clearState === true) { logger.setPersistentLogAttributes(initialPersistentAttributes); @@ -421,13 +426,13 @@ class Logger extends Utility implements ClassThatLogs { logger: Logger, event: unknown, context: Context, - options?: HandlerOptions + options?: InjectLambdaContextOptions ): void { logger.addContext(context); let shouldLogEvent = undefined; - if (options && options.hasOwnProperty('logEvent')) { - shouldLogEvent = options.logEvent; + if (Object.hasOwn(options || {}, 'logEvent')) { + shouldLogEvent = options!.logEvent; } logger.logEventIfEnabled(event, shouldLogEvent); } @@ -440,9 +445,7 @@ class Logger extends Utility implements ClassThatLogs { * @returns {void} */ public logEventIfEnabled(event: unknown, overwriteValue?: boolean): void { - if (!this.shouldLogEvent(overwriteValue)) { - return; - } + if (!this.shouldLogEvent(overwriteValue)) return; this.info('Lambda invocation event', { event }); } @@ -454,7 +457,7 @@ class Logger extends Utility implements ClassThatLogs { * @returns {void} */ public refreshSampleRateCalculation(): void { - this.setInitialSampleRate(this.powertoolLogData.sampleRateValue); + this.setInitialSampleRate(this.powertoolsLogData.sampleRateValue); } /** @@ -474,11 +477,11 @@ class Logger extends Utility implements ClassThatLogs { * @returns {void} */ public removePersistentLogAttributes(keys: string[]): void { - keys.forEach((key) => { - if (this.persistentLogAttributes && key in this.persistentLogAttributes) { + for (const key of keys) { + if (Object.hasOwn(this.persistentLogAttributes, key)) { delete this.persistentLogAttributes[key]; } - }); + } } /** @@ -564,16 +567,12 @@ class Logger extends Utility implements ClassThatLogs { /** * It stores information that is printed in all log items. * - * @param {Partial} attributesArray + * @param {Partial} attributes * @private * @returns {void} */ - private addToPowertoolLogData( - ...attributesArray: Array> - ): void { - attributesArray.forEach((attributes: Partial) => { - merge(this.powertoolLogData, attributes); - }); + private addToPowertoolsLogData(attributes: Partial): void { + merge(this.powertoolsLogData, attributes); } private awsLogLevelShortCircuit(selectedLogLevel?: string): boolean { @@ -624,7 +623,7 @@ class Logger extends Utility implements ClassThatLogs { message: typeof input === 'string' ? input : input.message, xRayTraceId: this.envVarsService.getXrayTraceId(), }, - this.getPowertoolLogData() + this.getPowertoolsLogData() ); let additionalLogAttributes: LogAttributes = {}; @@ -694,15 +693,15 @@ class Logger extends Utility implements ClassThatLogs { * @returns - The name of the log level */ private getLogLevelNameFromNumber(logLevel: number): Uppercase { - const found = Object.entries(this.logLevelThresholds).find( - ([key, value]) => { - if (value === logLevel) { - return key; - } + let found; + for (const [key, value] of Object.entries(this.logLevelThresholds)) { + if (value === logLevel) { + found = key; + break; } - )!; + } - return found[0] as Uppercase; + return found as Uppercase; } /** @@ -712,8 +711,8 @@ class Logger extends Utility implements ClassThatLogs { * @private * @returns {LogAttributes} */ - private getPowertoolLogData(): PowertoolLogData { - return this.powertoolLogData; + private getPowertoolsLogData(): PowertoolsLogData { + return this.powertoolsLogData; } /** @@ -794,7 +793,7 @@ class Logger extends Utility implements ClassThatLogs { logLevel === 24 ? 'error' : (this.getLogLevelNameFromNumber(logLevel).toLowerCase() as keyof Omit< - ClassThatLogs, + LogFunction, 'critical' >); @@ -923,14 +922,14 @@ class Logger extends Utility implements ClassThatLogs { * @returns {void} */ private setInitialSampleRate(sampleRateValue?: number): void { - this.powertoolLogData.sampleRateValue = 0; + this.powertoolsLogData.sampleRateValue = 0; const constructorValue = sampleRateValue; const customConfigValue = this.getCustomConfigService()?.getSampleRateValue(); const envVarsValue = this.getEnvVarsService().getSampleRateValue(); for (const value of [constructorValue, customConfigValue, envVarsValue]) { if (this.isValidSampleRate(value)) { - this.powertoolLogData.sampleRateValue = value; + this.powertoolsLogData.sampleRateValue = value; if (value && randomInt(0, 100) / 100 <= value) { this.setLogLevel('DEBUG'); @@ -1005,7 +1004,7 @@ class Logger extends Utility implements ClassThatLogs { this.setCustomConfigService(customConfigService); this.setInitialLogLevel(logLevel); this.setLogFormatter(logFormatter); - this.setPowertoolLogData(serviceName, environment); + this.setPowertoolsLogData(serviceName, environment); this.setInitialSampleRate(sampleRateValue); this.setLogEvent(); this.setLogIndentation(); @@ -1024,26 +1023,24 @@ class Logger extends Utility implements ClassThatLogs { * @private * @returns {void} */ - private setPowertoolLogData( + private setPowertoolsLogData( serviceName?: string, environment?: Environment, persistentLogAttributes: LogAttributes = {} ): void { - this.addToPowertoolLogData( - { - awsRegion: this.getEnvVarsService().getAwsRegion(), - environment: - environment || - this.getCustomConfigService()?.getCurrentEnvironment() || - this.getEnvVarsService().getCurrentEnvironment(), - serviceName: - serviceName || - this.getCustomConfigService()?.getServiceName() || - this.getEnvVarsService().getServiceName() || - this.getDefaultServiceName(), - }, - persistentLogAttributes - ); + this.addToPowertoolsLogData({ + awsRegion: this.getEnvVarsService().getAwsRegion(), + environment: + environment || + this.getCustomConfigService()?.getCurrentEnvironment() || + this.getEnvVarsService().getCurrentEnvironment(), + serviceName: + serviceName || + this.getCustomConfigService()?.getServiceName() || + this.getEnvVarsService().getServiceName() || + this.getDefaultServiceName(), + }); + this.addPersistentLogAttributes(persistentLogAttributes); } } diff --git a/packages/logger/src/config/EnvironmentVariablesService.ts b/packages/logger/src/config/EnvironmentVariablesService.ts index fd3eedc712..3248a3d0a7 100644 --- a/packages/logger/src/config/EnvironmentVariablesService.ts +++ b/packages/logger/src/config/EnvironmentVariablesService.ts @@ -1,4 +1,4 @@ -import { ConfigServiceInterface } from './ConfigServiceInterface.js'; +import { ConfigServiceInterface } from '../types/ConfigServiceInterface.js'; import { EnvironmentVariablesService as CommonEnvironmentVariablesService } from '@aws-lambda-powertools/commons'; /** diff --git a/packages/logger/src/constants.ts b/packages/logger/src/constants.ts new file mode 100644 index 0000000000..90a26baa24 --- /dev/null +++ b/packages/logger/src/constants.ts @@ -0,0 +1,17 @@ +/** + * The indent level for JSON logs. + * + * By default Logger will use the `LogJsonIndent.COMPACT` indent level, which + * produces logs on a single line. This is the most efficient option for + * CloudWatch Logs. + * + * When enabling the `POWERTOOLS_DEV` environment variable, Logger will use the + * `LogJsonIndent.PRETTY` indent level, which indents the JSON logs for easier + * reading. + */ +const LogJsonIndent = { + PRETTY: 4, + COMPACT: 0, +} as const; + +export { LogJsonIndent }; diff --git a/packages/logger/src/formatter/LogFormatter.ts b/packages/logger/src/formatter/LogFormatter.ts index 814e193ded..478e04152f 100644 --- a/packages/logger/src/formatter/LogFormatter.ts +++ b/packages/logger/src/formatter/LogFormatter.ts @@ -1,23 +1,6 @@ -import { LogFormatterInterface } from './LogFormatterInterface.js'; -import { LogAttributes } from '../types/Log.js'; -import { UnformattedAttributes } from '../types/Logger.js'; -import { LogItem } from '../log/LogItem.js'; - -/** - * Typeguard to monkey patch Error to add a cause property. - * - * This is needed because the `cause` property is present in ES2022 or newer. - * Since we want to be able to format errors in Node 16.x, we need to - * add this property ourselves. We can remove this once we drop support - * for Node 16.x. - * - * @see https://nodejs.org/api/errors.html#errors_error_cause - */ -const isErrorWithCause = ( - error: Error -): error is Error & { cause: unknown } => { - return 'cause' in error; -}; +import type { LogAttributes, LogFormatterInterface } from '../types/Log.js'; +import type { UnformattedAttributes } from '../types/Logger.js'; +import { LogItem } from './LogItem.js'; /** * This class defines and implements common methods for the formatting of log attributes. @@ -51,11 +34,10 @@ abstract class LogFormatter implements LogFormatterInterface { location: this.getCodeLocation(error.stack), message: error.message, stack: error.stack, - cause: isErrorWithCause(error) - ? error.cause instanceof Error + cause: + error.cause instanceof Error ? this.formatError(error.cause) - : error.cause - : undefined, + : error.cause, }; } diff --git a/packages/logger/src/formatter/LogFormatterInterface.ts b/packages/logger/src/formatter/LogFormatterInterface.ts deleted file mode 100644 index 49d35154c3..0000000000 --- a/packages/logger/src/formatter/LogFormatterInterface.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { LogAttributes } from '../types/Log.js'; -import { UnformattedAttributes } from '../types/Logger.js'; -import { LogItem } from '../log/LogItem.js'; - -/** - * @interface - */ -interface LogFormatterInterface { - /** - * It formats key-value pairs of log attributes. - * - * @param {UnformattedAttributes} attributes - * @param {LogAttributes} additionalLogAttributes - * @returns {LogItem} - */ - formatAttributes( - attributes: UnformattedAttributes, - additionalLogAttributes: LogAttributes - ): LogItem; - - /** - * It formats a given Error parameter. - * - * @param {Error} error - * @returns {LogAttributes} - */ - formatError(error: Error): LogAttributes; -} - -export { LogFormatterInterface }; diff --git a/packages/logger/src/log/LogItem.ts b/packages/logger/src/formatter/LogItem.ts similarity index 84% rename from packages/logger/src/log/LogItem.ts rename to packages/logger/src/formatter/LogItem.ts index 4183017b4f..7ddacc61f8 100644 --- a/packages/logger/src/log/LogItem.ts +++ b/packages/logger/src/formatter/LogItem.ts @@ -1,6 +1,5 @@ import merge from 'lodash.merge'; -import { LogItemInterface } from './LogItemInterface.js'; -import { LogAttributes } from '../types/Log.js'; +import type { LogAttributes, LogItemInterface } from '../types/Log.js'; class LogItem implements LogItemInterface { private attributes: LogAttributes = {}; @@ -13,8 +12,8 @@ class LogItem implements LogItemInterface { this.addAttributes(params.attributes); } - public addAttributes(attributes: LogAttributes): LogItem { - this.attributes = merge(this.attributes, attributes); + public addAttributes(attributes: LogAttributes): this { + merge(this.attributes, attributes); return this; } diff --git a/packages/logger/src/formatter/PowertoolsLogFormatter.ts b/packages/logger/src/formatter/PowertoolsLogFormatter.ts index f88630bf2a..80c0406bbb 100644 --- a/packages/logger/src/formatter/PowertoolsLogFormatter.ts +++ b/packages/logger/src/formatter/PowertoolsLogFormatter.ts @@ -1,12 +1,11 @@ +import type { LogAttributes, PowertoolsLog } from '../types/Log.js'; +import type { UnformattedAttributes } from '../types/Logger.js'; import { LogFormatter } from './LogFormatter.js'; -import { LogAttributes } from '../types/Log.js'; -import { UnformattedAttributes } from '../types/Logger.js'; -import { PowertoolsLog } from '../types/formats/PowertoolsLog.js'; -import { LogItem } from '../log/LogItem.js'; +import { LogItem } from './LogItem.js'; /** * This class is used to transform a set of log key-value pairs - * in the AWS Lambda Powertools' default structure log format. + * in the Powertools for AWS Lambda default structure log format. * * @class * @extends {LogFormatter} @@ -36,9 +35,7 @@ class PowertoolsLogFormatter extends LogFormatter { timestamp: this.formatTimestamp(attributes.timestamp), xray_trace_id: attributes.xRayTraceId, }; - const powertoolsLogItem = new LogItem({ attributes: baseAttributes }); - powertoolsLogItem.addAttributes(additionalLogAttributes); return powertoolsLogItem; diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index 096d26bb30..1fe46bcf9b 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -1,3 +1,3 @@ export { Logger } from './Logger.js'; export { LogFormatter } from './formatter/LogFormatter.js'; -export { LogItem } from './log/LogItem.js'; +export { LogItem } from './formatter/LogItem.js'; diff --git a/packages/logger/src/log/LogItemInterface.ts b/packages/logger/src/log/LogItemInterface.ts deleted file mode 100644 index ed0c89bb0b..0000000000 --- a/packages/logger/src/log/LogItemInterface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { LogAttributes } from '../types/Log.js'; - -interface LogItemInterface { - addAttributes(attributes: LogAttributes): void; - - getAttributes(): LogAttributes; -} - -export { LogItemInterface }; diff --git a/packages/logger/src/middleware/middy.ts b/packages/logger/src/middleware/middy.ts index 7d8b3099da..d40be85cd2 100644 --- a/packages/logger/src/middleware/middy.ts +++ b/packages/logger/src/middleware/middy.ts @@ -1,6 +1,6 @@ import { Logger } from '../Logger.js'; -import { LogAttributes } from '../types/Log.js'; -import { HandlerOptions } from '../types/Logger.js'; +import type { LogAttributes } from '../types/Log.js'; +import type { InjectLambdaContextOptions } from '../types/Logger.js'; import { LOGGER_KEY } from '@aws-lambda-powertools/commons'; import type { MiddlewareLikeObj, @@ -33,7 +33,7 @@ import type { */ const injectLambdaContext = ( target: Logger | Logger[], - options?: HandlerOptions + options?: InjectLambdaContextOptions ): MiddlewareLikeObj => { const loggers = target instanceof Array ? target : [target]; const persistentAttributes: LogAttributes[] = []; diff --git a/packages/logger/src/config/ConfigServiceInterface.ts b/packages/logger/src/types/ConfigServiceInterface.ts similarity index 100% rename from packages/logger/src/config/ConfigServiceInterface.ts rename to packages/logger/src/types/ConfigServiceInterface.ts diff --git a/packages/logger/src/types/Log.ts b/packages/logger/src/types/Log.ts index 931ef10027..dd15a69f72 100644 --- a/packages/logger/src/types/Log.ts +++ b/packages/logger/src/types/Log.ts @@ -1,3 +1,6 @@ +import type { LogItem } from '../formatter/LogItem.js'; +import type { UnformattedAttributes } from './Logger.js'; + type LogLevelDebug = 'DEBUG'; type LogLevelInfo = 'INFO'; type LogLevelWarn = 'WARN'; @@ -32,6 +35,137 @@ type LogAttributesWithMessage = LogAttributes & { type Environment = 'dev' | 'local' | 'staging' | 'prod' | string; +type PowertoolsLog = LogAttributes & { + /** + * Timestamp of actual log statement. + * + * @example "2020-05-24 18:17:33,774" + */ + timestamp?: string; + + /** + * Log level + * + * @example "INFO" + */ + level?: LogLevel; + + /** + * Service name defined. + * + * @example "payment" + */ + service: string; + + /** + * The value of the logging sampling rate in percentage. + * + * @example 0.1 + */ + sampling_rate?: number; + + /** + * Log statement value. Unserializable JSON values will be cast to string. + * + * @example "Collecting payment" + */ + message?: string; + + /** + * X-Ray Trace ID set by the Lambda runtime. + * + * @example "1-5759e988-bd862e3fe1be46a994272793" + */ + xray_trace_id?: string; + + /** + * Indicates whether the current execution experienced a cold start. + * + * @example false + */ + cold_start?: boolean; + + /** + * The name of the Lambda function. + * + * @example "example-powertools-HelloWorldFunction-1P1Z6B39FLU73" + */ + lambda_function_name?: string; + + /** + * The memory size of the Lambda function. + * + * Description: + * Example: 128 + */ + lambda_function_memory_size?: number; + + /** + * lambda_function_arn + * + * Description: The ARN of the Lambda function. + * Example: "arn:aws:lambda:eu-west-1:012345678910:function:example-powertools-HelloWorldFunction-1P1Z6B39FLU73" + */ + lambda_function_arn?: string; + + /** + * lambda_request_id + * + * Description: The request ID of the current invocation. + * Example: "899856cb-83d1-40d7-8611-9e78f15f32f4" + */ + lambda_request_id?: string; +}; + +interface LogItemInterface { + addAttributes(attributes: LogAttributes): void; + getAttributes(): LogAttributes; + prepareForPrint(): void; + removeEmptyKeys(attributes: LogAttributes): LogAttributes; + setAttributes(attributes: LogAttributes): void; +} + +/** + * @interface + */ +interface LogFormatterInterface { + /** + * It formats key-value pairs of log attributes. + * + * @param {UnformattedAttributes} attributes + * @param {LogAttributes} additionalLogAttributes + * @returns {LogItem} + */ + formatAttributes( + attributes: UnformattedAttributes, + additionalLogAttributes: LogAttributes + ): LogItem; + + /** + * It formats a given Error parameter. + * + * @param {Error} error + * @returns {LogAttributes} + */ + formatError(error: Error): LogAttributes; + + /** + * It formats a date into a string in simplified extended ISO format (ISO 8601). + * + * @param {Date} now + * @returns {string} + */ + formatTimestamp(now: Date): string; + + /** + * It returns a string containing the location of an error, given a particular stack trace. + * + * @param stack + * @returns {string} + */ + getCodeLocation(stack?: string): string; +} + export type { LogAttributesWithMessage, LogAttributeValue, @@ -39,4 +173,7 @@ export type { LogLevelThresholds, LogAttributes, LogLevel, + PowertoolsLog, + LogItemInterface, + LogFormatterInterface, }; diff --git a/packages/logger/src/types/Logger.ts b/packages/logger/src/types/Logger.ts index c937d66542..67d22a2963 100644 --- a/packages/logger/src/types/Logger.ts +++ b/packages/logger/src/types/Logger.ts @@ -1,26 +1,22 @@ +import type { HandlerMethodDecorator } from '@aws-lambda-powertools/commons/types'; +import type { ConfigServiceInterface } from './ConfigServiceInterface.js'; import type { - AsyncHandler, - LambdaInterface, - SyncHandler, -} from '@aws-lambda-powertools/commons/types'; -import { Handler } from 'aws-lambda'; -import { ConfigServiceInterface } from '../config/ConfigServiceInterface.js'; -import { LogFormatterInterface } from '../formatter/LogFormatterInterface.js'; -import { Environment, LogAttributes, LogAttributesWithMessage, LogLevel, + LogFormatterInterface, } from './Log.js'; +import type { Context } from 'aws-lambda'; -type ClassThatLogs = { +type LogFunction = { [key in Exclude, 'silent'>]: ( input: LogItemMessage, ...extraInput: LogItemExtraInput ) => void; }; -type HandlerOptions = { +type InjectLambdaContextOptions = { logEvent?: boolean; clearState?: boolean; }; @@ -35,32 +31,28 @@ type ConstructorOptions = { environment?: Environment; }; -type LambdaFunctionContext = { - functionName: string; - memoryLimitInMB: number; - functionVersion: string; +type LambdaFunctionContext = Pick< + Context, + | 'functionName' + | 'memoryLimitInMB' + | 'functionVersion' + | 'invokedFunctionArn' + | 'awsRequestId' +> & { coldStart: boolean; - invokedFunctionArn: string; - awsRequestId: string; }; -type PowertoolLogData = LogAttributes & { +type PowertoolsLogData = LogAttributes & { environment?: Environment; serviceName: string; sampleRateValue: number; - lambdaFunctionContext: LambdaFunctionContext; + lambdaContext?: LambdaFunctionContext; xRayTraceId?: string; awsRegion: string; }; -type UnformattedAttributes = { - environment?: Environment; +type UnformattedAttributes = PowertoolsLogData & { error?: Error; - serviceName: string; - sampleRateValue?: number; - lambdaContext?: LambdaFunctionContext; - xRayTraceId?: string; - awsRegion: string; logLevel: LogLevel; timestamp: Date; message: string; @@ -69,27 +61,39 @@ type UnformattedAttributes = { type LogItemMessage = string | LogAttributesWithMessage; type LogItemExtraInput = [Error | string] | LogAttributes[]; -type HandlerMethodDecorator = ( - target: LambdaInterface, - propertyKey: string | symbol, - descriptor: - | TypedPropertyDescriptor> - | TypedPropertyDescriptor> -) => void; +type LoggerInterface = { + addContext(context: Context): void; + addPersistentLogAttributes(attributes?: LogAttributes): void; + appendKeys(attributes?: LogAttributes): void; + createChild(options?: ConstructorOptions): LoggerInterface; + critical(input: LogItemMessage, ...extraInput: LogItemExtraInput): void; + debug(input: LogItemMessage, ...extraInput: LogItemExtraInput): void; + error(input: LogItemMessage, ...extraInput: LogItemExtraInput): void; + getLevelName(): Uppercase; + getLogEvent(): boolean; + getPersistentLogAttributes(): LogAttributes; + info(input: LogItemMessage, ...extraInput: LogItemExtraInput): void; + injectLambdaContext( + options?: InjectLambdaContextOptions + ): HandlerMethodDecorator; + logEventIfEnabled(event: unknown, overwriteValue?: boolean): void; + refreshSampleRateCalculation(): void; + removeKeys(keys?: string[]): void; + removePersistentLogAttributes(keys?: string[]): void; + setLogLevel(logLevel: LogLevel): void; + setPersistentLogAttributes(attributes?: LogAttributes): void; + shouldLogEvent(overwriteValue?: boolean): boolean; + warn(input: LogItemMessage, ...extraInput: LogItemExtraInput): void; +}; export { - ClassThatLogs, + LogFunction, + LoggerInterface, LogItemMessage, LogItemExtraInput, - HandlerMethodDecorator, LambdaFunctionContext, UnformattedAttributes, - PowertoolLogData, + PowertoolsLogData, ConstructorOptions, - HandlerOptions, + InjectLambdaContextOptions, }; - -export const enum LogJsonIndent { - PRETTY = 4, - COMPACT = 0, -} diff --git a/packages/logger/src/types/formats/PowertoolsLog.ts b/packages/logger/src/types/formats/PowertoolsLog.ts deleted file mode 100644 index 9406a2a5fe..0000000000 --- a/packages/logger/src/types/formats/PowertoolsLog.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { LogAttributes, LogLevel } from '../Log.js'; - -type PowertoolsLog = LogAttributes & { - /** - * timestamp - * - * Description: Timestamp of actual log statement. - * Example: "2020-05-24 18:17:33,774" - */ - timestamp?: string; - - /** - * level - * - * Description: Logging level - * Example: "INFO" - */ - level?: LogLevel; - - /** - * service - * - * Description: Service name defined. - * Example: "payment" - */ - service: string; - - /** - * sampling_rate - * - * Description: The value of the logging sampling rate in percentage. - * Example: 0.1 - */ - sampling_rate?: number; - - /** - * message - * - * Description: Log statement value. Unserializable JSON values will be cast to string. - * Example: "Collecting payment" - */ - message?: string; - - /** - * xray_trace_id - * - * Description: X-Ray Trace ID when Lambda function has enabled Tracing. - * Example: "1-5759e988-bd862e3fe1be46a994272793" - */ - xray_trace_id?: string; - - /** - * cold_start - * - * Description: Indicates whether the current execution experienced a cold start. - * Example: false - */ - cold_start?: boolean; - - /** - * lambda_function_name - * - * Description: The name of the Lambda function. - * Example: "example-powertools-HelloWorldFunction-1P1Z6B39FLU73" - */ - lambda_function_name?: string; - - /** - * lambda_function_memory_size - * - * Description: The memory size of the Lambda function. - * Example: 128 - */ - lambda_function_memory_size?: number; - - /** - * lambda_function_arn - * - * Description: The ARN of the Lambda function. - * Example: "arn:aws:lambda:eu-west-1:012345678910:function:example-powertools-HelloWorldFunction-1P1Z6B39FLU73" - */ - lambda_function_arn?: string; - - /** - * lambda_request_id - * - * Description: The request ID of the current invocation. - * Example: "899856cb-83d1-40d7-8611-9e78f15f32f4" - */ - lambda_request_id?: string; -}; - -export type { PowertoolsLog }; diff --git a/packages/logger/src/types/formats/index.ts b/packages/logger/src/types/formats/index.ts deleted file mode 100644 index 03dabd2013..0000000000 --- a/packages/logger/src/types/formats/index.ts +++ /dev/null @@ -1 +0,0 @@ -export type { PowertoolsLog } from './PowertoolsLog.js'; diff --git a/packages/logger/src/types/index.ts b/packages/logger/src/types/index.ts index 9c01595ede..88f38933d1 100644 --- a/packages/logger/src/types/index.ts +++ b/packages/logger/src/types/index.ts @@ -8,13 +8,11 @@ export type { } from './Log.js'; export type { - ClassThatLogs, LogItemMessage, LogItemExtraInput, - HandlerMethodDecorator, LambdaFunctionContext, UnformattedAttributes, - PowertoolLogData, + PowertoolsLogData, ConstructorOptions, - HandlerOptions, + InjectLambdaContextOptions, } from './Logger.js'; diff --git a/packages/logger/tests/e2e/sampleRate.decorator.test.ts b/packages/logger/tests/e2e/sampleRate.decorator.test.ts index 567957f9cd..d6db70c8c0 100644 --- a/packages/logger/tests/e2e/sampleRate.decorator.test.ts +++ b/packages/logger/tests/e2e/sampleRate.decorator.test.ts @@ -82,16 +82,17 @@ describe(`Logger E2E tests, sample rate and injectLambdaContext()`, () => { if (logMessages.length === 1 && logMessages[0].includes('ERROR')) { countNotSampled++; } else if ( - logMessages.length === 5 && - logMessages[0].includes( - 'Setting log level to DEBUG due to sampling rate' - ) + (logMessages.length === 5 && + logMessages[0].includes( + 'Setting log level to DEBUG due to sampling rate' + )) || + logMessages.length === 4 ) { countSampled++; } else { console.error(`Log group ${logGroupName} contains missing log`); throw new Error( - 'Sampled log should have either 1 error log or 4 logs of all levels' + 'Sampled log should have either 1 error log or 5 logs of all levels' ); } } diff --git a/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts b/packages/logger/tests/unit/EnvironmentVariablesService.test.ts similarity index 97% rename from packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts rename to packages/logger/tests/unit/EnvironmentVariablesService.test.ts index 648d8cacf8..515ceeb03e 100644 --- a/packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts +++ b/packages/logger/tests/unit/EnvironmentVariablesService.test.ts @@ -1,9 +1,9 @@ /** * Test Logger EnvironmentVariablesService class * - * @group unit/logger/all + * @group unit/logger/config */ -import { EnvironmentVariablesService } from '../../../src/config/EnvironmentVariablesService.js'; +import { EnvironmentVariablesService } from '../../src/config/EnvironmentVariablesService.js'; describe('Class: EnvironmentVariablesService', () => { const ENVIRONMENT_VARIABLES = process.env; diff --git a/packages/logger/tests/unit/Logger.test.ts b/packages/logger/tests/unit/Logger.test.ts index dda715829d..c7f9001415 100644 --- a/packages/logger/tests/unit/Logger.test.ts +++ b/packages/logger/tests/unit/Logger.test.ts @@ -1,31 +1,39 @@ /** * Test Logger class * - * @group unit/logger/all + * @group unit/logger/logger */ import context from '@aws-lambda-powertools/testing-utils/context'; import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; import { Logger, LogFormatter } from '../../src/index.js'; -import { ConfigServiceInterface } from '../../src/config/ConfigServiceInterface.js'; +import { ConfigServiceInterface } from '../../src/types/ConfigServiceInterface.js'; import { EnvironmentVariablesService } from '../../src/config/EnvironmentVariablesService.js'; import { PowertoolsLogFormatter } from '../../src/formatter/PowertoolsLogFormatter.js'; import { LogLevelThresholds, LogLevel } from '../../src/types/Log.js'; -import { - ClassThatLogs, - LogJsonIndent, +import type { + LogFunction, ConstructorOptions, } from '../../src/types/Logger.js'; +import { LogJsonIndent } from '../../src/constants.js'; import type { Context } from 'aws-lambda'; -import { Console } from 'node:console'; const mockDate = new Date(1466424490000); const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate); const getConsoleMethod = ( method: string -): keyof Omit => +): keyof Omit => method === 'critical' ? 'error' - : (method.toLowerCase() as keyof Omit); + : (method.toLowerCase() as keyof Omit); +jest.mock('node:console', () => ({ + ...jest.requireActual('node:console'), + Console: jest.fn().mockImplementation(() => ({ + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + })), +})); describe('Class: Logger', () => { const ENVIRONMENT_VARIABLES = process.env; @@ -60,7 +68,7 @@ describe('Class: Logger', () => { expect(logger).toEqual( expect.objectContaining({ persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { sampleRateValue: 0, awsRegion: 'eu-west-1', environment: '', @@ -103,14 +111,19 @@ describe('Class: Logger', () => { logIndentation: 0, logFormatter: expect.any(PowertoolsLogFormatter), logLevel: 8, // 100% sample rate value changes log level to DEBUG - console: expect.any(Console), + console: expect.objectContaining({ + debug: expect.any(Function), + error: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + }), logLevelThresholds: { ...logLevelThresholds, }, persistentLogAttributes: { awsAccountId: '123456789', }, - powertoolLogData: { + powertoolsLogData: { awsRegion: 'eu-west-1', environment: 'prod', sampleRateValue: 1, @@ -139,12 +152,17 @@ describe('Class: Logger', () => { logIndentation: 0, logFormatter: expect.any(PowertoolsLogFormatter), logLevel: 12, - console: expect.any(Console), + console: expect.objectContaining({ + debug: expect.any(Function), + error: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + }), logLevelThresholds: { ...logLevelThresholds, }, persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { awsRegion: 'eu-west-1', environment: '', sampleRateValue: 0, @@ -167,7 +185,7 @@ describe('Class: Logger', () => { expect(logger).toEqual( expect.objectContaining({ persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { sampleRateValue: 0, awsRegion: 'eu-west-1', environment: '', @@ -195,7 +213,7 @@ describe('Class: Logger', () => { expect(logger).toEqual( expect.objectContaining({ persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { sampleRateValue: 0, awsRegion: 'eu-west-1', environment: '', @@ -223,7 +241,7 @@ describe('Class: Logger', () => { expect(logger).toEqual( expect.objectContaining({ persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { sampleRateValue: 0, awsRegion: 'eu-west-1', environment: '', @@ -251,7 +269,7 @@ describe('Class: Logger', () => { expect(logger).toEqual( expect.objectContaining({ persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { sampleRateValue: 0, awsRegion: 'eu-west-1', environment: '', @@ -284,12 +302,17 @@ describe('Class: Logger', () => { logIndentation: 0, logFormatter: expect.any(PowertoolsLogFormatter), logLevel: 12, - console: expect.any(Console), + console: expect.objectContaining({ + debug: expect.any(Function), + error: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + }), logLevelThresholds: { ...logLevelThresholds, }, persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { awsRegion: 'eu-west-1', environment: '', sampleRateValue: 0, @@ -312,7 +335,7 @@ describe('Class: Logger', () => { expect(logger).toEqual( expect.objectContaining({ persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { sampleRateValue: 1, awsRegion: 'eu-west-1', environment: '', @@ -366,7 +389,7 @@ describe('Class: Logger', () => { expect(logger).toEqual( expect.objectContaining({ persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { sampleRateValue: 0, awsRegion: 'eu-west-1', environment: 'dev', @@ -408,7 +431,7 @@ describe('Class: Logger', () => { version: '0.2.4', }, }, - powertoolLogData: { + powertoolsLogData: { sampleRateValue: 0, awsRegion: 'eu-west-1', environment: '', @@ -436,7 +459,7 @@ describe('Class: Logger', () => { expect(logger).toEqual( expect.objectContaining({ persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { sampleRateValue: 0, awsRegion: 'eu-west-1', environment: 'dev', @@ -468,7 +491,7 @@ describe('Class: Logger', () => { ['error', 'DOES', true, 'DOES', true, 'DOES', true, 'DOES', true], ['critical', 'DOES', true, 'DOES', true, 'DOES', true, 'DOES', true], ])( - 'Method: %p', + 'Method:', ( method: string, debugAction, @@ -480,7 +503,7 @@ describe('Class: Logger', () => { errorAction, errorPrints ) => { - const methodOfLogger = method as keyof ClassThatLogs; + const methodOfLogger = method as keyof LogFunction; describe('Feature: log level', () => { test(`when the level is DEBUG, it ${debugAction} print to stdout`, () => { @@ -488,10 +511,10 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'DEBUG', }); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(method)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(method) + ); // Act logger[methodOfLogger]('foo'); @@ -517,10 +540,10 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'INFO', }); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); // Act logger[methodOfLogger]('foo'); @@ -546,10 +569,10 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'WARN', }); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); // Act logger[methodOfLogger]('foo'); @@ -575,10 +598,10 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'ERROR', }); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); // Act logger[methodOfLogger]('foo'); @@ -604,10 +627,10 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'SILENT', }); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); // Act logger[methodOfLogger]('foo'); @@ -619,10 +642,10 @@ describe('Class: Logger', () => { // Prepare process.env.POWERTOOLS_LOG_LEVEL = methodOfLogger.toUpperCase(); const logger = new Logger(); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); // Act logger[methodOfLogger]('foo'); @@ -649,10 +672,10 @@ describe('Class: Logger', () => { logLevel: 'SILENT', sampleRateValue: 0, }); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); // Act if (logger[methodOfLogger]) { logger[methodOfLogger]('foo'); @@ -670,10 +693,10 @@ describe('Class: Logger', () => { logLevel: 'SILENT', sampleRateValue: 1, }); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); // Act if (logger[methodOfLogger]) { logger[methodOfLogger]('foo'); @@ -682,9 +705,9 @@ describe('Class: Logger', () => { // Assess expect(logger.level).toBe(8); expect(logger.getLevelName()).toBe('DEBUG'); - expect(consoleSpy).toBeCalledTimes(1); + expect(consoleSpy).toBeCalledTimes(method === 'debug' ? 2 : 1); expect(consoleSpy).toHaveBeenNthCalledWith( - 1, + method === 'debug' ? 2 : 1, JSON.stringify({ level: method.toUpperCase(), message: 'foo', @@ -705,10 +728,10 @@ describe('Class: Logger', () => { () => { // Prepare const logger = new Logger(); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); // Act if (logger[methodOfLogger]) { logger[methodOfLogger]('foo'); @@ -740,10 +763,10 @@ describe('Class: Logger', () => { logLevel: 'DEBUG', }); logger.addContext(context); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); // Act if (logger[methodOfLogger]) { logger[methodOfLogger]('foo'); @@ -757,7 +780,7 @@ describe('Class: Logger', () => { cold_start: true, function_arn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - function_memory_size: 128, + function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', level: method.toUpperCase(), @@ -778,10 +801,10 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'DEBUG', }); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); interface NestedObject { bool: boolean; str: string; @@ -997,10 +1020,10 @@ describe('Class: Logger', () => { aws_region: 'eu-west-1', }, }); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); // Act if (logger[methodOfLogger]) { logger[methodOfLogger]('foo'); @@ -1030,10 +1053,10 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'DEBUG', }); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); // Act if (logger[methodOfLogger]) { logger[methodOfLogger]('foo'); @@ -1060,10 +1083,10 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'DEBUG', }); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); - + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); // Act if (logger[methodOfLogger]) { logger[methodOfLogger]('foo'); @@ -1090,9 +1113,10 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'DEBUG', }); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); const circularObject = { foo: 'bar', self: {}, @@ -1132,9 +1156,7 @@ describe('Class: Logger', () => { test('when a logged item has BigInt value, it does not throw TypeError', () => { // Prepare const logger = new Logger(); - jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); + jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)); const message = `This is an ${methodOfLogger} log with BigInt value`; const logItem = { value: BigInt(42) }; const errorMessage = 'Do not know how to serialize a BigInt'; @@ -1148,9 +1170,10 @@ describe('Class: Logger', () => { test('when a logged item has a BigInt value, it prints the log with value as a string', () => { // Prepare const logger = new Logger(); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); const message = `This is an ${methodOfLogger} log with BigInt value`; const logItem = { value: BigInt(42) }; @@ -1176,9 +1199,10 @@ describe('Class: Logger', () => { test('when a logged item has empty string, null, or undefined values, it removes it', () => { // Prepare const logger = new Logger(); - const consoleSpy = jest - .spyOn(logger['console'], getConsoleMethod(methodOfLogger)) - .mockImplementation(); + const consoleSpy = jest.spyOn( + logger['console'], + getConsoleMethod(methodOfLogger) + ); const message = `This is an ${methodOfLogger} log with empty, null, and undefined values`; const logItem = { value: 42, @@ -1210,7 +1234,7 @@ describe('Class: Logger', () => { ); describe('Method: addContext', () => { - test('when called during a cold start invocation, it populates the logger PowertoolLogData object with coldStart set to TRUE', () => { + test('when called during a cold start invocation, it populates the logger powertoolsLogData object with coldStart set to TRUE', () => { // Prepare const logger = new Logger(); @@ -1218,36 +1242,18 @@ describe('Class: Logger', () => { logger.addContext(context); // Assess - expect(logger).toEqual({ - console: expect.any(Console), - coldStart: false, // This is now false because the `coldStart` attribute has been already accessed once by the `addContext` method - customConfigService: undefined, - defaultServiceName: 'service_undefined', - envVarsService: expect.any(EnvironmentVariablesService), - logEvent: false, - logIndentation: 0, - logFormatter: expect.any(PowertoolsLogFormatter), - logLevel: 8, - logLevelThresholds: { - ...logLevelThresholds, - }, - persistentLogAttributes: {}, - powertoolLogData: { - awsRegion: 'eu-west-1', - environment: '', - lambdaContext: { - awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', - coldStart: true, - functionName: 'foo-bar-function', - functionVersion: '$LATEST', - invokedFunctionArn: - 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - memoryLimitInMB: 128, - }, - sampleRateValue: 0, - serviceName: 'hello-world', - }, - }); + expect(logger).toEqual( + expect.objectContaining({ + coldStart: false, // This is now false because the `coldStart` attribute has been already accessed once by the `addContext` method + powertoolsLogData: expect.objectContaining({ + lambdaContext: expect.objectContaining({ + coldStart: true, + }), + sampleRateValue: 0, + serviceName: 'hello-world', + }), + }) + ); }); test('when called with a context object, the object is not mutated', () => { @@ -1294,7 +1300,7 @@ describe('Class: Logger', () => { // Assess expect(logger).toEqual( expect.objectContaining({ - powertoolLogData: expect.objectContaining({ + powertoolsLogData: expect.objectContaining({ lambdaContext: expect.objectContaining({ awsRequestId: context2.awsRequestId, }), @@ -1472,9 +1478,7 @@ describe('Class: Logger', () => { // Prepare const logger = new Logger(); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); + const consoleSpy = jest.spyOn(logger['console'], 'info'); class LambdaFunction implements LambdaInterface { @logger.injectLambdaContext() public async handler( @@ -1502,7 +1506,7 @@ describe('Class: Logger', () => { cold_start: true, function_arn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - function_memory_size: 128, + function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', level: 'INFO', @@ -1518,9 +1522,7 @@ describe('Class: Logger', () => { test('it captures Lambda context information and adds it in the printed logs', async () => { // Prepare const logger = new Logger(); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); + const consoleSpy = jest.spyOn(logger['console'], 'info'); class LambdaFunction implements LambdaInterface { @logger.injectLambdaContext() public async handler( @@ -1557,7 +1559,7 @@ describe('Class: Logger', () => { cold_start: true, function_arn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - function_memory_size: 128, + function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', level: 'INFO', @@ -1574,9 +1576,7 @@ describe('Class: Logger', () => { // Prepare const expectedReturnValue = 'Lambda invoked!'; const logger = new Logger(); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); + const consoleSpy = jest.spyOn(logger['console'], 'info'); class LambdaFunction implements LambdaInterface { @logger.injectLambdaContext() public async handler( @@ -1616,7 +1616,7 @@ describe('Class: Logger', () => { cold_start: true, function_arn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - function_memory_size: 128, + function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', level: 'INFO', @@ -1638,7 +1638,6 @@ describe('Class: Logger', () => { biz: 'baz', }, }); - jest.spyOn(logger['console'], 'debug').mockImplementation(); class LambdaFunction implements LambdaInterface { @logger.injectLambdaContext({ clearState: true }) public async handler( @@ -1684,7 +1683,6 @@ describe('Class: Logger', () => { biz: 'baz', }, }); - jest.spyOn(logger['console'], 'debug').mockImplementation(); class LambdaFunction implements LambdaInterface { @logger.injectLambdaContext({ clearState: true }) public async handler( @@ -1726,9 +1724,7 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'DEBUG', }); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); + const consoleSpy = jest.spyOn(logger['console'], 'info'); class LambdaFunction implements LambdaInterface { @logger.injectLambdaContext({ logEvent: true }) public async handler( @@ -1752,7 +1748,7 @@ describe('Class: Logger', () => { cold_start: true, function_arn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - function_memory_size: 128, + function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', level: 'INFO', @@ -1775,10 +1771,7 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'DEBUG', }); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'info'); class LambdaFunction implements LambdaInterface { @logger.injectLambdaContext() public async handler( @@ -1802,7 +1795,7 @@ describe('Class: Logger', () => { cold_start: true, function_arn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - function_memory_size: 128, + function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', level: 'INFO', @@ -1824,10 +1817,7 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'DEBUG', }); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'info'); class LambdaFunction implements LambdaInterface { private readonly memberVariable: string; @@ -1863,7 +1853,7 @@ describe('Class: Logger', () => { cold_start: true, function_arn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - function_memory_size: 128, + function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', level: 'INFO', @@ -1885,9 +1875,7 @@ describe('Class: Logger', () => { const logger = new Logger({ logLevel: 'DEBUG', }); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); + const consoleSpy = jest.spyOn(logger['console'], 'info'); class LambdaFunction implements LambdaInterface { @logger.injectLambdaContext() public async handler( @@ -1927,9 +1915,7 @@ describe('Class: Logger', () => { version: '1.0.0', }, }); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); + const consoleSpy = jest.spyOn(logger['console'], 'info'); class LambdaFunction implements LambdaInterface { @logger.injectLambdaContext({ clearState: true, logEvent: true }) public async handler( @@ -2004,7 +1990,12 @@ describe('Class: Logger', () => { expect(parentLogger === grandchildLogger).toBe(false); expect(parentLogger).toEqual({ - console: expect.any(Console), + console: expect.objectContaining({ + debug: expect.any(Function), + error: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + }), coldStart: true, customConfigService: undefined, defaultServiceName: 'service_undefined', @@ -2017,7 +2008,7 @@ describe('Class: Logger', () => { ...logLevelThresholds, }, persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { awsRegion: 'eu-west-1', environment: '', sampleRateValue: 0, @@ -2026,7 +2017,12 @@ describe('Class: Logger', () => { }); expect(childLogger).toEqual({ - console: expect.any(Console), + console: expect.objectContaining({ + debug: expect.any(Function), + error: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + }), coldStart: true, customConfigService: undefined, defaultServiceName: 'service_undefined', @@ -2039,7 +2035,7 @@ describe('Class: Logger', () => { ...logLevelThresholds, }, persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { awsRegion: 'eu-west-1', environment: '', sampleRateValue: 1, @@ -2048,7 +2044,12 @@ describe('Class: Logger', () => { }); expect(grandchildLogger).toEqual({ - console: expect.any(Console), + console: expect.objectContaining({ + debug: expect.any(Function), + error: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + }), coldStart: true, customConfigService: undefined, defaultServiceName: 'service_undefined', @@ -2061,7 +2062,7 @@ describe('Class: Logger', () => { ...logLevelThresholds, }, persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { awsRegion: 'eu-west-1', environment: '', sampleRateValue: 1, @@ -2089,7 +2090,7 @@ describe('Class: Logger', () => { ); const optionsWithSampleRateEnabled = { - sampleRateValue: 1, // 100% probability to make sure that the logs are sampled + sampleRateValue: 1, }; const childLoggerWithSampleRateEnabled = parentLogger.createChild( optionsWithSampleRateEnabled @@ -2106,14 +2107,24 @@ describe('Class: Logger', () => { expect(parentLogger === childLogger).toBe(false); expect(childLogger).toEqual({ ...parentLogger, - console: expect.any(Console), + console: expect.objectContaining({ + debug: expect.any(Function), + error: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + }), }); expect(parentLogger === childLoggerWithPermanentAttributes).toBe(false); expect(parentLogger === childLoggerWithSampleRateEnabled).toBe(false); expect(parentLogger === childLoggerWithErrorLogLevel).toBe(false); expect(parentLogger).toEqual({ - console: expect.any(Console), + console: expect.objectContaining({ + debug: expect.any(Function), + error: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + }), coldStart: true, customConfigService: undefined, defaultServiceName: 'service_undefined', @@ -2126,7 +2137,7 @@ describe('Class: Logger', () => { ...logLevelThresholds, }, persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { awsRegion: 'eu-west-1', environment: '', sampleRateValue: 0, @@ -2135,7 +2146,12 @@ describe('Class: Logger', () => { }); expect(childLoggerWithPermanentAttributes).toEqual({ - console: expect.any(Console), + console: expect.objectContaining({ + debug: expect.any(Function), + error: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + }), coldStart: true, customConfigService: undefined, defaultServiceName: 'service_undefined', @@ -2151,7 +2167,7 @@ describe('Class: Logger', () => { extra: 'This is an attribute that will be logged only by the child logger', }, - powertoolLogData: { + powertoolsLogData: { awsRegion: 'eu-west-1', environment: '', sampleRateValue: 0, @@ -2160,7 +2176,12 @@ describe('Class: Logger', () => { }); expect(childLoggerWithSampleRateEnabled).toEqual({ - console: expect.any(Console), + console: expect.objectContaining({ + debug: expect.any(Function), + error: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + }), coldStart: true, customConfigService: undefined, defaultServiceName: 'service_undefined', @@ -2173,7 +2194,7 @@ describe('Class: Logger', () => { ...logLevelThresholds, }, persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { awsRegion: 'eu-west-1', environment: '', sampleRateValue: 1, @@ -2182,7 +2203,12 @@ describe('Class: Logger', () => { }); expect(childLoggerWithErrorLogLevel).toEqual({ - console: expect.any(Console), + console: expect.objectContaining({ + debug: expect.any(Function), + error: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + }), coldStart: true, customConfigService: undefined, defaultServiceName: 'service_undefined', @@ -2195,7 +2221,7 @@ describe('Class: Logger', () => { ...logLevelThresholds, }, persistentLogAttributes: {}, - powertoolLogData: { + powertoolsLogData: { awsRegion: 'eu-west-1', environment: '', sampleRateValue: 0, @@ -2206,7 +2232,6 @@ describe('Class: Logger', () => { test('child logger should have same keys in persistentLogAttributes as its parent', () => { // Prepare - const INDENTATION = LogJsonIndent.COMPACT; const parentLogger = new Logger(); const childLogger = parentLogger.createChild(); @@ -2224,85 +2249,25 @@ describe('Class: Logger', () => { childLoggerWithKeys.removeKeys(['test_key']); // Assess - expect(childLogger).toEqual({ - console: expect.any(Console), - coldStart: true, - customConfigService: undefined, - defaultServiceName: 'service_undefined', - envVarsService: expect.any(EnvironmentVariablesService), - logEvent: false, - logIndentation: INDENTATION, - logFormatter: expect.any(PowertoolsLogFormatter), - logLevel: 8, - logLevelThresholds: { - ...logLevelThresholds, - }, - persistentLogAttributes: {}, - powertoolLogData: { - awsRegion: 'eu-west-1', - environment: '', - sampleRateValue: 0, - serviceName: 'hello-world', - }, - }); + expect(childLogger.getPersistentLogAttributes()).toEqual({}); - expect(childLoggerWithKeys).toEqual({ - console: expect.any(Console), - coldStart: true, - customConfigService: undefined, - defaultServiceName: 'service_undefined', - envVarsService: expect.any(EnvironmentVariablesService), - logEvent: false, - logIndentation: INDENTATION, - logFormatter: expect.any(PowertoolsLogFormatter), - logLevel: 8, - logLevelThresholds: { - ...logLevelThresholds, - }, - persistentLogAttributes: { - aws_account_id: '123456789012', - aws_region: 'eu-west-1', - logger: { - name: 'aws-lambda-powertool-typescript', - version: '0.2.4', - }, - }, - powertoolLogData: { - awsRegion: 'eu-west-1', - environment: '', - sampleRateValue: 0, - serviceName: 'hello-world', + expect(childLoggerWithKeys.getPersistentLogAttributes()).toEqual({ + aws_account_id: '123456789012', + aws_region: 'eu-west-1', + logger: { + name: 'aws-lambda-powertool-typescript', + version: '0.2.4', }, }); - expect(parentLogger).toEqual({ - console: expect.any(Console), - coldStart: true, - customConfigService: undefined, - defaultServiceName: 'service_undefined', - envVarsService: expect.any(EnvironmentVariablesService), - logEvent: false, - logIndentation: INDENTATION, - logFormatter: expect.any(PowertoolsLogFormatter), - logLevel: 8, - logLevelThresholds: { - ...logLevelThresholds, - }, - persistentLogAttributes: { - aws_account_id: '123456789012', - aws_region: 'eu-west-1', - logger: { - name: 'aws-lambda-powertool-typescript', - version: '0.2.4', - }, - test_key: 'key-for-test', - }, - powertoolLogData: { - awsRegion: 'eu-west-1', - environment: '', - sampleRateValue: 0, - serviceName: 'hello-world', + expect(parentLogger.getPersistentLogAttributes()).toEqual({ + aws_account_id: '123456789012', + aws_region: 'eu-west-1', + logger: { + name: 'aws-lambda-powertool-typescript', + version: '0.2.4', }, + test_key: 'key-for-test', }); }); @@ -2315,36 +2280,26 @@ describe('Class: Logger', () => { const childLoggerWithContext = parentLogger.createChild(); // Assess - expect(childLoggerWithContext).toEqual({ - console: expect.any(Console), - coldStart: false, // This is now false because the `coldStart` attribute has been already accessed once by the `addContext` method - customConfigService: undefined, - defaultServiceName: 'service_undefined', - envVarsService: expect.any(EnvironmentVariablesService), - logEvent: false, - logIndentation: 0, - logFormatter: expect.any(PowertoolsLogFormatter), - logLevel: 8, - logLevelThresholds: { - ...logLevelThresholds, - }, - persistentLogAttributes: {}, - powertoolLogData: { - awsRegion: 'eu-west-1', - environment: '', - lambdaContext: { - awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', - coldStart: true, - functionName: 'foo-bar-function', - functionVersion: '$LATEST', - invokedFunctionArn: - 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - memoryLimitInMB: 128, + expect(childLoggerWithContext).toEqual( + expect.objectContaining({ + coldStart: false, // This is now false because the `coldStart` attribute has been already accessed once by the `addContext` method + powertoolsLogData: { + awsRegion: 'eu-west-1', + environment: '', + lambdaContext: { + awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', + coldStart: true, + functionName: 'foo-bar-function', + functionVersion: '$LATEST', + invokedFunctionArn: + 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', + memoryLimitInMB: '128', + }, + sampleRateValue: 0, + serviceName: 'hello-world', }, - sampleRateValue: 0, - serviceName: 'hello-world', - }, - }); + }) + ); }); test('child logger should have the same logFormatter as its parent', () => { @@ -2414,7 +2369,12 @@ describe('Class: Logger', () => { // Assess expect(childLogger).toEqual({ ...parentLogger, - console: expect.any(Console), + console: expect.objectContaining({ + debug: expect.any(Function), + error: expect.any(Function), + info: expect.any(Function), + warn: expect.any(Function), + }), }); expect(childLogger).toEqual( @@ -2435,10 +2395,7 @@ describe('Class: Logger', () => { test('When the feature is disabled, it DOES NOT log the event', () => { // Prepare const logger = new Logger(); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'info'); // Act logger.logEventIfEnabled(event); @@ -2452,10 +2409,7 @@ describe('Class: Logger', () => { something: 'happened!', }; const logger = new Logger(); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'info'); // Act logger.logEventIfEnabled(event, true); @@ -2484,9 +2438,7 @@ describe('Class: Logger', () => { process.env.POWERTOOLS_DEV = 'true'; const INDENTATION = LogJsonIndent.PRETTY; const logger = new Logger(); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); + const consoleSpy = jest.spyOn(console, 'info').mockImplementation(); // Act logger.info('Message with pretty identation'); @@ -2513,10 +2465,7 @@ describe('Class: Logger', () => { test('when the `POWERTOOLS_DEV` env var is NOT SET it makes log output as one-liner', () => { // Prepare const logger = new Logger(); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'info'); // Act logger.info('Message without pretty identation'); @@ -2627,7 +2576,6 @@ describe('Class: Logger', () => { logLevel: 'ERROR', customConfigService: new MyCustomEnvironmentVariablesService(), }; - const logger: Logger = new Logger(loggerOptions); // Assess @@ -2675,11 +2623,7 @@ describe('Class: Logger', () => { customConfigService: new MyCustomEnvironmentVariablesService(), }; const logger: Logger = new Logger(loggerOptions); - - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'info'); // Act logger.info('foo'); @@ -2712,11 +2656,7 @@ describe('Class: Logger', () => { customConfigService: new MyCustomEnvironmentVariablesService(), }; const logger: Logger = new Logger(loggerOptions); - - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'info'); // Act logger.info('foo'); @@ -2739,17 +2679,25 @@ describe('Class: Logger', () => { // Prepare process.env.POWERTOOLS_LOGGER_SAMPLE_RATE = '1'; const logger: Logger = new Logger(); - const consoleSpy = jest - .spyOn(logger['console'], 'debug') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'debug'); // Act logger.debug('foo'); // Assess - expect(consoleSpy).toBeCalledTimes(1); + expect(consoleSpy).toBeCalledTimes(2); expect(consoleSpy).toHaveBeenNthCalledWith( 1, + JSON.stringify({ + level: 'DEBUG', + message: 'Setting log level to DEBUG due to sampling rate', + sampling_rate: 1, + service: 'hello-world', + timestamp: '2016-06-20T12:08:10.000Z', + xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793', + }) + ); + expect(consoleSpy).toHaveBeenNthCalledWith( + 2, JSON.stringify({ level: 'DEBUG', message: 'foo', @@ -2774,10 +2722,7 @@ describe('Class: Logger', () => { }; const logger: Logger = new Logger(loggerOptions); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'info'); // Act logger.info('foo'); @@ -2802,10 +2747,7 @@ describe('Class: Logger', () => { logLevel: 'INFO', sampleRateValue: 42, }); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'info'); // Act logger.info('foo'); @@ -2838,10 +2780,7 @@ describe('Class: Logger', () => { }; const logger: Logger = new Logger(loggerOptions); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'info'); // Act logger.info('foo'); @@ -2866,10 +2805,7 @@ describe('Class: Logger', () => { const logger: Logger = new Logger({ logLevel: 'INFO', }); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'info'); // Act logger.info('foo'); @@ -2895,10 +2831,7 @@ describe('Class: Logger', () => { logLevel: 'INFO', sampleRateValue: 1, }); - const consoleSpy = jest - .spyOn(logger['console'], 'info') - .mockImplementation(); - + const consoleSpy = jest.spyOn(logger['console'], 'info'); // Act logger.refreshSampleRateCalculation(); logger.info('foo'); @@ -2925,9 +2858,6 @@ describe('Class: Logger', () => { sampleRateValue: 0.1, // 10% probability }); - // suppress "Setting log level to DEBUG due to sampling rate" log messages - jest.spyOn(logger['console'], 'debug').mockImplementation(); - let logLevelChangedToDebug = 0; const numOfIterations = 1000; const minExpected = numOfIterations * 0.05; // Min expected based on 5% probability diff --git a/packages/logger/tests/unit/middleware/middy.test.ts b/packages/logger/tests/unit/middleware/middy.test.ts index 271701d11f..9b0c094f74 100644 --- a/packages/logger/tests/unit/middleware/middy.test.ts +++ b/packages/logger/tests/unit/middleware/middy.test.ts @@ -1,18 +1,15 @@ /** * Test Logger middleware * - * @group unit/logger/all + * @group unit/logger/middleware */ -import context from '@aws-lambda-powertools/testing-utils/context'; import { cleanupMiddlewares } from '@aws-lambda-powertools/commons'; -import { ConfigServiceInterface } from '../../../src/config/ConfigServiceInterface.js'; -import { EnvironmentVariablesService } from '../../../src/config/EnvironmentVariablesService.js'; -import { injectLambdaContext } from '../../../src/middleware/middy.js'; -import { Logger } from './../../../src/Logger.js'; +import context from '@aws-lambda-powertools/testing-utils/context'; import middy from '@middy/core'; -import { PowertoolsLogFormatter } from '../../../src/formatter/PowertoolsLogFormatter.js'; -import { Console } from 'node:console'; import type { Context } from 'aws-lambda'; +import { injectLambdaContext } from '../../../src/middleware/middy.js'; +import { ConfigServiceInterface } from '../../../src/types/ConfigServiceInterface.js'; +import { Logger } from './../../../src/Logger.js'; const mockDate = new Date(1466424490000); const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate); @@ -52,11 +49,7 @@ describe('Middy middleware', () => { // Assess expect(logger).toEqual( expect.objectContaining({ - persistentLogAttributes: {}, - powertoolLogData: { - sampleRateValue: 0, - awsRegion: 'eu-west-1', - environment: '', + powertoolsLogData: expect.objectContaining({ lambdaContext: { awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', coldStart: true, @@ -64,14 +57,9 @@ describe('Middy middleware', () => { functionVersion: '$LATEST', invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - memoryLimitInMB: 128, + memoryLimitInMB: '128', }, - serviceName: 'hello-world', - }, - envVarsService: expect.any(EnvironmentVariablesService), - customConfigService: undefined, - logLevel: 8, - logFormatter: expect.any(PowertoolsLogFormatter), + }), }) ); }); @@ -90,11 +78,7 @@ describe('Middy middleware', () => { // Assess const expectation = expect.objectContaining({ - persistentLogAttributes: {}, - powertoolLogData: { - sampleRateValue: 0, - awsRegion: 'eu-west-1', - environment: '', + powertoolsLogData: expect.objectContaining({ lambdaContext: { awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', coldStart: true, @@ -102,15 +86,9 @@ describe('Middy middleware', () => { functionVersion: '$LATEST', invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - memoryLimitInMB: 128, + memoryLimitInMB: '128', }, - serviceName: 'hello-world', - }, - envVarsService: expect.any(EnvironmentVariablesService), - customConfigService: undefined, - logLevel: 8, - logFormatter: expect.any(PowertoolsLogFormatter), - console: expect.any(Console), + }), }); expect(logger).toEqual(expectation); expect(anotherLogger).toEqual(expectation); @@ -273,7 +251,7 @@ describe('Middy middleware', () => { cold_start: true, function_arn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - function_memory_size: 128, + function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', level: 'INFO', @@ -343,7 +321,7 @@ describe('Middy middleware', () => { cold_start: true, function_arn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - function_memory_size: 128, + function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', level: 'INFO', @@ -382,7 +360,7 @@ describe('Middy middleware', () => { cold_start: true, function_arn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - function_memory_size: 128, + function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', level: 'INFO', @@ -421,7 +399,7 @@ describe('Middy middleware', () => { cold_start: true, function_arn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', - function_memory_size: 128, + function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', level: 'INFO', diff --git a/packages/tracer/tests/unit/Tracer.test.ts b/packages/tracer/tests/unit/Tracer.test.ts index 812f8ce14f..4b6031e092 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -20,7 +20,7 @@ type CaptureAsyncFuncMock = jest.SpyInstance< [ name: string, fcn: (subsegment?: Subsegment) => unknown, - parent?: Segment | Subsegment + parent?: Segment | Subsegment, ] >; const createCaptureAsyncFuncMock = function ( diff --git a/tsconfig.json b/tsconfig.json index a49b03e30e..c2eb4a63ed 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "incremental": true, "composite": true, - "target": "ES2021", // Node.js 16 + "target": "ES2022", // Node.js 16 "experimentalDecorators": true, "module": "commonjs", "moduleResolution": "node", // TODO: experiment with bundler & esnext