export type SerializedError = { name?: string; message: string; stack?: string; code?: string | number; cause?: unknown; }; function isPlainObject(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } export function serializeError(error: Error): SerializedError { const code = (error as Error & { code?: string | number }).code; const cause = (error as Error & { cause?: unknown }).cause; return { name: error.name, message: error.message, stack: error.stack, ...(typeof code !== 'undefined' ? { code } : {}), ...(typeof cause !== 'undefined' ? { cause: serializeUnknownForLog(cause) } : {}), }; } export function serializeUnknownError(error: unknown): SerializedError { if (error instanceof Error) { return serializeError(error); } if (typeof error === 'string') { return { message: error }; } if (isPlainObject(error) && typeof error.message === 'string') { return { message: error.message, ...(typeof error.name === 'string' ? { name: error.name } : {}), ...(typeof error.stack === 'string' ? { stack: error.stack } : {}), ...(typeof error.code === 'string' || typeof error.code === 'number' ? { code: error.code } : {}), ...(typeof error.cause !== 'undefined' ? { cause: serializeUnknownForLog(error.cause) } : {}), }; } return { message: `Non-Error value thrown: ${String(error)}`, }; } export function serializeUnknownForLog(value: unknown): unknown { if (value instanceof Error) { return serializeError(value); } if (Array.isArray(value)) { return value.map((item) => serializeUnknownForLog(item)); } if (isPlainObject(value)) { return Object.fromEntries( Object.entries(value).map(([key, entryValue]) => [key, serializeUnknownForLog(entryValue)]), ); } return value; }