diff --git a/lib/logger/index.spec.ts b/lib/logger/index.spec.ts index c6de2300da9b2e6ca40d0c7f11a00c5158acffd8..d55c13a68b667c569f4519c13e7fda0ea0d7e5fa 100644 --- a/lib/logger/index.spec.ts +++ b/lib/logger/index.spec.ts @@ -152,6 +152,10 @@ describe('logger/index', () => { }); add({ password: 'secret"password' }); + class SomeClass { + constructor(public field: string) {} + } + logger.error({ foo: 'secret"password', bar: ['somethingelse', 'secret"password'], @@ -162,6 +166,8 @@ describe('logger/index', () => { secrets: { foo: 'barsecret', }, + someFn: () => 'secret"password', + someObject: new SomeClass('secret"password'), }); expect(logged.foo).not.toBe('secret"password'); @@ -173,5 +179,7 @@ describe('logger/index', () => { expect(logged.content).toBe('[content]'); expect(logged.prBody).toBe('[Template]'); expect(logged.secrets.foo).toBe('***********'); + expect(logged.someFn).toBe('[function]'); + expect(logged.someObject.field).toBe('**redacted**'); }); }); diff --git a/lib/logger/utils.ts b/lib/logger/utils.ts index b8d6ebef2c3391880396d1b8ce874969dc0f51ab..ab00f9746e5b4dda7f04d0f804202b065bf73ece 100644 --- a/lib/logger/utils.ts +++ b/lib/logger/utils.ts @@ -1,4 +1,5 @@ import { Stream } from 'stream'; +import is from '@sindresorhus/is'; import bunyan from 'bunyan'; import fs from 'fs-extra'; import { clone } from '../util/clone'; @@ -90,38 +91,54 @@ export default function prepareError(err: Error): Record<string, unknown> { return response; } -export function sanitizeValue(_value: unknown, seen = new WeakMap()): any { - let value = _value; - if (Array.isArray(value)) { - const length = value.length; - const arrayResult = Array(length); - seen.set(value, arrayResult); - for (let idx = 0; idx < length; idx += 1) { - const val = value[idx]; - arrayResult[idx] = seen.has(val) - ? seen.get(val) - : sanitizeValue(val, seen); - } - return arrayResult; +type NestedValue = unknown[] | object; + +function isNested(value: unknown): value is NestedValue { + return is.array(value) || is.object(value); +} + +export function sanitizeValue( + value: unknown, + seen = new WeakMap<NestedValue, unknown>() +): any { + if (is.string(value)) { + return sanitize(value); } - if (value instanceof Buffer) { - return '[content]'; + if (is.date(value)) { + return value; } - if (value instanceof Error) { - value = prepareError(value); + if (is.function_(value)) { + return '[function]'; } - const valueType = typeof value; + if (is.buffer(value)) { + return '[content]'; + } + + if (is.error(value)) { + const err = prepareError(value); + return sanitizeValue(err, seen); + } - if (value && valueType !== 'function' && valueType === 'object') { - if (value instanceof Date) { - return value; + if (is.array(value)) { + const length = value.length; + const arrayResult = Array(length); + seen.set(value, arrayResult); + for (let idx = 0; idx < length; idx += 1) { + const val = value[idx]; + arrayResult[idx] = + isNested(val) && seen.has(val) + ? seen.get(val) + : sanitizeValue(val, seen); } + return arrayResult; + } + if (is.object(value)) { const objectResult: Record<string, any> = {}; - seen.set(value as any, objectResult); + seen.set(value, objectResult); for (const [key, val] of Object.entries<any>(value)) { let curValue: any; if (!val) { @@ -143,10 +160,11 @@ export function sanitizeValue(_value: unknown, seen = new WeakMap()): any { objectResult[key] = curValue; } + return objectResult; } - return valueType === 'string' ? sanitize(value as string) : value; + return value; } export function withSanitizer(streamConfig: bunyan.Stream): bunyan.Stream {