From 7774bfc46d26120c602b14e6e680f9245ed6276f Mon Sep 17 00:00:00 2001 From: Mary Marchini Date: Thu, 4 May 2023 11:17:43 -0700 Subject: [PATCH] feat: pass logger/child logger as param to mixin Passing the logger or child logger as a parameter to `mixin` allows users to set logger-specific context in the logger object that can be used by `mixin` to enrich the context to be added to the resulting JSON. One example use case for this is avoiding the "duplicate keys" caveat that comes with child loggers. If the user wants to make a known key "mergeable", they could add a custom function that concatenates values to the key and then use `mixin` to pass that merged value to the context. For example: ```js const logger = pino({ mixin (obj, num, logger) { return { tags: logger.tags } } }) logger.tags = {} logger.addTag = function (key, value) { logger.tags[key] = value } function createChild (parent, ...context) { const newChild = logger.child(...context) newChild.tags = { ...logger.tags } newChild.addTag = function (key, value) { newChild.tags[key] = value } return newChild } logger.addTag('foo', 1) const child = createChild(logger, {}) child.addTag('bar', 2) logger.info('this will only have `foo: 1`') child.info('this will have both `foo: 1` and `bar: 2`') logger.info('this will still only have `foo: 1`') ``` --- docs/api.md | 37 +++++++++++++++++++++++++++++- lib/proto.js | 2 +- test/mixin.test.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) diff --git a/docs/api.md b/docs/api.md index cfd1ca7bc..9f3abf10c 100644 --- a/docs/api.md +++ b/docs/api.md @@ -121,6 +121,8 @@ Default: `undefined` If provided, the `mixin` function is called each time one of the active logging methods is called. The first parameter is the value `mergeObject` or an empty object. The second parameter is the log level number. +The third parameter is the logger or child logger itself, which can be used to +retrieve logger-specific context from within the `mixin` function. The function must synchronously return an object. The properties of the returned object will be added to the logged JSON. @@ -177,7 +179,40 @@ logger.error('Message 2') ``` If the `mixin` feature is being used merely to add static metadata to each log message, -then a [child logger ⇗](/docs/child-loggers.md) should be used instead. +then a [child logger ⇗](/docs/child-loggers.md) should be used instead. Unless your application +needs to concatenate values for a specific key multiple times, in which case `mixin` can be +used to avoid the [duplicate keys caveat](/docs/child-loggers.md#duplicate-keys-caveat): + +```js +const logger = pino({ + mixin (obj, num, logger) { + return { + tags: logger.tags + } + } +}) +logger.tags = {} + +logger.addTag = function (key, value) { + logger.tags[key] = value +} + +function createChild (parent, ...context) { + const newChild = logger.child(...context) + newChild.tags = { ...logger.tags } + newChild.addTag = function (key, value) { + newChild.tags[key] = value + } + return newChild +} + +logger.addTag('foo', 1) +const child = createChild(logger, {}) +child.addTag('bar', 2) +logger.info('this will only have `foo: 1`') +child.info('this will have both `foo: 1` and `bar: 2`') +logger.info('this will still only have `foo: 1`') +``` As of pino 7.x, when the `mixin` is used with the [`nestedKey` option](#opt-nestedkey), the object returned from the `mixin` method will also be nested. Prior versions would mix diff --git a/lib/proto.js b/lib/proto.js index 400945282..4187b3b5b 100644 --- a/lib/proto.js +++ b/lib/proto.js @@ -194,7 +194,7 @@ function write (_obj, msg, num) { } if (mixin) { - obj = mixinMergeStrategy(obj, mixin(obj, num)) + obj = mixinMergeStrategy(obj, mixin(obj, num, this)) } const s = this[asJsonSym](obj, msg, num, t) diff --git a/test/mixin.test.js b/test/mixin.test.js index 86b1622f6..18f073650 100644 --- a/test/mixin.test.js +++ b/test/mixin.test.js @@ -160,3 +160,59 @@ test('mixin can use level number', async ({ ok, same }) => { stack: 'stack' }, 'test') }) + +test('mixin receives logger as third parameter', async ({ ok, same }) => { + const stream = sink() + const instance = pino({ + mixin (context, num, logger) { + ok(logger !== null, 'logger should be defined') + ok(logger !== undefined, 'logger should be defined') + same(logger, instance) + return { ...context, num } + } + }, stream) + instance.level = name + instance[name]({ + message: '123' + }, 'test') +}) + +test('mixin receives child logger', async ({ ok, same }) => { + const stream = sink() + let child = null + const instance = pino({ + mixin (context, num, logger) { + ok(logger !== null, 'logger should be defined') + ok(logger !== undefined, 'logger should be defined') + same(logger.expected, child.expected) + return { ...context, num } + } + }, stream) + instance.level = name + instance.expected = false + child = instance.child({}) + child.expected = true + child[name]({ + message: '123' + }, 'test') +}) + +test('mixin receives logger even if child exists', async ({ ok, same }) => { + const stream = sink() + let child = null + const instance = pino({ + mixin (context, num, logger) { + ok(logger !== null, 'logger should be defined') + ok(logger !== undefined, 'logger should be defined') + same(logger.expected, instance.expected) + return { ...context, num } + } + }, stream) + instance.level = name + instance.expected = false + child = instance.child({}) + child.expected = true + instance[name]({ + message: '123' + }, 'test') +})