Skip to content

Commit

Permalink
feat: pass logger/child logger as param to mixin
Browse files Browse the repository at this point in the history
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`')
```
  • Loading branch information
mmarchini committed May 4, 2023
1 parent 27d2ab8 commit 7774bfc
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 2 deletions.
37 changes: 36 additions & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/proto.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
56 changes: 56 additions & 0 deletions test/mixin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})

0 comments on commit 7774bfc

Please sign in to comment.