Skip to content

Commit

Permalink
Allow custom levels to override default and allow to remove default l…
Browse files Browse the repository at this point in the history
…evels (#515)
  • Loading branch information
sooryranga authored and mcollina committed Sep 16, 2018
1 parent 7dd98cc commit e870ff3
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 80 deletions.
21 changes: 21 additions & 0 deletions docs/api.md
Expand Up @@ -73,6 +73,27 @@ const logger = pino({
logger.foo('hi')
```

<a id=opt-useOnlyCustomLevels></a>
#### `useOnlyCustomLevels` (Boolean)

Default: `false`

Use this option to only use defined `customLevels` and omit Pino's levels.
Logger's default `level` must be changed to a value in `customLevels` in order to use `useOnlyCustomLevels`
Warning: this option may not be supported by downstream transports.

```js
const logger = pino({
customLevels: {
foo: 35
},
useOnlyCustomLevels: true,
level: 'foo'
})
logger.foo('hi')
logger.info('hello') // Will throw an error saying info in not found in logger object
```

#### `redact` (Array | Object):

Default: `undefined`
Expand Down
35 changes: 30 additions & 5 deletions lib/levels.js
Expand Up @@ -94,24 +94,48 @@ function isLevelEnabled (logLevel) {
return logLevelVal !== undefined && (logLevelVal >= this[levelValSym])
}

function mappings (customLevels = null) {
function mappings (customLevels = null, useOnlyCustomLevels = false) {
const customNums = customLevels ? Object.keys(customLevels).reduce((o, k) => {
o[customLevels[k]] = k
return o
}, {}) : null

const labels = Object.assign(
Object.create(Object.prototype, { Infinity: { value: 'silent' } }),
nums,
useOnlyCustomLevels ? null : nums,
customNums
)
const values = Object.assign(
Object.create(Object.prototype, { silent: { value: Infinity } }),
levels,
useOnlyCustomLevels ? null : levels,
customLevels
)
return { labels, values }
}

function assertDefaultLevelFound (defaultLevel, customLevels, useOnlyCustomLevels) {
if (typeof defaultLevel === 'number') {
const values = [].concat(
Object.keys(customLevels || {}).map(key => customLevels[key]),
useOnlyCustomLevels ? [] : Object.keys(nums),

This comment has been minimized.

Copy link
@husticdf

husticdf Sep 17, 2018

Contributor

Since levels are numbers, this should be
useOnlyCustomLevels ? [] : Object.keys(nums).map(level => +level),

Otherwise, the if check 3 lines down is broken.

This comment has been minimized.

Copy link
@mcollina

mcollina Sep 17, 2018

Member

@husticdf can you please open a pull request with a fix? Thanks!

Infinity
)
if (!values.includes(defaultLevel)) {
throw Error(`default level:${defaultLevel} must be included in custom levels`)
}
return
}

const labels = Object.assign(
Object.create(Object.prototype, { silent: { value: Infinity } }),
useOnlyCustomLevels ? null : levels,
customLevels
)
if (!(defaultLevel in labels)) {
throw Error(`default level:${defaultLevel} must be included in custom levels`)
}
}

function assertNoLevelCollisions (levels, customLevels) {
const { labels, values } = levels
for (const k in customLevels) {
Expand All @@ -125,12 +149,13 @@ function assertNoLevelCollisions (levels, customLevels) {
}

module.exports = {
assertNoLevelCollisions,
initialLsCache,
genLsCache,
levelMethods,
getLevel,
setLevel,
isLevelEnabled,
mappings
mappings,
assertNoLevelCollisions,
assertDefaultLevelFound
}
3 changes: 2 additions & 1 deletion lib/proto.js
Expand Up @@ -13,6 +13,7 @@ const {
timeSym,
streamSym,
serializersSym,
useOnlyCustomLevelsSym,
needsMetadataGsym
} = require('./symbols')
const {
Expand Down Expand Up @@ -73,7 +74,7 @@ function child (bindings) {
} else instance[serializersSym] = serializers
if (bindings.hasOwnProperty('customLevels') === true) {
assertNoLevelCollisions(this.levels, bindings.customLevels)
instance.levels = mappings(bindings.customLevels)
instance.levels = mappings(bindings.customLevels, instance[useOnlyCustomLevelsSym])
genLsCache(instance)
}
instance[chindingsSym] = chindings
Expand Down
4 changes: 3 additions & 1 deletion lib/symbols.js
Expand Up @@ -5,6 +5,7 @@ const getLevelSym = Symbol('pino.getLevel')
const levelValSym = Symbol('pino.levelVal')
const useLevelLabelsSym = Symbol('pino.useLevelLabels')
const changeLevelNameSym = Symbol('pino.changeLevelName')
const useOnlyCustomLevelsSym = Symbol('pino.useOnlyCustomLevels')

const lsCacheSym = Symbol('pino.lsCache')
const chindingsSym = Symbol('pino.chindings')
Expand Down Expand Up @@ -49,5 +50,6 @@ module.exports = {
messageKeyStringSym,
changeLevelNameSym,
wildcardGsym,
needsMetadataGsym
needsMetadataGsym,
useOnlyCustomLevelsSym
}
19 changes: 12 additions & 7 deletions pino.js
Expand Up @@ -6,7 +6,7 @@ const redaction = require('./lib/redaction')
const time = require('./lib/time')
const proto = require('./lib/proto')
const symbols = require('./lib/symbols')
const { mappings, genLsCache, assertNoLevelCollisions } = require('./lib/levels')
const { assertDefaultLevelFound, mappings, genLsCache } = require('./lib/levels')
const {
createArgsNormalizer,
asChindings,
Expand All @@ -27,7 +27,8 @@ const {
formatOptsSym,
messageKeyStringSym,
useLevelLabelsSym,
changeLevelNameSym
changeLevelNameSym,
useOnlyCustomLevelsSym
} = symbols
const { epochTime, nullTime } = time
const { pid } = process
Expand All @@ -45,7 +46,8 @@ const defaultOptions = {
name: undefined,
redact: null,
customLevels: null,
changeLevelName: 'level'
changeLevelName: 'level',
useOnlyCustomLevels: false
}

const normalize = createArgsNormalizer(defaultOptions)
Expand All @@ -63,11 +65,10 @@ function pino (...args) {
level,
customLevels,
useLevelLabels,
changeLevelName
changeLevelName,
useOnlyCustomLevels
} = opts

assertNoLevelCollisions(pino.levels, customLevels)

const stringifiers = redact ? redaction(redact, stringify) : {}
const formatOpts = redact
? { stringify: stringifiers[redactFmtSym] }
Expand All @@ -85,12 +86,16 @@ function pino (...args) {
const time = (timestamp instanceof Function)
? timestamp : (timestamp ? epochTime : nullTime)

const levels = mappings(customLevels)
if (useOnlyCustomLevels && !customLevels) throw Error('customLevels is required if useOnlyCustomLevels is set true')

assertDefaultLevelFound(level, customLevels, useOnlyCustomLevels)
const levels = mappings(customLevels, useOnlyCustomLevels)

const instance = {
levels,
[useLevelLabelsSym]: useLevelLabels,
[changeLevelNameSym]: changeLevelName,
[useOnlyCustomLevelsSym]: useOnlyCustomLevels,
[streamSym]: stream,
[timeSym]: time,
[stringifySym]: stringify,
Expand Down
15 changes: 15 additions & 0 deletions test/basic.test.js
Expand Up @@ -515,3 +515,18 @@ test('fast-safe-stringify must be used when interpolating', async (t) => {
const { msg } = await once(stream, 'data')
t.is(msg, 'test {"a":{"b":{"c":"[Circular]"}}}')
})

test('throws when setting useOnlyCustomLevels without customLevels', async ({ is, throws }) => {
throws(() => {
pino({
useOnlyCustomLevels: true
})
})
try {
pino({
useOnlyCustomLevels: true
})
} catch ({ message }) {
is(message, 'customLevels is required if useOnlyCustomLevels is set true')
}
})
129 changes: 63 additions & 66 deletions test/custom-levels.test.js
Expand Up @@ -18,42 +18,43 @@ test('adds additional levels', async ({ is }) => {
is(level, 35)
})

test('throws when specifying existing levels', async ({ is, throws }) => {
test('custom levels does not override default levels', async ({ is }) => {
const stream = sink()
throws(() => pino({
const logger = pino({
customLevels: {
info: 35
foo: 35
}
}, stream)
)
try {
pino({
customLevels: {
info: 35
}
})
} catch ({ message }) {
is(message, 'levels cannot be overridden')
}

logger.info('test')
const { level } = await once(stream, 'data')
is(level, 30)
})

test('throws when specifying existing values', async ({ is, throws }) => {
test('custom levels overrides default level label if use useOnlyCustomLevels', async ({ is }) => {
const stream = sink()
throws(() => pino({
const logger = pino({
customLevels: {
foo: 30
}
foo: 35
},
useOnlyCustomLevels: true,
level: 'foo'
}, stream)
)
try {
pino({
customLevels: {
foo: 30
}
})
} catch ({ message }) {
is(message, 'pre-existing level values cannot be used for new levels')
}

is(logger.hasOwnProperty('info'), false)
})

test('custom levels overrides default level value if use useOnlyCustomLevels', async ({ is }) => {
const stream = sink()
const logger = pino({
customLevels: {
foo: 35
},
useOnlyCustomLevels: true,
level: 35
}, stream)

is(logger.hasOwnProperty('info'), false)
})

test('custom levels are inherited by children', async ({ is }) => {
Expand Down Expand Up @@ -101,45 +102,7 @@ test('customLevels property child bindings does not get logged', async ({ is })
is(customLevels, undefined)
})

test('throws when specifying core levels via child bindings', async ({ is, throws }) => {
const stream = sink()
throws(() => pino(stream).child({
customLevels: {
info: 35
}
})
)
try {
pino(stream).child({
customLevels: {
info: 35
}
})
} catch ({ message }) {
is(message, 'levels cannot be overridden')
}
})

test('throws when specifying core values via child bindings', async ({ is, throws }) => {
const stream = sink()
throws(() => pino(stream).child({
customLevels: {
foo: 30
}
})
)
try {
pino(stream).child({
customLevels: {
foo: 30
}
})
} catch ({ message }) {
is(message, 'pre-existing level values cannot be used for new levels')
}
})

test('throws when specifying pre-existing parent levels via child bindings', async ({ is, throws }) => {
test('throws when specifying pre-existing parent labels via child bindings', async ({ is, throws }) => {
const stream = sink()
throws(() => pino({
customLevels: {
Expand Down Expand Up @@ -193,6 +156,40 @@ test('throws when specifying pre-existing parent values via child bindings', asy
}
})

test('throws when specifying core values via child bindings', async ({ is, throws }) => {
const stream = sink()
throws(() => pino(stream).child({
customLevels: {
foo: 30
}
})
)
try {
pino(stream).child({
customLevels: {
foo: 30
}
})
} catch ({ message }) {
is(message, 'pre-existing level values cannot be used for new levels')
}
})

test('throws when useOnlyCustomLevels is set true without customLevels', async ({ is, throws }) => {
const stream = sink()
throws(() => pino({
useOnlyCustomLevels: true
}, stream)
)
try {
pino({
useOnlyCustomLevels: true
}, stream)
} catch ({ message }) {
is(message, 'customLevels is required if useOnlyCustomLevels is set true')
}
})

test('custom level on one instance does not affect other instances', async ({ is }) => {
pino({ customLevels: {
foo: 37
Expand Down

0 comments on commit e870ff3

Please sign in to comment.