UNPKG

@aws-lambda-powertools/logger

Version:
149 lines (148 loc) 5.32 kB
/** * Class that defines and implements common methods for the formatting of log attributes. * * 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 */ class LogFormatter { /** * Instance of the {@link EnvironmentVariablesService} to use for configuration. */ envVarsService; constructor(options) { this.envVarsService = options?.envVarsService; } /** * 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 to format */ formatError(error) { const { name, message, stack, cause, ...errorAttributes } = error; const formattedError = { name, location: this.getCodeLocation(error.stack), message, stack, cause: error.cause instanceof Error ? this.formatError(error.cause) : error.cause, }; for (const key in error) { if (typeof key === 'string' && !['name', 'message', 'stack', 'cause'].includes(key)) { formattedError[key] = errorAttributes[key]; } } return formattedError; } /** * 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. * * Otherwise, the timezone defaults to ':UTC'. * * @param now - The date to format */ formatTimestamp(now) { const defaultTimezone = 'UTC'; /** * If a specific timezone is configured and it's not the default `UTC`, * format the timestamp with the appropriate timezone offset. **/ const configuredTimezone = this.envVarsService?.getTimezone(); if (configuredTimezone && !configuredTimezone.includes(defaultTimezone)) return this.#generateISOTimestampWithOffset(now, configuredTimezone); return now.toISOString(); } /** * Get the location of an error from a stack trace. * * @param stack - stack trace to parse */ getCodeLocation(stack) { if (!stack) { return ''; } const stackLines = stack.split('\n'); const regex = /\(([^)]*?):(\d+?):(\d+?)\)\\?$/; for (const item of stackLines) { const match = regex.exec(item); if (Array.isArray(match)) { return `${match[1]}:${Number(match[2])}`; } } return ''; } /** * 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). * * @param timezone - IANA time zone identifier (e.g., "Asia/Dhaka"). */ #getDateFormatter = (timezone) => { const twoDigitFormatOption = '2-digit'; const validTimeZone = Intl.supportedValuesOf('timeZone').includes(timezone) ? timezone : 'UTC'; return new Intl.DateTimeFormat('en', { year: 'numeric', month: twoDigitFormatOption, day: twoDigitFormatOption, hour: twoDigitFormatOption, minute: twoDigitFormatOption, second: twoDigitFormatOption, hour12: false, timeZone: validTimeZone, }); }; /** * Generate an ISO 8601 timestamp string with the specified time zone and the local time zone offset. * * @param date - date to format * @param timezone - IANA time zone identifier (e.g., "Asia/Dhaka"). */ #generateISOTimestampWithOffset(date, timezone) { const { year, month, day, hour, minute, second } = this.#getDateFormatter(timezone) .formatToParts(date) .reduce((acc, item) => { acc[item.type] = item.value; return acc; }, {}); const datePart = `${year}-${month}-${day}T${hour}:${minute}:${second}`; const offset = -date.getTimezoneOffset(); const offsetSign = offset >= 0 ? '+' : '-'; const offsetHours = Math.abs(Math.floor(offset / 60)) .toString() .padStart(2, '0'); const offsetMinutes = Math.abs(offset % 60) .toString() .padStart(2, '0'); const millisecondPart = date.getMilliseconds().toString().padStart(3, '0'); const offsetPart = `${offsetSign}${offsetHours}:${offsetMinutes}`; return `${datePart}.${millisecondPart}${offsetPart}`; } } export { LogFormatter };