Skip to content

Commit

Permalink
add option mixinMergeStrategy (to v7)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexpts committed Feb 15, 2022
1 parent ef5e2e6 commit b777d94
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 14 deletions.
65 changes: 64 additions & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Default: `100`

Option to limit stringification of properties/elements when logging a specific object/array with circular references.

<a id="opt-mixin"></a>
#### `mixin` (Function):

Default: `undefined`
Expand All @@ -130,7 +131,8 @@ logger.info('world')
```

The result of `mixin()` is supposed to be a _new_ object. For performance reason, the object returned by `mixin()` will be mutated by pino.
In the following example, passing `mergingObject` argument to the first `info` call will mutate the global `mixin` object:
In the following example, passing `mergingObject` argument to the first `info` call will mutate the global `mixin` object by default:
(* See [`mixinMergeStrategy` option](#opt-mixin-merge-strategy)):
```js
const mixin = {
appName: 'My app'
Expand All @@ -154,6 +156,67 @@ logger.info('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.

<a id="opt-mixin-merge-strategy"></a>
#### `mixinMergeStrategy` (Function):

Default: `undefined`

If provided, the `mixinMergeStrategy` 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 value resulting from `mixin()` (* See [`mixin` option](#opt-mixin) or an empty object.
The function must synchronously return an object.

```js
// Default strategy, `mergeObject` has priority
const logger = pino({
mixin() {
return { tag: 'docker' }
},
// mixinMergeStrategy(mergeObject, mixinObject) {
// return Object.assign(mixinMeta, mergeObject)
// }
})

logger.info({
tag: 'local'
}, 'Message')
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","tag":"local","msg":"Message"}
```

```js
// Custom mutable strategy, `mixin` has priority
const logger = pino({
mixin() {
return { tag: 'k8s' }
},
mixinMergeStrategy(mergeObject, mixinObject) {
return Object.assign(mergeObject, mixinObject)
}
})

logger.info({
tag: 'local'
}, 'Message')
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","tag":"k8s","msg":"Message"}
```

```js
// Custom immutable strategy, `mixin` has priority
const logger = pino({
mixin() {
return { tag: 'k8s' }
},
mixinMergeStrategy(mergeObject, mixinObject) {
return Object.assign({}, mergeObject, mixinObject)
}
})

logger.info({
tag: 'local'
}, 'Message')
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","tag":"k8s","msg":"Message"}
```

<a id="opt-redact"></a>
#### `redact` (Array | Object):

Expand Down
18 changes: 16 additions & 2 deletions lib/proto.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const {
mixinSym,
asJsonSym,
writeSym,
mixinMergeStrategySym,
timeSym,
timeSliceIndexSym,
streamSym,
Expand Down Expand Up @@ -158,20 +159,33 @@ function setBindings (newBindings) {
delete this[parsedChindingsSym]
}

/**
* Default strategy for creating `mergeObject` from arguments and the result from `mixin()`.
* Fields from `mergeObject` have higher priority in this strategy.
*
* @param {Object} mergeObject The object a user has supplied to the logging function.
* @param {Object} mixinObject The result of the `mixin` method.
* @return {Object}
*/
function defaultMixinMergeStrategy (mergeObject, mixinObject) {
return Object.assign(mixinObject, mergeObject)
}

function write (_obj, msg, num) {
const t = this[timeSym]()
const mixin = this[mixinSym]
const mixinMergeStrategy = this[mixinMergeStrategySym] || defaultMixinMergeStrategy
let obj

if (_obj === undefined || _obj === null) {
obj = mixin ? mixin({}) : {}
} else if (_obj instanceof Error) {
obj = Object.assign(mixin ? mixin({}) : {}, { err: _obj })
obj = mixinMergeStrategy({ err: _obj }, mixin ? mixin(_obj) : {})
if (msg === undefined) {
msg = _obj.message
}
} else {
obj = Object.assign(mixin ? mixin({}) : {}, _obj)
obj = mixinMergeStrategy(_obj, mixin ? mixin(_obj) : {})
if (msg === undefined && _obj.err) {
msg = _obj.err.message
}
Expand Down
4 changes: 3 additions & 1 deletion lib/symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const formatOptsSym = Symbol('pino.formatOpts')
const messageKeySym = Symbol('pino.messageKey')
const nestedKeySym = Symbol('pino.nestedKey')
const nestedKeyStrSym = Symbol('pino.nestedKeyStr')
const mixinMergeStrategySym = Symbol('pino.mixinMergeStrategy')

const wildcardFirstSym = Symbol('pino.wildcardFirst')

Expand Down Expand Up @@ -64,5 +65,6 @@ module.exports = {
useOnlyCustomLevelsSym,
formattersSym,
hooksSym,
nestedKeyStrSym
nestedKeyStrSym,
mixinMergeStrategySym
}
27 changes: 18 additions & 9 deletions pino.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import type { WorkerOptions } from "worker_threads";
type ThreadStream = any

type TimeFn = () => string;
type MixinFn = () => object;
type MixinFn = (mergeObject: object) => object;
type MixinMergeStrategyFn = (mergeObject: object, mixinObject: object) => object;

type CustomLevelLogger<Options> = Options extends { customLevels: Record<string, number> } ? Record<keyof Options["customLevels"], LogFn> : Record<never, LogFn>

Expand Down Expand Up @@ -107,7 +108,7 @@ interface LoggerExtras<Options = LoggerOptions> extends EventEmitter {

declare namespace pino {
//// Exported types and interfaces

interface BaseLogger {
/**
* Set this property to the desired logging level. In order of priority, available levels are:
Expand All @@ -125,7 +126,7 @@ declare namespace pino {
* You can pass `'silent'` to disable logging.
*/
level: pino.LevelWithSilent | string;

/**
* Log at `'fatal'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line.
* If more args follows `msg`, these will be used to format `msg` using `util.format`.
Expand Down Expand Up @@ -199,14 +200,14 @@ declare namespace pino {

type SerializerFn = (value: any) => any;
type WriteFn = (o: object) => void;

type LevelChangeEventListener = (
lvl: LevelWithSilent | string,
val: number,
prevLvl: LevelWithSilent | string,
prevVal: number,
) => void;

type LogDescriptor = Record<string, any>;

type Logger<Options = LoggerOptions> = BaseLogger & LoggerExtras<Options> & CustomLevelLogger<Options>;
Expand Down Expand Up @@ -331,6 +332,14 @@ declare namespace pino {
*/
mixin?: MixinFn;

/**
* If provided, the `mixinMergeStrategy` 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 value resulting from `mixin()` or an empty object.
* The function must synchronously return an object.
*/
mixinMergeStrategy?: MixinMergeStrategyFn

/**
* As an array, the redact option specifies paths that should have their values redacted from any log output.
*
Expand Down Expand Up @@ -666,12 +675,12 @@ declare namespace pino {
readonly formattersSym: unique symbol;
readonly hooksSym: unique symbol;
};

/**
* Exposes the Pino package version. Also available on the logger instance.
*/
export const version: string;

/**
* Provides functions for generating the timestamp property in the log output. You can set the `timestamp` option during
* initialization to one of these functions to adjust the output format. Alternatively, you can specify your own time function.
Expand All @@ -696,7 +705,7 @@ declare namespace pino {
*/
isoTime: TimeFn;
};

//// Exported functions

/**
Expand Down Expand Up @@ -750,7 +759,7 @@ declare function pino<Options extends LoggerOptions | DestinationStream>(options
* @returns a new logger instance.
*/
declare function pino<Options extends LoggerOptions>(options: Options, stream: DestinationStream): Logger<Options>;


// Pass through all the top-level exports, allows `import {version} from "pino"`
// Constants and functions
Expand Down
5 changes: 4 additions & 1 deletion pino.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ const {
useOnlyCustomLevelsSym,
formattersSym,
hooksSym,
nestedKeyStrSym
nestedKeyStrSym,
mixinMergeStrategySym
} = symbols
const { epochTime, nullTime } = time
const { pid } = process
Expand Down Expand Up @@ -94,6 +95,7 @@ function pino (...args) {
level,
customLevels,
mixin,
mixinMergeStrategy,
useOnlyCustomLevels,
formatters,
hooks,
Expand Down Expand Up @@ -166,6 +168,7 @@ function pino (...args) {
[nestedKeyStrSym]: nestedKey ? `,${JSON.stringify(nestedKey)}:{` : '',
[serializersSym]: serializers,
[mixinSym]: mixin,
[mixinMergeStrategySym]: mixinMergeStrategy,
[chindingsSym]: chindings,
[formattersSym]: allFormatters,
[hooksSym]: hooks,
Expand Down
55 changes: 55 additions & 0 deletions test/mixin-merge-strategy.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict'

const { test } = require('tap')
const { sink, once } = require('./helper')
const pino = require('../')

const level = 50
const name = 'error'

test('default merge strategy', async ({ ok, same }) => {
const stream = sink()
const instance = pino({
base: {},
mixin () {
return { tag: 'k8s' }
}
}, stream)
instance.level = name
instance[name]({
tag: 'local'
}, 'test')
const result = await once(stream, 'data')
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
delete result.time
same(result, {
level,
msg: 'test',
tag: 'local'
})
})

test('custom merge strategy with mixin priority', async ({ ok, same }) => {
const stream = sink()
const instance = pino({
base: {},
mixin () {
return { tag: 'k8s' }
},
mixinMergeStrategy (mergeObject, mixinObject) {
return Object.assign(mergeObject, mixinObject)
}
}, stream)
instance.level = name
instance[name]({
tag: 'local'
}, 'test')
const result = await once(stream, 'data')
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
delete result.time
same(result, {
level,
msg: 'test',
tag: 'k8s'
})
})

0 comments on commit b777d94

Please sign in to comment.