import appInfo from '@dotcom-reliability-kit/app-info'; import reliabilityKitLogger from '@dotcom-reliability-kit/logger'; import serializeError, { type SerializedError } from '@dotcom-reliability-kit/serialize-error'; import serializeRequest, { type Request } from '@dotcom-reliability-kit/serialize-request'; type LogMethod = (...logData: any) => any; interface Logger { error: LogMethod; fatal?: LogMethod; warn: LogMethod; } interface Options { error: string | Error | (Error & Record); includeHeaders?: string[]; logger?: Logger & { [key: string]: any }; request?: string | Request; } interface HandledErrorOptions extends Options { logUserErrorsAsWarnings?: boolean; } interface InternalOptions extends Omit { error: SerializedError; event: string; level: 'error' | 'fatal' | 'warn'; } /** * Log an error object with optional request information. */ function logError({ error, event, includeHeaders, level, logger = reliabilityKitLogger, request }: InternalOptions) { const logData: { [key: string]: any } = { event, message: extractErrorMessage(error), error, app: { commit: appInfo.commitHash, name: appInfo.systemCode, nodeVersion: process.versions.node, region: appInfo.region, releaseDate: appInfo.releaseDate, processType: appInfo.processType } }; if (request) { logData.request = serializeRequest(request, { includeHeaders }); } try { const logMethod = logger[level] || logger.error; logMethod.call(logger, logData); } catch (/** @type {any} */ loggingError) { // biome-ignore lint/suspicious/noConsole: a fallback to log when logging is broken console.log( JSON.stringify({ level: 'error', event: 'LOG_METHOD_FAILURE', message: `Failed to log at level '${level}'`, error: serializeError(loggingError) }) ); // biome-ignore lint/suspicious/noConsole: a fallback to log when logging is broken console.log(JSON.stringify(logData)); } } /** * Get a human readable error message from a serialized error object. */ function extractErrorMessage(serializedError: SerializedError) { return `${serializedError.name || 'Error'}${ serializedError.message ? `: ${serializedError.message}` : '' }`; } /** * Log a handled error. */ export function logHandledError({ error, includeHeaders, logger, logUserErrorsAsWarnings = false, request }: HandledErrorOptions) { const serializedError = serializeError(error); logError({ error: serializedError, event: 'HANDLED_ERROR', includeHeaders, level: logUserErrorsAsWarnings && isUserError(serializedError) ? 'warn' : 'error', logger, request }); } /** * Log a recoverable error. */ export function logRecoverableError({ error, includeHeaders, logger, request }: Options) { const serializedError = serializeError(error); logError({ error: serializedError, event: 'RECOVERABLE_ERROR', includeHeaders, level: 'warn', logger, request }); } /** * Log an unhandled error. */ export function logUnhandledError({ error, includeHeaders, logger, request }: Options) { const serializedError = serializeError(error); logError({ error: serializedError, event: 'UNHANDLED_ERROR', includeHeaders, level: 'fatal', logger, request }); } function isUserError(error: SerializedError) { return error.statusCode !== null && error.statusCode >= 400 && error.statusCode < 500; }