Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom levels to override default and allow to remove default levels #515

Merged
merged 1 commit into from
Sep 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,27 @@ const logger = pino({
logger.foo('hi')
```

<a id=opt-useOnlyCustomLevels></a>
#### `useOnlyCustomLevels` (Boolean)
jsumners marked this conversation as resolved.
Show resolved Hide resolved

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
Original file line number Diff line number Diff line change
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),
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading