From cc209404e0cc19d68d5a5de2d0b7ae6ae32564e2 Mon Sep 17 00:00:00 2001 From: Damien Garrido Date: Wed, 24 Apr 2024 23:08:29 +0200 Subject: [PATCH] feat: keep parent formatters bindings function option --- docs/api.md | 21 ++++++++++++++++++++- lib/proto.js | 11 +++++++---- lib/tools.js | 3 ++- pino.js | 1 + test/formatters.test.js | 39 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 6 deletions(-) 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({