diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index 5947db21f1..de806ac146 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -1,117 +1,88 @@ import { Console } from 'node:console'; import { randomInt } from 'node:crypto'; import { Utility } from '@aws-lambda-powertools/commons'; -import type { HandlerMethodDecorator } from '@aws-lambda-powertools/commons/types'; +import type { + AsyncHandler, + HandlerMethodDecorator, + SyncHandler, +} from '@aws-lambda-powertools/commons/types'; import type { Context, Handler } from 'aws-lambda'; import merge from 'lodash.merge'; import { EnvironmentVariablesService } from './config/EnvironmentVariablesService.js'; import { LogJsonIndent } from './constants.js'; +import type { LogFormatter } from './formatter/LogFormatter.js'; import type { LogItem } from './formatter/LogItem.js'; import { PowertoolsLogFormatter } from './formatter/PowertoolsLogFormatter.js'; import type { ConfigServiceInterface } from './types/ConfigServiceInterface.js'; -import type { - Environment, - LogAttributes, - LogFormatterInterface, - LogLevel, - LogLevelThresholds, -} from './types/Log.js'; import type { ConstructorOptions, CustomJsonReplacerFn, InjectLambdaContextOptions, + LogAttributes, LogFunction, LogItemExtraInput, LogItemMessage, - LogRecordOrder, + LogLevel, + LogLevelThresholds, LoggerInterface, - PowertoolsLogData, } from './types/Logger.js'; +import type { PowertoolsLogData } from './types/logKeys.js'; /** - * ## Intro - * The Logger utility provides an opinionated logger with output structured as JSON. - * - * ## Key features - * * Capture key fields from Lambda context, cold start and structures logging output as JSON - * * Log Lambda context when instructed (disabled by default) - * * Log sampling prints all logs for a percentage of invocations (disabled by default) - * * Append additional keys to structured log at any point in time + * The Logger utility provides an opinionated logger with output structured as JSON for AWS Lambda. * - * ## Usage + * **Key features** + * * Capture key fields from AWS Lambda context, cold start, and structure log output as JSON + * * Append additional keys to one or all log items + * * Switch log level to `DEBUG` based on a sample rate value for a percentage of invocations * - * For more usage examples, see [our documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/core/logger/). - * - * ### Basic usage + * After initializing the Logger class, you can use the methods to log messages at different levels. * * @example * ```typescript * import { Logger } from '@aws-lambda-powertools/logger'; * - * // Logger parameters fetched from the environment variables: - * const logger = new Logger(); - * ``` - * - * ### Functions usage with middleware + * const logger = new Logger({ serviceName: 'serverlessAirline' }); * - * If you use function-based Lambda handlers you can use the {@link injectLambdaContext} middleware - * to automatically add context to your Lambda logs. - * - * @example - * ```typescript - * import { Logger } from '@aws-lambda-powertools/logger'; - * import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware'; - * import middy from '@middy/core'; - * - * const logger = new Logger(); - * - * const lambdaHandler = async (_event: unknown, _context: unknown) => { - * logger.info('This is an INFO log with some context'); + * export const handler = async (event, context) => { + * logger.info('This is an INFO log'); + * logger.warn('This is a WARNING log'); * }; - * - * export const handler = middy(lambdaHandler).use(injectLambdaContext(logger)); * ``` * - * ### Object oriented usage with decorators - * - * If instead you use TypeScript classes to wrap your Lambda handler you can use the {@link injectLambdaContext} decorator. + * To enrich the log items with information from the Lambda context, you can use the {@link Logger.addContext | addContext()} method. * * @example * ```typescript * import { Logger } from '@aws-lambda-powertools/logger'; - * import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; * - * const logger = new Logger(); + * const logger = new Logger({ serviceName: 'serverlessAirline' }); * - * class Lambda implements LambdaInterface { - * // Decorate your handler class method - * ⁣@logger.injectLambdaContext() - * public async handler(_event: unknown, _context: unknown): Promise { - * logger.info('This is an INFO log with some context'); - * } - * } + * export const handler = async (event, context) => { + * logger.addContext(context); * - * const handlerClass = new Lambda(); - * export const handler = handlerClass.handler.bind(handlerClass); + * logger.info('This is an INFO log with some context'); + * }; * ``` * - * ### Functions usage with manual instrumentation - * - * If you prefer to manually instrument your Lambda handler you can use the methods in the Logger class directly. + * You can also add additional attributes to all log items using the {@link Logger.appendKeys | appendKeys()} method. * * @example * ```typescript - * import { Logger } from '@aws-lambda-powertools/logger'; + * export const handler = async (event, context) => { + * logger.appendKeys({ key1: 'value1' }); * - * const logger = new Logger(); + * logger.info('This is an INFO log with additional keys'); * - * export const handler = async (_event, context) => { - * logger.addContext(context); - * logger.info('This is an INFO log with some context'); + * logger.removeKeys(['key1']); * }; - * ``` + *``` + * + * If you write your functions as classes and use TypeScript, you can use the {@link Logger.injectLambdaContext} class method decorator + * to automatically add context to your logs and clear the state after the invocation. + * + * If instead you use Middy.js middlewares, you use the {@link "middleware/middy".injectLambdaContext | injectLambdaContext()} middleware. * - * @class * @see https://docs.powertools.aws.dev/lambda/typescript/latest/core/logger/ */ class Logger extends Utility implements LoggerInterface { @@ -123,8 +94,6 @@ class Logger extends Utility implements LoggerInterface { * default console instance. * * This property is initialized in the constructor in setOptions(). - * - * @private */ private console!: Console; /** @@ -143,7 +112,7 @@ class Logger extends Utility implements LoggerInterface { * Formatter used to format the log items. * @default new PowertoolsLogFormatter() */ - private logFormatter?: LogFormatterInterface; + private logFormatter: ConstructorOptions['logFormatter']; /** * JSON indentation used to format the logs. */ @@ -217,11 +186,6 @@ class Logger extends Utility implements LoggerInterface { return this.logLevel; } - /** - * Initialize the Logger class with an optional set of options (settings). - * - * @param {ConstructorOptions} options - The options to initialize the logger with. - */ public constructor(options: ConstructorOptions = {}) { super(); const { customConfigService, ...rest } = options; @@ -240,7 +204,7 @@ class Logger extends Utility implements LoggerInterface { * Add the current Lambda function's invocation context data to the powertoolLogData property of the instance. * This context data will be part of all printed log items. * - * @param {Context} context - The Lambda function's invocation context. + * @param context - The Lambda function's invocation context. */ public addContext(context: Context): void { this.addToPowertoolsLogData({ @@ -260,7 +224,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()} instead. * - * @param {LogAttributes} attributes - The attributes to add to all log items. + * @param attributes - The attributes to add to all log items. */ public addPersistentLogAttributes(attributes: LogAttributes): void { this.appendPersistentKeys(attributes); @@ -269,7 +233,7 @@ class Logger extends Utility implements LoggerInterface { /** * Add the given temporary attributes (key-value pairs) to all log items generated by this Logger instance. * - * @param {LogAttributes} attributes + * @param attributes - The attributes to add to all log items. */ public appendKeys(attributes: LogAttributes): void { for (const attributeKey of Object.keys(attributes)) { @@ -294,7 +258,7 @@ class Logger extends Utility implements LoggerInterface { * Create a separate Logger instance, identical to the current one. * It's possible to overwrite the new instance options by passing them. * - * @param {ConstructorOptions} options - The options to initialize the child logger with. + * @param options - The options to initialize the child logger with. */ public createChild(options: ConstructorOptions = {}): Logger { const childLogger = this.createLogger( @@ -327,8 +291,8 @@ class Logger extends Utility implements LoggerInterface { /** * Print a log item with level CRITICAL. * - * @param {LogItemMessage} input - The log message. - * @param {Error | LogAttributes | string} extraInput - The extra input to log. + * @param input - The log message. + * @param extraInput - The extra input to log. */ public critical( input: LogItemMessage, @@ -340,8 +304,8 @@ class Logger extends Utility implements LoggerInterface { /** * Print a log item with level DEBUG. * - * @param {LogItemMessage} input - * @param {Error | LogAttributes | string} extraInput - The extra input to log. + * @param input + * @param extraInput - The extra input to log. */ public debug(input: LogItemMessage, ...extraInput: LogItemExtraInput): void { this.processLogItem(this.logLevelThresholds.DEBUG, input, extraInput); @@ -350,8 +314,8 @@ class Logger extends Utility implements LoggerInterface { /** * Print a log item with level ERROR. * - * @param {LogItemMessage} input - The log message. - * @param {Error | LogAttributes | string} extraInput - The extra input to log. + * @param input - The log message. + * @param extraInput - The extra input to log. */ public error(input: LogItemMessage, ...extraInput: LogItemExtraInput): void { this.processLogItem(this.logLevelThresholds.ERROR, input, extraInput); @@ -386,36 +350,33 @@ class Logger extends Utility implements LoggerInterface { /** * Print a log item with level INFO. * - * @param {LogItemMessage} input - The log message. - * @param {Error | LogAttributes | string} extraInput - The extra input to log. + * @param input - The log message. + * @param extraInput - The extra input to log. */ public info(input: LogItemMessage, ...extraInput: LogItemExtraInput): void { this.processLogItem(this.logLevelThresholds.INFO, input, extraInput); } /** - * Method decorator that adds the current Lambda function context as extra + * Class method decorator that adds the current Lambda function context as extra * information in all log items. * - * The decorator can be used only when attached to a Lambda function handler which - * is written as method of a class, and should be declared just before the handler declaration. - * - * Note: Currently TypeScript only supports decorators on classes and methods. If you are using the - * function syntax, you should use the middleware instead. + * This decorator is useful when you want to add the Lambda context to all log items + * and it works only when decorating a class method that is a Lambda function handler. * * @example * ```typescript * import { Logger } from '@aws-lambda-powertools/logger'; * import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; * - * const logger = new Logger(); + * const logger = new Logger({ serviceName: 'serverlessAirline' }); * * class Lambda implements LambdaInterface { - * // Decorate your handler class method - * ⁣@logger.injectLambdaContext() - * public async handler(_event: unknown, _context: unknown): Promise { - * logger.info('This is an INFO log with some context'); - * } + * // Decorate your handler class method + * ⁣@logger.injectLambdaContext() + * public async handler(_event: unknown, _context: unknown): Promise { + * logger.info('This is an INFO log with some context'); + * } * } * * const handlerClass = new Lambda(); @@ -428,8 +389,9 @@ class Logger extends Utility implements LoggerInterface { options?: InjectLambdaContextOptions ): HandlerMethodDecorator { return (_target, _propertyKey, descriptor) => { - // biome-ignore lint/style/noNonNullAssertion: The descriptor.value is the method this decorator decorates, it cannot be undefined. - const originalMethod = descriptor.value!; + const originalMethod = descriptor.value as + | SyncHandler + | AsyncHandler; const loggerRef = this; // Use a function() {} instead of an () => {} arrow function so that we can // access `myClass` as `this` in a decorated `myClass.myMethod()`. @@ -467,6 +429,9 @@ class Logger extends Utility implements LoggerInterface { } } + /** + * @deprecated - This method is deprecated and will be removed in the next major version. + */ public static injectLambdaContextBefore( logger: Logger, event: unknown, @@ -499,8 +464,8 @@ class Logger extends Utility implements LoggerInterface { * } * ``` * - * @param {unknown} event - The AWS Lambda event payload. - * @param {boolean} overwriteValue - Overwrite the environment variable value. + * @param event - The AWS Lambda event payload. + * @param overwriteValue - Overwrite the environment variable value. */ public logEventIfEnabled(event: unknown, overwriteValue?: boolean): void { if (!this.shouldLogEvent(overwriteValue)) return; @@ -519,7 +484,7 @@ class Logger extends Utility implements LoggerInterface { /** * Remove temporary attributes based on provided keys to all log items generated by this Logger instance. * - * @param {string[]} keys - The keys to remove. + * @param keys - The keys to remove. */ public removeKeys(keys: string[]): void { for (const key of keys) { @@ -566,7 +531,7 @@ 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 {string[]} keys - The keys to remove. + * @param keys - The keys to remove. */ public removePersistentLogAttributes(keys: string[]): void { this.removePersistentKeys(keys); @@ -609,7 +574,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()} instead. * - * @param {LogAttributes} attributes - The attributes to set. + * @param attributes - The attributes to set. */ public setPersistentLogAttributes(attributes: LogAttributes): void { this.persistentLogAttributes = attributes; @@ -618,8 +583,7 @@ class Logger extends Utility implements LoggerInterface { /** * Check whether the current Lambda invocation event should be printed in the logs or not. * - * @private - * @param {boolean} [overwriteValue] - Overwrite the environment variable value. + * @param overwriteValue - Overwrite the environment variable value. */ public shouldLogEvent(overwriteValue?: boolean): boolean { if (typeof overwriteValue === 'boolean') { @@ -632,8 +596,8 @@ class Logger extends Utility implements LoggerInterface { /** * Print a log item with level TRACE. * - * @param {LogItemMessage} input - The log message. - * @param {Error | LogAttributes | string} extraInput - The extra input to log. + * @param input - The log message. + * @param extraInput - The extra input to log. */ public trace(input: LogItemMessage, ...extraInput: LogItemExtraInput): void { this.processLogItem(this.logLevelThresholds.TRACE, input, extraInput); @@ -642,8 +606,8 @@ class Logger extends Utility implements LoggerInterface { /** * Print a log item with level WARN. * - * @param {LogItemMessage} input - The log message. - * @param {Error | LogAttributes | string} extraInput - The extra input to log. + * @param input - The log message. + * @param extraInput - The extra input to log. */ public warn(input: LogItemMessage, ...extraInput: LogItemExtraInput): void { this.processLogItem(this.logLevelThresholds.WARN, input, extraInput); @@ -668,7 +632,7 @@ class Logger extends Utility implements LoggerInterface { * } * ``` * - * @param {ConstructorOptions} [options] Logger configuration options. + * @param options - Logger configuration options. */ protected createLogger(options?: ConstructorOptions): Logger { return new Logger(options); @@ -712,9 +676,7 @@ class Logger extends Utility implements LoggerInterface { /** * Store information that is printed in all log items. * - * @param {Partial} attributes - * @private - * @returns {void} + * @param attributes - The attributes to add to all log items. */ private addToPowertoolsLogData(attributes: Partial): void { merge(this.powertoolsLogData, attributes); @@ -750,9 +712,9 @@ class Logger extends Utility implements LoggerInterface { * 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 + * @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 */ private createAndPopulateLogItem( logLevel: number, @@ -810,8 +772,6 @@ class Logger extends Utility implements LoggerInterface { /** * Get the custom config service, an abstraction used to fetch environment variables. - * - * @private */ private getCustomConfigService(): ConfigServiceInterface | undefined { return this.customConfigService; @@ -819,8 +779,6 @@ class Logger extends Utility implements LoggerInterface { /** * Get the instance of a service that fetches environment variables. - * - * @private */ private getEnvVarsService(): EnvironmentVariablesService { return this.envVarsService as EnvironmentVariablesService; @@ -829,11 +787,9 @@ class Logger extends Utility implements LoggerInterface { /** * Get the instance of a service that formats the structure of a * log item's keys and values in the desired way. - * - * @private */ - private getLogFormatter(): LogFormatterInterface { - return this.logFormatter as LogFormatterInterface; + private getLogFormatter(): LogFormatter { + return this.logFormatter as LogFormatter; } /** @@ -858,8 +814,6 @@ class Logger extends Utility implements LoggerInterface { /** * Get information that will be added in all log item by * this Logger instance (different from user-provided persistent attributes). - * - * @private */ private getPowertoolsLogData(): PowertoolsLogData { return this.powertoolsLogData; @@ -868,8 +822,7 @@ class Logger extends Utility implements LoggerInterface { /** * Check if a given log level is valid. * - * @param {LogLevel} logLevel - The log level to check - * @private + * @param logLevel - The log level to check */ private isValidLogLevel( logLevel?: LogLevel | string @@ -880,8 +833,7 @@ class Logger extends Utility implements LoggerInterface { /** * Check if a given sample rate value is valid. * - * @param sampleRateValue - * @private + * @param sampleRateValue - The sample rate value to check */ private isValidSampleRate( sampleRateValue?: number @@ -896,9 +848,8 @@ class Logger extends Utility implements LoggerInterface { /** * Print a given log with given log level. * - * @param {number} logLevel - The log level - * @param {LogItem} log - The log item to print - * @private + * @param logLevel - The log level + * @param log - The log item to print */ private printLog(logLevel: number, log: LogItem): void { log.prepareForPrint(); @@ -923,10 +874,9 @@ class Logger extends Utility implements LoggerInterface { /** * Print a given log with given log level. * - * @param {number} logLevel - The log level - * @param {LogItemMessage} input - The log message - * @param {LogItemExtraInput} extraInput - The extra input to log - * @private + * @param logLevel - The log level threshold + * @param input - The log message + * @param extraInput - The extra input to log */ private processLogItem( logLevel: number, @@ -948,8 +898,6 @@ class Logger extends Utility implements LoggerInterface { /** * Initialize the console property as an instance of the internal version of Console() class (PR #748) * or as the global node console if the `POWERTOOLS_DEV' env variable is set and has truthy value. - * - * @private */ private setConsole(): void { if (!this.getEnvVarsService().isDevMode()) { @@ -973,8 +921,7 @@ class Logger extends Utility implements LoggerInterface { * Set the Logger's customer config service instance, which will be used * to fetch environment variables. * - * @private - * @param {ConfigServiceInterface} customConfigService - The custom config service + * @param customConfigService - The custom config service */ private setCustomConfigService( customConfigService?: ConfigServiceInterface @@ -993,10 +940,9 @@ class Logger extends Utility implements LoggerInterface { * * If none of the above is true, the default log level applies (`INFO`). * - * @private - * @param {LogLevel} [logLevel] - Log level passed to the constructor + * @param logLevel - Log level passed to the constructor */ - private setInitialLogLevel(logLevel?: LogLevel): void { + private setInitialLogLevel(logLevel?: ConstructorOptions['logLevel']): void { const constructorLogLevel = logLevel?.toUpperCase(); if (this.awsLogLevelShortCircuit(constructorLogLevel)) return; @@ -1032,8 +978,7 @@ class Logger extends Utility implements LoggerInterface { * 3. Environment variable value * 4. Default value (zero) * - * @private - * @param {number} [sampleRateValue] - The sample rate value + * @param sampleRateValue - The sample rate value */ private setInitialSampleRate(sampleRateValue?: number): void { this.powertoolsLogData.sampleRateValue = 0; @@ -1067,8 +1012,6 @@ class Logger extends Utility implements LoggerInterface { /** * If the log event feature is enabled via env variable, it sets a property that tracks whether * the event passed to the Lambda function handler should be logged or not. - * - * @private */ private setLogEvent(): void { if (this.getEnvVarsService().getLogEvent()) { @@ -1080,13 +1023,12 @@ class Logger extends Utility implements LoggerInterface { * Set the log formatter instance, in charge of giving a custom format * to the structured logs, and optionally the ordering for keys within logs. * - * @private - * @param {LogFormatterInterface} logFormatter - The log formatter - * @param {LogRecordOrder} logRecordOrder - Optional list of keys to specify order in logs + * @param logFormatter - The log formatter + * @param logRecordOrder - Optional list of keys to specify order in logs */ private setLogFormatter( - logFormatter?: LogFormatterInterface, - logRecordOrder?: LogRecordOrder + logFormatter?: ConstructorOptions['logFormatter'], + logRecordOrder?: ConstructorOptions['logRecordOrder'] ): void { this.logFormatter = logFormatter ?? @@ -1099,8 +1041,6 @@ class Logger extends Utility implements LoggerInterface { /** * If the `POWERTOOLS_DEV` env variable is set, * add JSON indentation for pretty printing logs. - * - * @private */ private setLogIndentation(): void { if (this.getEnvVarsService().isDevMode()) { @@ -1112,7 +1052,6 @@ class Logger extends Utility implements LoggerInterface { * Configure the Logger instance settings that will affect the Logger's behaviour * and the content of all logs. * - * @private * @param options - Options to configure the Logger instance */ private setOptions( @@ -1160,15 +1099,14 @@ class Logger extends Utility implements LoggerInterface { /** * Add important data to the Logger instance that will affect the content of all logs. * - * @param {string} serviceName - The service name - * @param {Environment} environment - The environment - * @param {LogAttributes} persistentLogAttributes - The persistent log attributes - * @private + * @param serviceName - The service name + * @param environment - The environment + * @param persistentKeys - The persistent log attributes */ private setPowertoolsLogData( - serviceName?: string, - environment?: Environment, - persistentLogAttributes: LogAttributes = {} + serviceName?: ConstructorOptions['serviceName'], + environment?: ConstructorOptions['environment'], + persistentKeys: ConstructorOptions['persistentKeys'] = {} ): void { this.addToPowertoolsLogData({ awsRegion: this.getEnvVarsService().getAwsRegion(), @@ -1182,7 +1120,7 @@ class Logger extends Utility implements LoggerInterface { this.getEnvVarsService().getServiceName() || this.getDefaultServiceName(), }); - this.appendPersistentKeys(persistentLogAttributes); + this.appendPersistentKeys(persistentKeys); } } diff --git a/packages/logger/src/config/EnvironmentVariablesService.ts b/packages/logger/src/config/EnvironmentVariablesService.ts index 9097831556..e5759dd92c 100644 --- a/packages/logger/src/config/EnvironmentVariablesService.ts +++ b/packages/logger/src/config/EnvironmentVariablesService.ts @@ -9,8 +9,6 @@ import type { ConfigServiceInterface } from '../types/ConfigServiceInterface.js' * These variables can be a mix of runtime environment variables set by AWS and * variables that can be set by the developer additionally. * - * @class - * @extends {CommonEnvironmentVariablesService} * @see https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime * @see https://docs.powertools.aws.dev/lambda/typescript/latest/#environment-variables */ diff --git a/packages/logger/src/formatter/LogFormatter.ts b/packages/logger/src/formatter/LogFormatter.ts index d34c222666..7e34d71f4b 100644 --- a/packages/logger/src/formatter/LogFormatter.ts +++ b/packages/logger/src/formatter/LogFormatter.ts @@ -1,21 +1,20 @@ import type { EnvironmentVariablesService } from '../config/EnvironmentVariablesService.js'; -import type { - LogAttributes, - LogFormatterInterface, - LogFormatterOptions, -} from '../types/Log.js'; -import type { UnformattedAttributes } from '../types/Logger.js'; +import type { LogAttributes } from '../types/Logger.js'; +import type { LogFormatterOptions } from '../types/formatters.js'; +import type { UnformattedAttributes } from '../types/logKeys.js'; import type { LogItem } from './LogItem.js'; /** - * This class defines and implements common methods for the formatting of log attributes. + * Class that defines and implements common methods for the formatting of log attributes. * - * @class + * When creating a custom log formatter, you should extend this class and implement the + * {@link formatAttributes | formatAttributes()} method to define the structure of the log item. + * + * @abstract */ -abstract class LogFormatter implements LogFormatterInterface { +abstract class LogFormatter { /** - * EnvironmentVariablesService instance. - * If set, it allows to access environment variables. + * Instance of the {@link EnvironmentVariablesService} to use for configuration. */ protected envVarsService?: EnvironmentVariablesService; @@ -24,10 +23,59 @@ abstract class LogFormatter implements LogFormatterInterface { } /** - * It formats key-value pairs of log attributes. + * Format key-value pairs of log attributes. + * + * You should implement this method in a subclass to define the structure of the log item. + * + * @example + * ```typescript + * import { LogFormatter, LogItem } from '@aws-lambda-powertools/logger'; + * import type { + * LogAttributes, + * UnformattedAttributes, + * } from '@aws-lambda-powertools/logger/types'; + * + * class MyCompanyLogFormatter extends LogFormatter { + * public formatAttributes( + * attributes: UnformattedAttributes, + * additionalLogAttributes: LogAttributes + * ): LogItem { + * const baseAttributes: MyCompanyLog = { + * message: attributes.message, + * service: attributes.serviceName, + * environment: attributes.environment, + * awsRegion: attributes.awsRegion, + * correlationIds: { + * awsRequestId: attributes.lambdaContext?.awsRequestId, + * xRayTraceId: attributes.xRayTraceId, + * }, + * lambdaFunction: { + * name: attributes.lambdaContext?.functionName, + * arn: attributes.lambdaContext?.invokedFunctionArn, + * memoryLimitInMB: attributes.lambdaContext?.memoryLimitInMB, + * version: attributes.lambdaContext?.functionVersion, + * coldStart: attributes.lambdaContext?.coldStart, + * }, + * logLevel: attributes.logLevel, + * timestamp: this.formatTimestamp(attributes.timestamp), // You can extend this function + * logger: { + * sampleRateValue: attributes.sampleRateValue, + * }, + * }; + * + * const logItem = new LogItem({ attributes: baseAttributes }); + * // add any attributes not explicitly defined + * logItem.addAttributes(additionalLogAttributes); + * + * return logItem; + * } + * } * - * @param {UnformattedAttributes} attributes - unformatted attributes - * @param {LogAttributes} additionalLogAttributes - additional log attributes + * export { MyCompanyLogFormatter }; + * ``` + * + * @param attributes - Unformatted attributes + * @param additionalLogAttributes - Additional log attributes */ public abstract formatAttributes( attributes: UnformattedAttributes, @@ -35,10 +83,25 @@ abstract class LogFormatter implements LogFormatterInterface { ): LogItem; /** - * Format a given Error parameter. + * Format an error into a loggable object. + * + * @example + * ```json + * { + * "name": "Error", + * "location": "file.js:1", + * "message": "An error occurred", + * "stack": "Error: An error occurred\n at file.js:1\n at file.js:2\n at file.js:3", + * "cause": { + * "name": "OtherError", + * "location": "file.js:2", + * "message": "Another error occurred", + * "stack": "Error: Another error occurred\n at file.js:2\n at file.js:3\n at file.js:4" + * } + * } + * ``` * - * @param {Error} error - error to format - * @returns {LogAttributes} formatted error + * @param error - Error to format */ public formatError(error: Error): LogAttributes { return { @@ -54,11 +117,14 @@ abstract class LogFormatter implements LogFormatterInterface { } /** - * Format a given date into an ISO 8601 string, considering the configured timezone. - * If `envVarsService` is set and the configured timezone differs from 'UTC', - * the date is formatted to that timezone. Otherwise, it defaults to 'UTC'. + * Format a date into an ISO 8601 string with the configured timezone. + * + * If the log formatter is passed an {@link EnvironmentVariablesService} instance + * during construction, the timezone is read from the `TZ` environment variable, if present. * - * @param {Date} now - The date to format + * Otherwise, the timezone defaults to ':UTC'. + * + * @param now - The date to format */ public formatTimestamp(now: Date): string { const defaultTimezone = 'UTC'; @@ -75,9 +141,9 @@ abstract class LogFormatter implements LogFormatterInterface { } /** - * Get a string containing the location of an error, given a particular stack trace. + * Get the location of an error from a stack trace. * - * @param {string} stack - stack trace + * @param stack - stack trace to parse */ public getCodeLocation(stack?: string): string { if (!stack) { @@ -100,14 +166,16 @@ abstract class LogFormatter implements LogFormatterInterface { /** * Create a new Intl.DateTimeFormat object configured with the specified time zone - * and formatting options. The time is displayed in 24-hour format (hour12: false). + * and formatting options. + * + * The time is displayed in 24-hour format (hour12: false). * - * @param {string} timeZone - IANA time zone identifier (e.g., "Asia/Dhaka"). + * @param timezone - IANA time zone identifier (e.g., "Asia/Dhaka"). */ - #getDateFormatter = (timeZone: string): Intl.DateTimeFormat => { + #getDateFormatter = (timezone: string): Intl.DateTimeFormat => { const twoDigitFormatOption = '2-digit'; - const validTimeZone = Intl.supportedValuesOf('timeZone').includes(timeZone) - ? timeZone + const validTimeZone = Intl.supportedValuesOf('timeZone').includes(timezone) + ? timezone : 'UTC'; return new Intl.DateTimeFormat('en', { @@ -125,12 +193,12 @@ abstract class LogFormatter implements LogFormatterInterface { /** * Generate an ISO 8601 timestamp string with the specified time zone and the local time zone offset. * - * @param {Date} date - date to format - * @param {string} timeZone - IANA time zone identifier (e.g., "Asia/Dhaka"). + * @param date - date to format + * @param timezone - IANA time zone identifier (e.g., "Asia/Dhaka"). */ - #generateISOTimestampWithOffset(date: Date, timeZone: string): string { + #generateISOTimestampWithOffset(date: Date, timezone: string): string { const { year, month, day, hour, minute, second } = this.#getDateFormatter( - timeZone + timezone ) .formatToParts(date) .reduce( diff --git a/packages/logger/src/formatter/LogItem.ts b/packages/logger/src/formatter/LogItem.ts index 7d5f581f64..90516aae4f 100644 --- a/packages/logger/src/formatter/LogItem.ts +++ b/packages/logger/src/formatter/LogItem.ts @@ -1,14 +1,12 @@ import merge from 'lodash.merge'; -import type { LogAttributes, LogItemInterface } from '../types/Log.js'; +import type { LogAttributes } from '../types/Logger.js'; /** * LogItem is a class that holds the attributes of a log item. - * It is used to store the attributes of a log item and to add additional attributes to it. - * It is used by the LogFormatter to store the attributes of a log item. * - * @class + * It is used by {@link LogFormatter} to store the attributes of a log item and to add additional attributes to it. */ -class LogItem implements LogItemInterface { +class LogItem { /** * The attributes of the log item. */ @@ -16,20 +14,22 @@ class LogItem implements LogItemInterface { /** * Constructor for LogItem. - * @param {Object} params - The parameters for the LogItem. - * @param {LogAttributes} params.attributes - The initial attributes for the LogItem. + * + * Attributes are added in the following order: + * - Standard keys provided by the logger (e.g. `message`, `level`, `timestamp`) + * - Persistent attributes provided by developer, not formatted (done later) + * - Ephemeral attributes provided as parameters for a single log item (done later) + * + * @param params - The parameters for the LogItem. */ public constructor(params: { attributes: LogAttributes }) { - // Add attributes in the log item in this order: - // - Base attributes supported by the Powertool by default - // - Persistent attributes provided by developer, not formatted (done later) - // - Ephemeral attributes provided as parameters for a single log item (done later) this.addAttributes(params.attributes); } /** * Add attributes to the log item. - * @param {LogAttributes} attributes - The attributes to add to the log item. + * + * @param attributes - The attributes to add to the log item. */ public addAttributes(attributes: LogAttributes): this { merge(this.attributes, attributes); @@ -46,14 +46,18 @@ class LogItem implements LogItemInterface { /** * Prepare the log item for printing. + * + * 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())); } /** - * Remove empty keys from the log item. - * @param {LogAttributes} attributes - The attributes to remove empty keys from. + * Remove empty keys from the log item, where empty keys are defined as keys with + * values of `undefined`, empty strings (`''`), or `null`. + * + * @param attributes - The attributes to remove empty keys from. */ public removeEmptyKeys(attributes: LogAttributes): LogAttributes { const newAttributes: LogAttributes = {}; @@ -71,8 +75,9 @@ class LogItem implements LogItemInterface { } /** - * Set the attributes of the log item. - * @param {LogAttributes} attributes - The attributes to set for the log item. + * Replace the attributes of the log item. + * + * @param attributes - The attributes to set for the log item. */ public setAttributes(attributes: LogAttributes): void { this.attributes = attributes; diff --git a/packages/logger/src/formatter/PowertoolsLogFormatter.ts b/packages/logger/src/formatter/PowertoolsLogFormatter.ts index b69aab16c2..aae7ecbade 100644 --- a/packages/logger/src/formatter/PowertoolsLogFormatter.ts +++ b/packages/logger/src/formatter/PowertoolsLogFormatter.ts @@ -1,9 +1,13 @@ +import type { + LogRecordOrderKeys, + PowertoolsLogFormatterOptions, +} from '../types/formatters.js'; import type { LogAttributes, - PowerToolsLogFormatterOptions, - PowertoolsLog, -} from '../types/Log.js'; -import type { LogRecordOrder, UnformattedAttributes } from '../types/Logger.js'; + PowertoolsLambdaContextKeys, + PowertoolsStandardKeys, + UnformattedAttributes, +} from '../types/logKeys.js'; import { LogFormatter } from './LogFormatter.js'; import { LogItem } from './LogItem.js'; @@ -16,13 +20,14 @@ import { LogItem } from './LogItem.js'; */ class PowertoolsLogFormatter extends LogFormatter { /** - * An array of keys that defines the order of the log record. + * List of keys to order log attributes by. + * + * This can be a set of keys or an array of keys. */ - #logRecordOrder?: LogRecordOrder; + #logRecordOrder?: LogRecordOrderKeys; - public constructor(options?: PowerToolsLogFormatterOptions) { + public constructor(options?: PowertoolsLogFormatterOptions) { super(options); - this.#logRecordOrder = options?.logRecordOrder; } @@ -36,7 +41,9 @@ class PowertoolsLogFormatter extends LogFormatter { attributes: UnformattedAttributes, additionalLogAttributes: LogAttributes ): LogItem { - const baseAttributes: PowertoolsLog = { + const baseAttributes: Partial & + Partial & + LogAttributes = { cold_start: attributes.lambdaContext?.coldStart, function_arn: attributes.lambdaContext?.invokedFunctionArn, function_memory_size: attributes.lambdaContext?.memoryLimitInMB, @@ -57,8 +64,7 @@ class PowertoolsLogFormatter extends LogFormatter { ); } - const orderedAttributes = {} as PowertoolsLog; - + const orderedAttributes: LogAttributes = {}; // If logRecordOrder is set, order the attributes in the log item for (const key of this.#logRecordOrder) { if (key in baseAttributes && !(key in orderedAttributes)) { diff --git a/packages/logger/src/middleware/middy.ts b/packages/logger/src/middleware/middy.ts index 7514da013a..e399c1bae8 100644 --- a/packages/logger/src/middleware/middy.ts +++ b/packages/logger/src/middleware/middy.ts @@ -7,9 +7,10 @@ import { Logger } from '../Logger.js'; import type { InjectLambdaContextOptions } from '../types/Logger.js'; /** - * A middy middleware that helps emitting CloudWatch EMF metrics in your logs. + * A Middy.js-compatible middleware to enrich your logs with AWS Lambda context information. * - * Using this middleware on your handler function will automatically add context information to logs, as well as optionally log the event and clear attributes set during the invocation. + * Using this middleware on your handler function will automatically adds context information to logs, + * as well as optionally log the event and clear attributes set during the invocation. * * @example * ```typescript @@ -17,19 +18,47 @@ import type { InjectLambdaContextOptions } from '../types/Logger.js'; * import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware'; * import middy from '@middy/core'; * + * const logger = new Logger({ serviceName: 'serverlessAirline' }); * - * const logger = new Logger(); + * export const handler = middy(() => { + * logger.info('This is an INFO log with some context'); + * }).use(injectLambdaContext(logger)); + * ``` + * + * **Logging the event payload** + * + * When debugging, you might want to log the event payload to understand the input to your Lambda function. + * You can enable this by setting the `logEvent` option to `true` when creating the Logger instance. * - * const lambdaHandler = async (_event: any, _context: any) => { - * logger.info('This is an INFO log with some context'); - * }; + * @example + * ```typescript + * const logger = new Logger({ serviceName: 'serverlessAirline' }); * - * export const handler = middy(lambdaHandler).use(injectLambdaContext(logger)); + * export const handler = middy(() => { + * logger.info('This is an INFO log with some context'); + * }).use(injectLambdaContext(logger, { + * logEvent: true, + * })); * ``` * + * **Resetting state** + * + * To avoid leaking sensitive information across invocations, you can reset the keys added via + * {@link Logger.appendKeys()} by setting the `resetKeys` option to `true`. + * + * @example + * ```typescript + * const logger = new Logger({ serviceName: 'serverlessAirline' }); + * + * export const handler = middy(() => { + * logger.appendKeys({ key1: 'value1' }); + * logger.info('This is an INFO log with some context'); + * }).use(injectLambdaContext(logger, { + * resetKeys: true, + * })); + * * @param target - The Logger instance(s) to use for logging - * @param options - (_optional_) Options for the middleware - * @returns - The middy middleware object + * @param options - Options for the middleware such as clearing state or resetting keys */ const injectLambdaContext = ( target: Logger | Logger[], diff --git a/packages/logger/src/types/Log.ts b/packages/logger/src/types/Log.ts deleted file mode 100644 index 6e26008bed..0000000000 --- a/packages/logger/src/types/Log.ts +++ /dev/null @@ -1,193 +0,0 @@ -import type { EnvironmentVariablesService } from '../config/EnvironmentVariablesService.js'; -import type { LogLevel as LogLevelList } from '../constants.js'; -import type { LogItem } from '../formatter/LogItem.js'; -import type { LogRecordOrder, UnformattedAttributes } from './Logger.js'; - -type LogLevel = - | (typeof LogLevelList)[keyof typeof LogLevelList] - | Lowercase<(typeof LogLevelList)[keyof typeof LogLevelList]>; - -type LogLevelThresholds = { - [key in Uppercase]: number; -}; - -type LogAttributeValue = unknown; -type LogAttributes = { [key: string]: LogAttributeValue }; - -type LogAttributesWithMessage = LogAttributes & { - message: string; -}; - -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 - */ -interface LogItemInterface { - addAttributes(attributes: LogAttributes): void; - getAttributes(): LogAttributes; - prepareForPrint(): void; - removeEmptyKeys(attributes: LogAttributes): LogAttributes; - setAttributes(attributes: LogAttributes): void; -} - -/** - * Options for the `LogFormatter`. - * - * @type {Object} LogFormatterOptions - * @property {EnvironmentVariablesService} [envVarsService] - EnvironmentVariablesService instance. - */ -type LogFormatterOptions = { - /** - * EnvironmentVariablesService instance. - * If set, it gives the LogFormatter access to environment variables. - */ - envVarsService?: EnvironmentVariablesService; -}; - -/** - * Options for the `PowertoolsLogFormatter`. - * - * @type {Object} PowertoolsLogFormatterOptions - * @extends {LogFormatterOptions} - * @property {LogRecordOrder} [logRecordOrder] - Optional list of keys to specify order in logs - */ -type PowerToolsLogFormatterOptions = LogFormatterOptions & { - /** - * An array of keys that defines the order of the log record. - */ - logRecordOrder?: LogRecordOrder; -}; - -/** - * @interface - */ -interface LogFormatterInterface { - /** - * Format key-value pairs of log attributes. - * - * @param {UnformattedAttributes} attributes - Unformatted attributes - * @param {LogAttributes} additionalLogAttributes - Additional log attributes - */ - formatAttributes( - attributes: UnformattedAttributes, - additionalLogAttributes: LogAttributes - ): LogItem; - - /** - * Format a given Error parameter. - * - * @param {Error} error - Error to format - */ - formatError(error: Error): LogAttributes; - - /** - * Format a date into a string in simplified extended ISO format (ISO 8601). - * - * @param {Date} now - Date to format - */ - formatTimestamp(now: Date): string; - - /** - * Get a string containing the location of an error, given a particular stack trace. - * - * @param {string} stack - Stack trace - */ - getCodeLocation(stack?: string): string; -} - -export type { - LogAttributesWithMessage, - LogAttributeValue, - Environment, - LogLevelThresholds, - LogAttributes, - LogLevel, - PowertoolsLog, - LogItemInterface, - LogFormatterOptions, - LogFormatterInterface, - PowerToolsLogFormatterOptions, -}; diff --git a/packages/logger/src/types/Logger.ts b/packages/logger/src/types/Logger.ts index c2de245f86..72292ea2b8 100644 --- a/packages/logger/src/types/Logger.ts +++ b/packages/logger/src/types/Logger.ts @@ -1,23 +1,35 @@ import type { HandlerMethodDecorator } from '@aws-lambda-powertools/commons/types'; import type { Context } from 'aws-lambda'; +import type { LogLevel as LogLevelList } from '../constants.js'; +import type { LogFormatter } from '../formatter/LogFormatter.js'; import type { ConfigServiceInterface } from './ConfigServiceInterface.js'; +import type { LogRecordOrderKeys } from './formatters.js'; import type { Environment, LogAttributes, LogAttributesWithMessage, - LogFormatterInterface, - LogLevel, -} from './Log.js'; +} from './logKeys.js'; + +/** + * Type definition for the log level. + * + * It includes the lowercase and uppercase versions of each log level. + */ +type LogLevel = + | (typeof LogLevelList)[keyof typeof LogLevelList] + | Lowercase<(typeof LogLevelList)[keyof typeof LogLevelList]>; /** - * The log function type. + * Type definition for the log level thresholds. * - * @type {Object} LogFunction - * @property {function} [critical] - The critical log function. - * @property {function} [debug] - The debug log function. - * @property {function} [error] - The error log function. - * @property {function} [info] - The info log function. - * @property {function} [warn] - The warn log function. + * Each log level has a corresponding number that represents its severity. + */ +type LogLevelThresholds = { + [key in Uppercase]: number; +}; + +/** + * Type definition for a function that logs messages at different levels to the console. */ type LogFunction = { [key in Exclude, 'silent'>]: ( @@ -27,20 +39,21 @@ type LogFunction = { }; /** - * Options for the `injectLambdaContext` method. - * - * @type {Object} InjectLambdaContextOptions - * @property {boolean} [logEvent] - If `true`, the logger will log the event. - * @property {boolean} [resetKeys] - If `true`, the logger will reset the keys added via {@link `appendKeys()`}. + * Options for the {@link LoggerInterface.injectLambdaContext()} method. */ type InjectLambdaContextOptions = { + /** + * When `true` the logger will log the event. + * + * To avoid logging sensitive information, we recommend using this option only for debugging purposes in local environments. + */ logEvent?: boolean; /** - * @deprecated Use `resetKeys` instead. + * @deprecated Use {@link InjectLambdaContextOptions.resetKeys()}` instead. */ clearState?: boolean; /** - * If `true`, the logger will reset the keys added via {@link index.Logger.appendKeys()} + * If `true`, the logger will reset the keys added via {@link LoggerInterface.appendKeys()} */ resetKeys?: boolean; }; @@ -60,19 +73,31 @@ type CustomJsonReplacerFn = (key: string, value: unknown) => unknown; /** * Base constructor options for the Logger class. - * - * @type {Object} BaseConstructorOptions - * @property {LogLevel} [logLevel] - The log level. - * @property {string} [serviceName] - The service name. - * @property {number} [sampleRateValue] - The sample rate value. - * @property {ConfigServiceInterface} [customConfigService] - The custom config service. - * @property {Environment} [environment] - The environment. */ type BaseConstructorOptions = { + /** + * The level threshold for the logger to log at. + */ logLevel?: LogLevel; + /** + * Service name to be included in log items for easier correlation. + */ serviceName?: string; + /** + * The percentage rate at which the log level is `DEBUG`. + * + * See {@link https://docs.powertools.aws.dev/lambda/typescript/latest/core/logger/#sampling-debug-logs | Sampling debug logs} for more information. + */ sampleRateValue?: number; + /** + * A custom config service that can be passed to the Logger constructor to extend the default behavior. + * + * See {@link ConfigServiceInterface} for more information. + */ customConfigService?: ConfigServiceInterface; + /** + * The environment in which the Lambda function is running. + */ environment?: Environment; /** * A custom JSON replacer function that can be passed to the Logger constructor to extend the default serialization behavior. @@ -87,13 +112,10 @@ type BaseConstructorOptions = { /** * Options for the `persistentKeys` constructor option. - * - * @type {Object} PersistentKeysOption - * @property {LogAttributes} [persistentKeys] - Keys that will be added in all log items. */ type PersistentKeysOption = { /** - * Keys that will be added in all log items. + * Keys that will be added to all log items. */ persistentKeys?: LogAttributes; /** @@ -102,13 +124,18 @@ type PersistentKeysOption = { persistentLogAttributes?: never; }; -type DeprecatedOption = { +/** + * Deprecated options for the `persistentLogAttributes` constructor option. + * + * Used to maintain backwards compatibility with the `persistentLogAttributes` option. + */ +type DeprecatedPersistentKeysOption = { /** * @deprecated Use `persistentKeys` instead. */ persistentLogAttributes?: LogAttributes; /** - * Keys that will be added in all log items. + * Keys that will be added to all log items. */ persistentKeys?: never; }; @@ -116,16 +143,17 @@ type DeprecatedOption = { /** * Options for the `logFormatter` constructor option. * - * @type {Object} LogFormatterOption - * @property {LogFormatterInterface} [logFormatter] - The custom log formatter. + * Used to make the `logFormatter` option mutually exclusive with the `logRecordOrder` option. */ type LogFormatterOption = { /** - * The custom log formatter. + * The custom log formatter to process log attributes. + * + * See {@link https://docs.powertools.aws.dev/lambda/typescript/latest/core/logger/#custom-log-formatter-bring-your-own-formatter | Custom Log Formatters} for more information. */ - logFormatter?: LogFormatterInterface; + logFormatter?: LogFormatter; /** - * Optional list of keys to specify order in logs + * Optional list of keys to specify order of the keys in logs. */ logRecordOrder?: never; }; @@ -133,14 +161,13 @@ type LogFormatterOption = { /** * Options for the `logRecordOrder` constructor option. * - * @type {Object} LogRecordOrderOption - * @property {LogRecordOrder} [logRecordOrder] - The log record order. + * Used to make the `logRecordOrder` option mutually exclusive with the `logFormatter` option. */ type LogRecordOrderOption = { /** - * Optional list of keys to specify order in logs + * Optional list of keys to specify order of the keys in logs. */ - logRecordOrder?: LogRecordOrder; + logRecordOrder?: LogRecordOrderKeys; /** * The custom log formatter. */ @@ -148,54 +175,18 @@ type LogRecordOrderOption = { }; /** - * Options for the Logger class constructor. - * - * @type {Object} ConstructorOptions - * @property {LogLevel} [logLevel] - The log level. - * @property {string} [serviceName] - The service name. - * @property {number} [sampleRateValue] - The sample rate value. - * @property {LogFormatterInterface} [logFormatter] - The custom log formatter. - * @property {ConfigServiceInterface} [customConfigService] - The custom config service. - * @property {Environment} [environment] - The environment. - * @property {LogAttributes} [persistentKeys] - Keys that will be added in all log items. - * @property {LogRecordOrder} [logRecordOrder] - The log record order. + * Options to configure the Logger. */ type ConstructorOptions = BaseConstructorOptions & - (PersistentKeysOption | DeprecatedOption) & + (PersistentKeysOption | DeprecatedPersistentKeysOption) & (LogFormatterOption | LogRecordOrderOption); -type LambdaFunctionContext = Pick< - Context, - | 'functionName' - | 'memoryLimitInMB' - | 'functionVersion' - | 'invokedFunctionArn' - | 'awsRequestId' -> & { - coldStart: boolean; -}; - -type PowertoolsLogData = LogAttributes & { - environment?: Environment; - serviceName: string; - sampleRateValue: number; - lambdaContext?: LambdaFunctionContext; - xRayTraceId?: string; - awsRegion: string; -}; - -type UnformattedAttributes = PowertoolsLogData & { - error?: Error; - logLevel: LogLevel; - timestamp: Date; - message: string; -}; - -type LogRecordOrder = Array; - type LogItemMessage = string | LogAttributesWithMessage; type LogItemExtraInput = [Error | string] | LogAttributes[]; +/** + * Interface for the Logger class. + */ type LoggerInterface = { addContext(context: Context): void; addPersistentLogAttributes(attributes?: LogAttributes): void; @@ -223,15 +214,15 @@ type LoggerInterface = { }; export type { + Environment, + LogLevelThresholds, + LogAttributes, + LogLevel, LogFunction, LoggerInterface, LogItemMessage, LogItemExtraInput, - LambdaFunctionContext, - UnformattedAttributes, - PowertoolsLogData, ConstructorOptions, InjectLambdaContextOptions, CustomJsonReplacerFn, - LogRecordOrder, }; diff --git a/packages/logger/src/types/formatters.ts b/packages/logger/src/types/formatters.ts new file mode 100644 index 0000000000..5909ed83e2 --- /dev/null +++ b/packages/logger/src/types/formatters.ts @@ -0,0 +1,36 @@ +import type { EnvironmentVariablesService } from '../config/EnvironmentVariablesService.js'; +import type { LogFormatter } from '../formatter/LogFormatter.js'; +import type { LogKey } from './logKeys.js'; + +/** + * Options for the {@link LogFormatter} class. + */ +type LogFormatterOptions = { + /** + * Instance of the {@link EnvironmentVariablesService} to use for configuration. + */ + envVarsService?: EnvironmentVariablesService; +}; + +/** + * List of keys to order log attributes by. + * + * This can be a set of keys or an array of keys. + */ +type LogRecordOrderKeys = Set | LogKey[]; + +/** + * Options for the {@link PowertoolsLogFormatter} class. + */ +type PowertoolsLogFormatterOptions = LogFormatterOptions & { + /** + * An array of keys that defines the order of the log record. + */ + logRecordOrder?: LogRecordOrderKeys; +}; + +export type { + LogFormatterOptions, + PowertoolsLogFormatterOptions, + LogRecordOrderKeys, +}; diff --git a/packages/logger/src/types/index.ts b/packages/logger/src/types/index.ts index 3b3d69232f..8933cef721 100644 --- a/packages/logger/src/types/index.ts +++ b/packages/logger/src/types/index.ts @@ -1,19 +1,13 @@ export type { - LogAttributesWithMessage, - LogAttributeValue, Environment, LogLevelThresholds, LogAttributes, LogLevel, -} from './Log.js'; -export type { LogItemMessage, LogItemExtraInput, - LambdaFunctionContext, - UnformattedAttributes, - PowertoolsLogData, ConstructorOptions, InjectLambdaContextOptions, CustomJsonReplacerFn, LoggerInterface, } from './Logger.js'; +export type { UnformattedAttributes } from './logKeys.js'; diff --git a/packages/logger/src/types/logKeys.ts b/packages/logger/src/types/logKeys.ts new file mode 100644 index 0000000000..1f2749445d --- /dev/null +++ b/packages/logger/src/types/logKeys.ts @@ -0,0 +1,229 @@ +import type { Context } from 'aws-lambda'; +import type { LogLevel } from './Logger.js'; + +/** + * Type that extends an autocompletable string by allowing any string value. + * + * For example, if we have a type `type MyString = 'foo' | 'bar';`, when using + * `MyString` in a function, the autocomplete will only show `foo` and `bar`. + * + * For many cases, this is fine, but sometimes we also want to allow any _other_ + * string value, so we can use `AutocompletableString` instead and extend it like + * this: `type MyString = 'foo' | 'bar' | AutocompletableString;`. + */ +type AutocompletableString = string & {}; + +/** + * Generic log attribute object used as a base. + */ +type LogAttributes = { [key: string]: unknown }; + +/** + * Log attribute object with a message key. + * + * This is the most common log attribute object and it's used as first argument in the logger methods. + */ +type LogAttributesWithMessage = LogAttributes & { + message: string; +}; + +/** + * The environment in which the Lambda function is invoked. + */ +type Environment = 'dev' | 'local' | 'staging' | 'prod' | AutocompletableString; + +/** + * Standard keys that are included in every log item when using the default log formatter. + * + * See {@link https://docs.powertools.aws.dev/lambda/python/latest/core/logger/#standard-structured-keys | Standard structured keys} for more information. + */ +type PowertoolsStandardKeys = { + /** + * Log level threshold of the log item + * + * @example "INFO" + */ + level: LogLevel; + /** + * A descriptive, human-readable representation of the log item + * + * @example "Query performed to DynamoDB" + */ + message: string; + /** + * The percentage rate at which the log level is `DEBUG`. + * + * See {@link https://docs.powertools.aws.dev/lambda/typescript/latest/core/logger/#sampling-debug-logs | Sampling debug logs} for more information. + * + * @example 0.1 + */ + sampling_rate: number; + /** + * A unique name identifier of the service this AWS Lambda function belongs to. + * + * @default "service_undefined" + * + * @example "serverlessAirline" + */ + service: string; + /** + * Timestamp string in simplified extended ISO format (ISO 8601) + * + * @example "2011-10-05T14:48:00.000Z" + */ + timestamp: string; + /** + * X-Ray Trace ID set by the Lambda runtime. + * + * @example "1-5759e988-bd862e3fe1be46a994272793" + */ + xray_trace_id: string; + /** + * An optional object containing information about the error passed to the logger + */ + error?: Error; +}; + +/** + * Additional keys that are added to the log item when using the default log formatter and + * having added AWS Lambda context. + * + * See {@link https://docs.powertools.aws.dev/lambda/typescript/latest/core/logger/#capturing-lambda-context-info | Capturing Lambda context info} for more information. + */ +type PowertoolsLambdaContextKeys = { + /** + * 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" + */ + function_name: string; + /** + * The memory size of the Lambda function. + * + * @example "128" + */ + function_memory_size: string; + /** + * The ARN of the Lambda function. + * + * @example "arn:aws:lambda:eu-west-1:012345678910:function:example-powertools-HelloWorldFunction-1P1Z6B39FLU73" + */ + function_arn: string; + /** + * The request ID of the current invocation. + * + * @example "899856cb-83d1-40d7-8611-9e78f15f32f4" + */ + function_request_id: string; +}; + +/** + * Keys available to the logger when customers have captured AWS Lambda context information. + * + * This object is a subset of the `Context` object from the `aws-lambda` package and is used + * only internally by the logger and passed to the log formatter. + * + * For the public API used in the default log formatter, see {@link PowertoolsLambdaContextKeys}. + * + * @internal + */ +type LambdaFunctionContext = Pick< + Context, + | 'functionName' + | 'memoryLimitInMB' + | 'functionVersion' + | 'invokedFunctionArn' + | 'awsRequestId' +> & { + coldStart: boolean; +}; + +/** + * Keys managed by the Powertools for AWS Lambda Logger. + * + * This type is used internally by the logger and passed to the log formatter. + * + * For the public API used in the log formatter, see {@link PowertoolsStandardKeys} and {@link PowertoolsLambdaContextKeys}. + */ +type PowertoolsLogData = { + /** + * The environment in which the Lambda function is invoked. + */ + environment?: Environment; + /** + * The name of the service this AWS Lambda function belongs to. + */ + serviceName: string; + /** + * The percentage rate at which the log level is `DEBUG`. + */ + sampleRateValue: number; + /** + * Lambda context information. + * + * See {@link LambdaFunctionContext} for more information. + */ + lambdaContext?: LambdaFunctionContext; + /** + * The X-Ray Trace ID set by the Lambda runtime. + */ + xRayTraceId?: string; + /** + * The AWS region in which the Lambda function is invoked. + */ + awsRegion: string; +}; + +/** + * Base log attributes that are included in every log item. + */ +type BaseLogAttributes = { + /** + * An optional object containing information about the error passed to the logger. + */ + error?: Error; + /** + * Log level threshold of the log item. + */ + logLevel: LogLevel; + /** + * Timestamp string in simplified extended ISO format (ISO 8601). + */ + timestamp: Date; + /** + * A descriptive, human-readable representation of the log item. + */ + message: string; +}; + +/** + * Unformatted attributes that are passed to the log formatter. + */ +type UnformattedAttributes = LogAttributes & + PowertoolsLogData & + BaseLogAttributes; + +/** + * Keys that can be used to order log attributes by. + */ +type LogKey = + | keyof PowertoolsStandardKeys + | keyof PowertoolsLambdaContextKeys + | AutocompletableString; + +export type { + LogAttributes, + LogAttributesWithMessage, + Environment, + PowertoolsLogData, + PowertoolsStandardKeys, + PowertoolsLambdaContextKeys, + UnformattedAttributes, + LogKey, +}; diff --git a/packages/logger/tests/unit/formatters.test.ts b/packages/logger/tests/unit/formatters.test.ts index a8aa727000..7bdde1ff4d 100644 --- a/packages/logger/tests/unit/formatters.test.ts +++ b/packages/logger/tests/unit/formatters.test.ts @@ -20,11 +20,12 @@ import { LogLevelThreshold, Logger, } from '../../src/index.js'; -import type { LogAttributes, LogLevel } from '../../src/types/Log.js'; import type { CustomJsonReplacerFn, - UnformattedAttributes, + LogAttributes, + LogLevel, } from '../../src/types/Logger.js'; +import type { LogKey, UnformattedAttributes } from '../../src/types/logKeys.js'; const fileNameRegexp = new RegExp(/index.js:\d+$/); const fileNameRegexpWithLine = new RegExp(/formatters.test.ts:\d+:\d+/); @@ -232,13 +233,13 @@ describe('Formatters', () => { it('when `logRecordOrder` is set, it orders the attributes in the log item taking `additionalLogAttributes` into consideration', () => { // Prepare const formatter = new PowertoolsLogFormatter({ - logRecordOrder: [ + logRecordOrder: new Set([ 'message', 'additional_key', 'timestamp', 'serviceName', 'environment', - ], + ]), }); const additionalLogAttributes: LogAttributes = { additional_key: 'additional_value', diff --git a/packages/logger/tests/unit/logLevels.test.ts b/packages/logger/tests/unit/logLevels.test.ts index 0f35ffec26..f5017436c7 100644 --- a/packages/logger/tests/unit/logLevels.test.ts +++ b/packages/logger/tests/unit/logLevels.test.ts @@ -6,8 +6,10 @@ import { beforeEach, describe, expect, it, jest } from '@jest/globals'; import { Logger } from '../../src/Logger.js'; import { LogLevel, LogLevelThreshold } from '../../src/constants.js'; -import type { LogLevel as LogLevelType } from '../../src/types/Log.js'; -import type { LogFunction } from '../../src/types/Logger.js'; +import type { + LogFunction, + LogLevel as LogLevelType, +} from '../../src/types/Logger.js'; /** * Helper function to get the console method for a given log level, we use this