Skip to content

Commit 384df31

Browse files
Add ability to override custom levels comparison (#1883)
* feat: add ability to override custom levels compare * fix: use function instead of closure * docs: update level comparison to docs * test: update types tests for level comparison * refactor: move default levels and sorting order to constants * fix: made suggested changes in pr review * fix: change enum annotation type
1 parent fdd0fd9 commit 384df31

9 files changed

+330
-58
lines changed

docs/api.md

+26
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,32 @@ Additional levels can be added to the instance via the `customLevels` option.
6565
* See [`customLevels` option](#opt-customlevels)
6666

6767
<a id=opt-customlevels></a>
68+
69+
#### `levelComparison` ("ASC", "DESC", Function)
70+
71+
Default: `ASC`
72+
73+
Use this option to customize levels order.
74+
In order to be able to define custom levels ordering pass a function which will accept `current` and `expected` values and return `boolean` which shows should `current` level to be shown or not.
75+
76+
```js
77+
const logger = pino({
78+
levelComparison: 'DESC',
79+
customLevels: {
80+
foo: 20, // `foo` is more valuable than `bar`
81+
bar: 10
82+
},
83+
})
84+
85+
// OR
86+
87+
const logger = pino({
88+
levelComparison: function(current, expected) {
89+
return current >= expected;
90+
}
91+
})
92+
```
93+
6894
#### `customLevels` (Object)
6995

7096
Default: `undefined`

lib/constants.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Represents default log level values
3+
*
4+
* @enum {number}
5+
*/
6+
const DEFAULT_LEVELS = {
7+
trace: 10,
8+
debug: 20,
9+
info: 30,
10+
warn: 40,
11+
error: 50,
12+
fatal: 60
13+
}
14+
15+
/**
16+
* Represents sort order direction: `ascending` or `descending`
17+
*
18+
* @enum {string}
19+
*/
20+
const SORTING_ORDER = {
21+
ASC: 'ASC',
22+
DESC: 'DESC'
23+
}
24+
25+
module.exports = {
26+
DEFAULT_LEVELS,
27+
SORTING_ORDER
28+
}

lib/levels.js

+68-22
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,15 @@ const {
66
useOnlyCustomLevelsSym,
77
streamSym,
88
formattersSym,
9-
hooksSym
9+
hooksSym,
10+
levelCompSym
1011
} = require('./symbols')
1112
const { noop, genLog } = require('./tools')
13+
const { DEFAULT_LEVELS, SORTING_ORDER } = require('./constants')
1214

13-
const levels = {
14-
trace: 10,
15-
debug: 20,
16-
info: 30,
17-
warn: 40,
18-
error: 50,
19-
fatal: 60
20-
}
2115
const levelMethods = {
2216
fatal: (hook) => {
23-
const logFatal = genLog(levels.fatal, hook)
17+
const logFatal = genLog(DEFAULT_LEVELS.fatal, hook)
2418
return function (...args) {
2519
const stream = this[streamSym]
2620
logFatal.call(this, ...args)
@@ -33,15 +27,15 @@ const levelMethods = {
3327
}
3428
}
3529
},
36-
error: (hook) => genLog(levels.error, hook),
37-
warn: (hook) => genLog(levels.warn, hook),
38-
info: (hook) => genLog(levels.info, hook),
39-
debug: (hook) => genLog(levels.debug, hook),
40-
trace: (hook) => genLog(levels.trace, hook)
30+
error: (hook) => genLog(DEFAULT_LEVELS.error, hook),
31+
warn: (hook) => genLog(DEFAULT_LEVELS.warn, hook),
32+
info: (hook) => genLog(DEFAULT_LEVELS.info, hook),
33+
debug: (hook) => genLog(DEFAULT_LEVELS.debug, hook),
34+
trace: (hook) => genLog(DEFAULT_LEVELS.trace, hook)
4135
}
4236

43-
const nums = Object.keys(levels).reduce((o, k) => {
44-
o[levels[k]] = k
37+
const nums = Object.keys(DEFAULT_LEVELS).reduce((o, k) => {
38+
o[DEFAULT_LEVELS[k]] = k
4539
return o
4640
}, {})
4741

@@ -119,7 +113,39 @@ function getLevel (level) {
119113
function isLevelEnabled (logLevel) {
120114
const { values } = this.levels
121115
const logLevelVal = values[logLevel]
122-
return logLevelVal !== undefined && (logLevelVal >= this[levelValSym])
116+
return logLevelVal !== undefined && this[levelCompSym](logLevelVal, this[levelValSym])
117+
}
118+
119+
/**
120+
* Determine if the given `current` level is enabled by comparing it
121+
* against the current threshold (`expected`).
122+
*
123+
* @param {SORTING_ORDER} direction comparison direction "ASC" or "DESC"
124+
* @param {number} current current log level number representatiton
125+
* @param {number} expected threshold value to compare with
126+
* @returns {boolean}
127+
*/
128+
function compareLevel (direction, current, expected) {
129+
if (direction === SORTING_ORDER.DESC) {
130+
return current <= expected
131+
}
132+
133+
return current >= expected
134+
}
135+
136+
/**
137+
* Create a level comparison function based on `levelComparison`
138+
* it could a default function which compares levels either in "ascending" or "descending" order or custom comparison function
139+
*
140+
* @param {SORTING_ORDER | Function} levelComparison sort levels order direction or custom comparison function
141+
* @returns Function
142+
*/
143+
function genLevelComparison (levelComparison) {
144+
if (typeof levelComparison === 'string') {
145+
return compareLevel.bind(null, levelComparison)
146+
}
147+
148+
return levelComparison
123149
}
124150

125151
function mappings (customLevels = null, useOnlyCustomLevels = false) {
@@ -139,7 +165,7 @@ function mappings (customLevels = null, useOnlyCustomLevels = false) {
139165
)
140166
const values = Object.assign(
141167
Object.create(Object.prototype, { silent: { value: Infinity } }),
142-
useOnlyCustomLevels ? null : levels,
168+
useOnlyCustomLevels ? null : DEFAULT_LEVELS,
143169
customLevels
144170
)
145171
return { labels, values }
@@ -160,7 +186,7 @@ function assertDefaultLevelFound (defaultLevel, customLevels, useOnlyCustomLevel
160186

161187
const labels = Object.assign(
162188
Object.create(Object.prototype, { silent: { value: Infinity } }),
163-
useOnlyCustomLevels ? null : levels,
189+
useOnlyCustomLevels ? null : DEFAULT_LEVELS,
164190
customLevels
165191
)
166192
if (!(defaultLevel in labels)) {
@@ -180,6 +206,25 @@ function assertNoLevelCollisions (levels, customLevels) {
180206
}
181207
}
182208

209+
/**
210+
* Validates whether `levelComparison` is correct
211+
*
212+
* @throws Error
213+
* @param {SORTING_ORDER | Function} levelComparison - value to validate
214+
* @returns
215+
*/
216+
function assertLevelComparison (levelComparison) {
217+
if (typeof levelComparison === 'function') {
218+
return
219+
}
220+
221+
if (typeof levelComparison === 'string' && Object.values(SORTING_ORDER).includes(levelComparison)) {
222+
return
223+
}
224+
225+
throw new Error('Levels comparison should be one of "ASC", "DESC" or "function" type')
226+
}
227+
183228
module.exports = {
184229
initialLsCache,
185230
genLsCache,
@@ -188,7 +233,8 @@ module.exports = {
188233
setLevel,
189234
isLevelEnabled,
190235
mappings,
191-
levels,
192236
assertNoLevelCollisions,
193-
assertDefaultLevelFound
237+
assertDefaultLevelFound,
238+
genLevelComparison,
239+
assertLevelComparison
194240
}

lib/multistream.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
'use strict'
22

33
const metadata = Symbol.for('pino.metadata')
4-
const { levels } = require('./levels')
4+
const { DEFAULT_LEVELS } = require('./constants')
55

6-
const DEFAULT_INFO_LEVEL = levels.info
6+
const DEFAULT_INFO_LEVEL = DEFAULT_LEVELS.info
77

88
function multistream (streamsArray, opts) {
99
let counter = 0
1010
streamsArray = streamsArray || []
1111
opts = opts || { dedupe: false }
1212

13-
const streamLevels = Object.create(levels)
13+
const streamLevels = Object.create(DEFAULT_LEVELS)
1414
streamLevels.silent = Infinity
1515
if (opts.levels && typeof opts.levels === 'object') {
1616
Object.keys(opts.levels).forEach(i => {

lib/symbols.js

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const setLevelSym = Symbol('pino.setLevel')
44
const getLevelSym = Symbol('pino.getLevel')
55
const levelValSym = Symbol('pino.levelVal')
6+
const levelCompSym = Symbol('pino.levelComp')
67
const useLevelLabelsSym = Symbol('pino.useLevelLabels')
78
const useOnlyCustomLevelsSym = Symbol('pino.useOnlyCustomLevels')
89
const mixinSym = Symbol('pino.mixin')
@@ -42,6 +43,7 @@ module.exports = {
4243
setLevelSym,
4344
getLevelSym,
4445
levelValSym,
46+
levelCompSym,
4547
useLevelLabelsSym,
4648
mixinSym,
4749
lsCacheSym,

pino.d.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,12 @@ declare namespace pino {
354354
* The keys of the object correspond the namespace of the log level, and the values should be the numerical value of the level.
355355
*/
356356
customLevels?: { [level in CustomLevels]: number };
357+
/**
358+
* Use this option to define custom comparison of log levels.
359+
* Usefull to compare custom log levels or non-standard level values.
360+
* Default: "ASC"
361+
*/
362+
levelComparison?: "ASC" | "DESC" | ((current: number, expected: number) => boolean);
357363
/**
358364
* Use this option to only use defined `customLevels` and omit Pino's levels.
359365
* Logger's default `level` must be changed to a value in `customLevels` in order to use `useOnlyCustomLevels`
@@ -853,4 +859,5 @@ export { pino as default, pino };
853859
// Export just the type side of the namespace as "P", allows
854860
// `import {P} from "pino"; const log: P.Logger;`.
855861
// (Legacy support for early 7.x releases, remove in 8.x.)
856-
export type { pino as P };
862+
export type { pino as P };
863+

pino.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const time = require('./lib/time')
88
const proto = require('./lib/proto')
99
const symbols = require('./lib/symbols')
1010
const { configure } = require('safe-stable-stringify')
11-
const { assertDefaultLevelFound, mappings, genLsCache, levels } = require('./lib/levels')
11+
const { assertDefaultLevelFound, mappings, genLsCache, genLevelComparison, assertLevelComparison } = require('./lib/levels')
12+
const { DEFAULT_LEVELS, SORTING_ORDER } = require('./lib/constants')
1213
const {
1314
createArgsNormalizer,
1415
asChindings,
@@ -36,6 +37,7 @@ const {
3637
errorKeySym,
3738
nestedKeySym,
3839
mixinSym,
40+
levelCompSym,
3941
useOnlyCustomLevelsSym,
4042
formattersSym,
4143
hooksSym,
@@ -49,7 +51,8 @@ const hostname = os.hostname()
4951
const defaultErrorSerializer = stdSerializers.err
5052
const defaultOptions = {
5153
level: 'info',
52-
levels,
54+
levelComparison: SORTING_ORDER.ASC,
55+
levels: DEFAULT_LEVELS,
5356
messageKey: 'msg',
5457
errorKey: 'err',
5558
nestedKey: null,
@@ -97,6 +100,7 @@ function pino (...args) {
97100
name,
98101
level,
99102
customLevels,
103+
levelComparison,
100104
mixin,
101105
mixinMergeStrategy,
102106
useOnlyCustomLevels,
@@ -157,8 +161,12 @@ function pino (...args) {
157161
assertDefaultLevelFound(level, customLevels, useOnlyCustomLevels)
158162
const levels = mappings(customLevels, useOnlyCustomLevels)
159163

164+
assertLevelComparison(levelComparison)
165+
const levelCompFunc = genLevelComparison(levelComparison)
166+
160167
Object.assign(instance, {
161168
levels,
169+
[levelCompSym]: levelCompFunc,
162170
[useOnlyCustomLevelsSym]: useOnlyCustomLevels,
163171
[streamSym]: stream,
164172
[timeSym]: time,

0 commit comments

Comments
 (0)