Skip to content

Commit

Permalink
Add formatters for core log components (pinojs#775)
Browse files Browse the repository at this point in the history
* WIP: added bindings and levels serializers

* Addressed comments

* Fixed test

* Moved from custom srializers to formatters

* Use stricter check in genLsCache

* Updated test

* Addressed comments

* Added log formatter and removed pino.* serializer

* Updated test

* Use factory for formatters object

* Added deprecations messages
- deprecated useLevelLabels
- deprecated changeLevelName
- deprecated pino.* serializer

* Updated test

* Addressed comments

* Fix test

* Improve code coverage

* Fixed levelKey handling

* Updated test

* Addressed comments

* Updated test

* Added formatters benchmarks

* Rename test

* 100% code coverage

* Added formatters to benchmark runner

* Updated documentation

* Update docs/api.md

Co-Authored-By: James Sumners <[email protected]>

* Update docs/api.md

Co-Authored-By: James Sumners <[email protected]>

* Update docs/api.md

Co-Authored-By: James Sumners <[email protected]>

* Update docs/api.md

Co-Authored-By: James Sumners <[email protected]>

* Update docs/api.md

Co-Authored-By: James Sumners <[email protected]>

* Update docs/api.md

Co-Authored-By: James Sumners <[email protected]>

* Addressed comments

Co-authored-by: James Sumners <[email protected]>
Co-authored-by: Matteo Collina <[email protected]>
  • Loading branch information
3 people authored Mar 18, 2020
1 parent 5c85000 commit f3ab176
Show file tree
Hide file tree
Showing 15 changed files with 716 additions and 125 deletions.
50 changes: 50 additions & 0 deletions benchmarks/formatters.bench.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict'

const formatters = {
level (label, number) {
return {
log: {
level: label
}
}
},
bindings (bindings) {
return {
process: {
pid: bindings.pid
},
host: {
name: bindings.hostname
}
}
},
log (obj) {
return { foo: 'bar', ...obj }
}
}

const bench = require('fastbench')
const pino = require('../')
delete require.cache[require.resolve('../')]
const pinoNoFormatters = require('../')(pino.destination('/dev/null'))
delete require.cache[require.resolve('../')]
const pinoFormatters = require('../')({ formatters }, pino.destination('/dev/null'))

const max = 10

const run = bench([
function benchPinoNoFormatters (cb) {
for (var i = 0; i < max; i++) {
pinoNoFormatters.info({ hello: 'world' })
}
setImmediate(cb)
},
function benchPinoFormatters (cb) {
for (var i = 0; i < max; i++) {
pinoFormatters.info({ hello: 'world' })
}
setImmediate(cb)
}
], 10000)

run(run)
4 changes: 3 additions & 1 deletion benchmarks/utils/runbench.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function usage () {
・child ⁃ child from a parent
・child-child ⁃ child from a child
・child-creation ⁃ child constructor
・formatters ⁃ difference between with or without formatters
Example:
Expand All @@ -46,7 +47,8 @@ const benchmarks = {
'long-string': 'long-string.bench.js',
child: 'child.bench.js',
'child-child': 'child-child.bench.js',
'child-creation': 'child-creation.bench.js'
'child-creation': 'child-creation.bench.js',
formatters: 'formatters.bench.js'
}

function runBenchmark (name, done) {
Expand Down
81 changes: 59 additions & 22 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,58 @@ If an object is supplied, three options can be specified:
* See the [redaction ⇗](/docs/redaction.md) documentation.
* See [fast-redact#caveat ⇗](http://github.com/davidmarkclements/fast-redact#caveat)

<a id=opt-formatters></a>
#### `formatters` (Object)

An object containing 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.

##### `level`

Changes the shape of the log level. The default shape is `{ level: number }`.
The function takes two arguments, the label of the level (e.g. `'info'`)
and the numeric value (e.g. `30`).

```js
const formatters = {
level (label, number) {
return { level: number }
}
}
```

##### `bindings`

Changes the shape of the bindings. The default shape is `{ pid, hostname }`.
The function takes a single argument, the bindings object. It will
be called every time a child logger is created.

```js
const formatters = {
bindings (bindings) {
return { pid: bindings.pid, hostname: bindings.hostname }
}
}
```

##### `log`

Changes the shape of the log object. This function will be called every time
one of the log methods (such as `.info`) is called. All arguments passed to the
log method, except the message, will be pass to this function. By default it does
not change the shape of the log object.

```js
const formatters = {
log (object) {
return object
}
}
```

<a id=opt-serializers></a>
#### `serializers` (Object)

Expand All @@ -149,12 +201,9 @@ matching the exact key of a serializer will be serialized using the defined seri

* See [pino.stdSerializers](#pino-stdserializers)

##### `serializers[Symbol.for('pino.*')]` (Function)

Default: `undefined`
##### `serializers[Symbol.for('pino.*')]` (Function) - DEPRECATED

The `serializers` object may contain a key which is the global symbol: `Symbol.for('pino.*')`.
This will act upon the complete log object rather than corresponding to a particular key.
Use `formatters.log` instead.

#### `base` (Object)

Expand Down Expand Up @@ -243,30 +292,18 @@ npm install pino-pretty
```

<a id="useLevelLabels"></a>
#### `useLevelLabels` (Boolean)
#### `useLevelLabels` (Boolean) - DEPRECATED

Default: `false`

Enables printing of level labels instead of level values in the printed logs.
Warning: this option may not be supported by downstream transports.
Use `formatters.level` instead. This will be removed in v7.

<a id="changeLevelName"></a>
#### `changeLevelName` (String) - DEPRECATED
Use `levelKey` instead. This will be removed in v7.
Use `formatters.level` instead. This will be removed in v7.

<a id="levelKey"></a>
#### `levelKey` (String)
#### `levelKey` (String) - DEPRECATED

Default: `'level'`

Changes the property `level` to any string value you pass in:
```js
const logger = pino({
levelKey: 'priority'
})
logger.info('hello world')
// {"priority":30,"time":1531257112193,"msg":"hello world","pid":55956,"hostname":"x"}
```
Use `formatters.level` instead. This will be removed in v7.

#### `browser` (Object)

Expand Down
21 changes: 11 additions & 10 deletions lib/levels.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use strict'
/* eslint no-prototype-builtins: 0 */
const flatstr = require('flatstr')
const {
lsCacheSym,
levelValSym,
useLevelLabelsSym,
levelKeySym,
useOnlyCustomLevelsSym,
streamSym
streamSym,
formattersSym
} = require('./symbols')
const { noop, genLog } = require('./tools')

Expand Down Expand Up @@ -49,13 +49,14 @@ const initialLsCache = Object.keys(nums).reduce((o, k) => {
}, {})

function genLsCache (instance) {
const levelName = instance[levelKeySym]
instance[lsCacheSym] = Object.keys(instance.levels.labels).reduce((o, k) => {
o[k] = instance[useLevelLabelsSym]
? `{"${levelName}":"${instance.levels.labels[k]}"`
: flatstr(`{"${levelName}":` + Number(k))
return o
}, Object.assign({}, instance[lsCacheSym]))
const formatter = instance[formattersSym].level
const { labels } = instance.levels
const cache = {}
for (const label in labels) {
const level = formatter(labels[label], Number(label))
cache[label] = JSON.stringify(level).slice(0, -1)
}
instance[lsCacheSym] = cache
return instance
}

Expand Down
40 changes: 27 additions & 13 deletions lib/proto.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ const {
chindingsSym,
mixinSym,
asJsonSym,
messageKeySym,
writeSym,
timeSym,
timeSliceIndexSym,
streamSym,
serializersSym,
formattersSym,
useOnlyCustomLevelsSym,
needsMetadataGsym
} = require('./symbols')
Expand All @@ -33,7 +33,8 @@ const {
} = require('./levels')
const {
asChindings,
asJson
asJson,
buildFormatters
} = require('./tools')
const {
version
Expand Down Expand Up @@ -65,10 +66,13 @@ Object.setPrototypeOf(prototype, EventEmitter.prototype)

module.exports = prototype

const resetChildingsFormatter = bindings => bindings
function child (bindings) {
const { level } = this
if (!bindings) {
throw Error('missing bindings for child Pino')
}
const serializers = this[serializersSym]
const chindings = asChindings(this, bindings)
const formatters = this[formattersSym]
const instance = Object.create(this)
if (bindings.hasOwnProperty('serializers') === true) {
instance[serializersSym] = Object.create(null)
Expand All @@ -91,13 +95,27 @@ function child (bindings) {
instance[serializersSym][bks] = bindings.serializers[bks]
}
} else instance[serializersSym] = serializers
if (bindings.hasOwnProperty('formatters')) {
const { level, bindings: chindings, log } = bindings.formatters
instance[formattersSym] = buildFormatters(
level || formatters.level,
chindings || resetChildingsFormatter,
log || formatters.log
)
} else {
instance[formattersSym] = buildFormatters(
formatters.level,
resetChildingsFormatter,
formatters.log
)
}
if (bindings.hasOwnProperty('customLevels') === true) {
assertNoLevelCollisions(this.levels, bindings.customLevels)
instance.levels = mappings(bindings.customLevels, instance[useOnlyCustomLevelsSym])
genLsCache(instance)
}
instance[chindingsSym] = chindings
const childLevel = bindings.level || level
instance[chindingsSym] = asChindings(instance, bindings)
const childLevel = bindings.level || this.level
instance[setLevelSym](childLevel)

return instance
Expand All @@ -119,20 +137,16 @@ function setBindings (newBindings) {

function write (_obj, msg, num) {
const t = this[timeSym]()
const messageKey = this[messageKeySym]
const mixin = this[mixinSym]
const objError = _obj instanceof Error
var obj

if (_obj === undefined || _obj === null) {
obj = mixin ? mixin() : {}
obj[messageKey] = msg
} else {
obj = Object.assign(mixin ? mixin() : {}, _obj)
if (msg) {
obj[messageKey] = msg
} else if (objError) {
obj[messageKey] = _obj.message
if (!msg && objError) {
msg = _obj.message
}

if (objError) {
Expand All @@ -143,7 +157,7 @@ function write (_obj, msg, num) {
}
}

const s = this[asJsonSym](obj, num, t)
const s = this[asJsonSym](obj, msg, num, t)

const stream = this[streamSym]
if (stream[needsMetadataGsym] === true) {
Expand Down
8 changes: 3 additions & 5 deletions lib/symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const setLevelSym = Symbol('pino.setLevel')
const getLevelSym = Symbol('pino.getLevel')
const levelValSym = Symbol('pino.levelVal')
const useLevelLabelsSym = Symbol('pino.useLevelLabels')
const levelKeySym = Symbol('pino.levelKey')
const useOnlyCustomLevelsSym = Symbol('pino.useOnlyCustomLevels')
const mixinSym = Symbol('pino.mixin')

Expand All @@ -31,7 +30,7 @@ const wildcardFirstSym = Symbol('pino.wildcardFirst')
// public symbols, no need to use the same pino
// version for these
const serializersSym = Symbol.for('pino.serializers')
const wildcardGsym = Symbol.for('pino.*')
const formattersSym = Symbol.for('pino.formatters')
const needsMetadataGsym = Symbol.for('pino.metadata')

module.exports = {
Expand All @@ -57,8 +56,7 @@ module.exports = {
messageKeySym,
nestedKeySym,
wildcardFirstSym,
levelKeySym,
wildcardGsym,
needsMetadataGsym,
useOnlyCustomLevelsSym
useOnlyCustomLevelsSym,
formattersSym
}
Loading

0 comments on commit f3ab176

Please sign in to comment.