diff --git a/README.md b/README.md index fcb8020..4951592 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,24 @@ const logger = createLogger({ }); ``` +#### Removal of Default Properties + +The library will remove a set of default properties. +To bypass this, set `ignoreDefaultRemovePaths` to `true` on the `redact` options. + +Example: + +```typescript +const logger = createLogger({ + name: 'my-app', + redact: { + paths: ['req.headers["x-redact-this"]'], + removePaths: ['req.headers["x-remove-this"]'], + ignoreDefaultRemovePaths: true, + }, +}); +``` + ### Trimming The following trimming rules apply to all logging data: diff --git a/src/index.test.ts b/src/index.test.ts index befea1f..6775c1c 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,5 +1,7 @@ import split from 'split2'; +import { defaultRemovePaths } from './redact'; + import createLogger, { type LoggerOptions } from '.'; const bearerToken = @@ -472,35 +474,67 @@ testLog( ['req.headers.x-remove-me'], ); +const buildObjectFromPath = (path: string): Record => + path + .split(/[.\[]/) + .reverse() + .reduce( + (previous, current) => { + const key = current.replace(/["\[\]]/g, ''); + if (!previous) { + return { [key]: 'Default path property' }; + } + + return { [key]: previous }; + }, + undefined as unknown as Record, + ); + +const buildObjectFromDefaultRemovePaths = (): Record => + !defaultRemovePaths?.[0] ? {} : buildObjectFromPath(defaultRemovePaths[0]); + testLog( - 'should redact or remove specified paths where property names contain dots', + 'should remove default paths when ignoreDefaultRemovePaths is missing', { - msg: 'allowed', - data: { - top: { - prop1: 'Should be redacted', - prop2: 'Should be removed', - }, - ['top.prop1']: 'Should be removed', - ['top.prop2']: 'Should be redacted', - }, + redact: 'Should be redacted', + remove: 'Should be removed', + ...buildObjectFromDefaultRemovePaths(), }, { - msg: 'allowed', - data: { - top: { prop1: '[Redacted]' }, - ['top.prop2']: '[Redacted]', + redact: '[Redacted]', + }, + 'info', + { + maxObjectDepth: 20, + redact: { + paths: ['redact'], + removePaths: ['remove'], }, }, + ['remove', ...defaultRemovePaths], +); + +testLog( + 'should not remove default paths when ignoreDefaultRemovePaths is true', + { + redact: 'Should be redacted', + remove: 'Should be removed', + ...buildObjectFromDefaultRemovePaths(), + }, + { + redact: '[Redacted]', + ...buildObjectFromDefaultRemovePaths(), + }, 'info', { maxObjectDepth: 20, redact: { - paths: ['data.top.prop1', 'data["top.prop2"]'], - removePaths: ['data.top.prop2', 'data["top.prop1"]'], + paths: ['redact'], + removePaths: ['remove'], + ignoreDefaultRemovePaths: true, }, }, - ['data.top.prop2', ['data', 'top.prop1']], + ['remove'], ); testLog( diff --git a/src/index.ts b/src/index.ts index 4e548fe..0474612 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ import serializers from './serializers'; export { pino }; export type LoggerOptions = Omit & - FormatterOptions & { redact?: redact.ExtendedRedactOptions }; + FormatterOptions & { redact?: redact.ExtendedRedact }; export type Logger = pino.Logger; /** diff --git a/src/redact/index.ts b/src/redact/index.ts index 69534bb..344c022 100644 --- a/src/redact/index.ts +++ b/src/redact/index.ts @@ -3,26 +3,42 @@ import type * as pino from 'pino'; // TODO: Redact cookies? export const defaultRedact = []; -export type ExtendedRedactOptions = pino.LoggerOptions['redact'] & { +export const defaultRemovePaths: string[] = []; + +type redactOptions = Extract; + +type extendedRedactOptions = redactOptions & { /** * A list of paths to remove from the logged object instead of redacting them. * Note that if you are only removing, rather use the `paths` property and set `remove` to `true`. */ removePaths?: string[]; + + /** + * When `true`, the `defaultRemovePaths` will not be appended to the `removePaths` list. + */ + ignoreDefaultRemovePaths?: true; }; -type ExtendedRedact = string[] | ExtendedRedactOptions | undefined; +export type ExtendedRedact = string[] | extendedRedactOptions | undefined; -const addDefaultRedactPathStrings = ( +const appendDefaultRedactAndRemove = ( redact: ExtendedRedact, -): ExtendedRedact => { - if (!redact) { - return defaultRedact; - } - if (Array.isArray(redact)) { - return redact.concat(defaultRedact); - } - return redact; +): extendedRedactOptions | undefined => { + const inputRedact = + typeof redact !== 'undefined' && !Array.isArray(redact) + ? redact + : { paths: redact ?? [] }; + + const paths = inputRedact.paths.concat(defaultRedact); + const inputRemovePaths = inputRedact.removePaths ?? []; + const removePaths = inputRedact.ignoreDefaultRemovePaths + ? inputRemovePaths + : inputRemovePaths.concat(defaultRemovePaths); + + return paths.length === 0 && removePaths.length === 0 + ? undefined + : { ...inputRedact, paths, removePaths }; }; const STARTS_WITH_INVALID_CHARS = '^[^$_a-zA-Z]'; @@ -66,4 +82,4 @@ const configureRedactCensor = (redact: ExtendedRedact): ExtendedRedact => { }; export const configureRedact = (redact: ExtendedRedact): ExtendedRedact => - configureRedactCensor(addDefaultRedactPathStrings(redact)); + configureRedactCensor(appendDefaultRedactAndRemove(redact));