diff --git a/docs/api.md b/docs/api.md index 1fc438423..e3e373790 100644 --- a/docs/api.md +++ b/docs/api.md @@ -378,12 +378,31 @@ const hooks = { #### `formatters` (Object) -An object containing functions for formatting the shape of the log lines. +An object containing an `options` object and functions for formatting +the shape of the log lines. These functions should return a JSONifiable object and should never throw. These functions allow for full customization of the resulting log lines. For example, they can be used to change the level key name or to enrich the default metadata. +##### `options` + +The formatters `options` object is passed to the child loggers, except if a new formatters `options` object is passed to the `child()` method. + +###### `keepParentBindings` + +If `true`, child loggers will keep their parent `bindings` formatters function. + +If `false` or `undefined`, child loggers will have their `bindings` formatters function reset to a "no-op" formatter function. + +```js +const formatters = { + options: { + keepParentBindings: true + } +} +``` + ##### `level` Changes the shape of the log level. The default shape is `{ level: number }`. diff --git a/lib/proto.js b/lib/proto.js index b82fd0c26..8b73cbf9a 100644 --- a/lib/proto.js +++ b/lib/proto.js @@ -78,7 +78,7 @@ module.exports = function () { return Object.create(prototype) } -const resetChildingsFormatter = bindings => bindings +const noOpFormatter = bindings => bindings function child (bindings, options) { if (!bindings) { throw Error('missing bindings for child Pino') @@ -110,17 +110,20 @@ function child (bindings, options) { instance[serializersSym][bks] = options.serializers[bks] } } else instance[serializersSym] = serializers + const keepParentBindings = typeof formatters.options === 'object' && formatters.options.keepParentBindings if (options.hasOwnProperty('formatters')) { - const { level, bindings: chindings, log } = options.formatters + const { options: formattersOptions, level, bindings: chindings, log } = options.formatters instance[formattersSym] = buildFormatters( + formattersOptions || formatters.options, level || formatters.level, - chindings || resetChildingsFormatter, + chindings || (keepParentBindings ? formatters.bindings || noOpFormatter : noOpFormatter), log || formatters.log ) } else { instance[formattersSym] = buildFormatters( + formatters.options, formatters.level, - resetChildingsFormatter, + keepParentBindings ? formatters.bindings || noOpFormatter : noOpFormatter, formatters.log ) } diff --git a/lib/tools.js b/lib/tools.js index 5f68f588e..6752d0c48 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -352,8 +352,9 @@ function stringify (obj, stringifySafeFn) { } } -function buildFormatters (level, bindings, log) { +function buildFormatters (options, level, bindings, log) { return { + options, level, bindings, log diff --git a/pino.js b/pino.js index b2ec4dd46..1924aa496 100644 --- a/pino.js +++ b/pino.js @@ -118,6 +118,7 @@ function pino (...args) { }) const allFormatters = buildFormatters( + formatters.options, formatters.level, formatters.bindings, formatters.log diff --git a/test/formatters.test.js b/test/formatters.test.js index 39a0013c9..7cb2234d8 100644 --- a/test/formatters.test.js +++ b/test/formatters.test.js @@ -200,6 +200,45 @@ test('Formatters in child logger', async ({ match }) => { }) }) +test('Formatters with keepParentBindings option', async ({ match }) => { + const stream = sink() + + const logger = pino({ + formatters: { + options: { + keepParentBindings: true + }, + bindings (bindings) { + if (bindings.foobar) bindings.foobar = bindings.foobar.toUpperCase() + if (bindings.faz) bindings.faz = bindings.faz.toUpperCase() + if (bindings.foobat) bindings.foobat = bindings.foobat.toUpperCase() + if (bindings.foobaz) bindings.foobaz = bindings.foobaz.toUpperCase() + return bindings + } + } + }, stream) + logger.setBindings({ foobar: 'foobar' }) + + const child = logger.child({ + faz: 'baz', + nested: { object: true } + }) + child.setBindings({ foobat: 'foobat' }) + + const childChild = child.child({ foobaz: 'foobaz' }) + + const o = once(stream, 'data') + childChild.info({ foo: 'bar', nested: { object: true } }, 'hello world') + match(await o, { + foobar: 'FOOBAR', + faz: 'BAZ', + foobat: 'FOOBAT', + foobaz: 'FOOBAZ', + foo: 'bar', + nested: { object: true } + }) +}) + test('Formatters without bindings in child logger', async ({ match }) => { const stream = sink() const logger = pino({