diff --git a/src/index.ts b/src/index.ts index 656b2aac..ae21cbda 100644 --- a/src/index.ts +++ b/src/index.ts @@ -130,13 +130,7 @@ export const start = async ( if (err instanceof ExecutionError) { throw err } - censorLogs(() => - logger.error({ - name: err.name, - stack: err.stack, - message: err.message, - }), - ) + censorLogs(() => logger.error(err)) }) if ( diff --git a/src/util/logger.ts b/src/util/logger.ts index 76269f7c..43d4af20 100644 --- a/src/util/logger.ts +++ b/src/util/logger.ts @@ -217,16 +217,21 @@ export const loggingContextMiddleware = ( // Obj is typed as "any" because it could be a variety of structures in the logger // eslint-disable-next-line @typescript-eslint/no-explicit-any export function censor(obj: any, censorList: CensorKeyValue[], throwOnError = false) { + // Name/message/stack are non-enumerable own properties on Error, so a plain + // JSON.stringify would silently drop them; pull them up before serializing. + const target = + obj instanceof Error ? { ...obj, name: obj.name, message: obj.message, stack: obj.stack } : obj + let stringified: string | undefined try { - // JSON.stringify(obj) will fail if obj contains a circular reference or a bigint. - stringified = JSON.stringify(obj) + // JSON.stringify(target) will fail if target contains a circular reference or a bigint. + stringified = JSON.stringify(target) } catch { try { // Retry with a bigint-safe replacer in case the failure was due to a bigint value. // JSON.stringify with a replacer function is slower, so we only pay that cost when // the fast path above actually fails, rather than on every call. - stringified = JSON.stringify(obj, (_key, value) => { + stringified = JSON.stringify(target, (_key, value) => { return typeof value === 'bigint' ? value.toString() : value }) } catch (e) { diff --git a/test/logger.test.ts b/test/logger.test.ts index f3fc5913..2cd712d7 100644 --- a/test/logger.test.ts +++ b/test/logger.test.ts @@ -129,3 +129,19 @@ test('properly handle nested bigint values', async (t) => { const log = censor({ apiKey: 'mock-api-key', tx: { gasLimit: 21000n } }, CensorList.getAll()) t.deepEqual(log, { apiKey: '[API_KEY REDACTED]', tx: { gasLimit: '21000' } }) }) + +test('properly surfaces message/stack when given an Error', async (t) => { + const error = new Error('mock-api-key failure') + const log = censor(error, CensorList.getAll()) + t.is(log.name, 'Error') + t.is(log.message, '[API_KEY REDACTED] failure') + t.truthy(log.stack) +}) + +test('properly surfaces custom enumerable properties alongside message/stack', async (t) => { + const error = Object.assign(new Error('boom'), { code: 'CALL_EXCEPTION' }) + const log = censor(error, CensorList.getAll()) + t.is(log.message, 'boom') + t.is(log.code, 'CALL_EXCEPTION') + t.truthy(log.stack) +})