Skip to content

Commit

Permalink
Add ability to override custom levels comparison (#1883)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
obrus-corcentric authored Feb 1, 2024
1 parent fdd0fd9 commit 384df31
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 58 deletions.
26 changes: 26 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,32 @@ Additional levels can be added to the instance via the `customLevels` option.
* See [`customLevels` option](#opt-customlevels)

<a id=opt-customlevels></a>

#### `levelComparison` ("ASC", "DESC", Function)

Default: `ASC`

Use this option to customize levels order.
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.

```js
const logger = pino({
levelComparison: 'DESC',
customLevels: {
foo: 20, // `foo` is more valuable than `bar`
bar: 10
},
})

// OR

const logger = pino({
levelComparison: function(current, expected) {
return current >= expected;
}
})
```

#### `customLevels` (Object)

Default: `undefined`
Expand Down
28 changes: 28 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Represents default log level values
*
* @enum {number}
*/
const DEFAULT_LEVELS = {
trace: 10,
debug: 20,
info: 30,
warn: 40,
error: 50,
fatal: 60
}

/**
* Represents sort order direction: `ascending` or `descending`
*
* @enum {string}
*/
const SORTING_ORDER = {
ASC: 'ASC',
DESC: 'DESC'
}

module.exports = {
DEFAULT_LEVELS,
SORTING_ORDER
}
90 changes: 68 additions & 22 deletions lib/levels.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,15 @@ const {
useOnlyCustomLevelsSym,
streamSym,
formattersSym,
hooksSym
hooksSym,
levelCompSym
} = require('./symbols')
const { noop, genLog } = require('./tools')
const { DEFAULT_LEVELS, SORTING_ORDER } = require('./constants')

const levels = {
trace: 10,
debug: 20,
info: 30,
warn: 40,
error: 50,
fatal: 60
}
const levelMethods = {
fatal: (hook) => {
const logFatal = genLog(levels.fatal, hook)
const logFatal = genLog(DEFAULT_LEVELS.fatal, hook)
return function (...args) {
const stream = this[streamSym]
logFatal.call(this, ...args)
Expand All @@ -33,15 +27,15 @@ const levelMethods = {
}
}
},
error: (hook) => genLog(levels.error, hook),
warn: (hook) => genLog(levels.warn, hook),
info: (hook) => genLog(levels.info, hook),
debug: (hook) => genLog(levels.debug, hook),
trace: (hook) => genLog(levels.trace, hook)
error: (hook) => genLog(DEFAULT_LEVELS.error, hook),
warn: (hook) => genLog(DEFAULT_LEVELS.warn, hook),
info: (hook) => genLog(DEFAULT_LEVELS.info, hook),
debug: (hook) => genLog(DEFAULT_LEVELS.debug, hook),
trace: (hook) => genLog(DEFAULT_LEVELS.trace, hook)
}

const nums = Object.keys(levels).reduce((o, k) => {
o[levels[k]] = k
const nums = Object.keys(DEFAULT_LEVELS).reduce((o, k) => {
o[DEFAULT_LEVELS[k]] = k
return o
}, {})

Expand Down Expand Up @@ -119,7 +113,39 @@ function getLevel (level) {
function isLevelEnabled (logLevel) {
const { values } = this.levels
const logLevelVal = values[logLevel]
return logLevelVal !== undefined && (logLevelVal >= this[levelValSym])
return logLevelVal !== undefined && this[levelCompSym](logLevelVal, this[levelValSym])
}

/**
* Determine if the given `current` level is enabled by comparing it
* against the current threshold (`expected`).
*
* @param {SORTING_ORDER} direction comparison direction "ASC" or "DESC"
* @param {number} current current log level number representatiton
* @param {number} expected threshold value to compare with
* @returns {boolean}
*/
function compareLevel (direction, current, expected) {
if (direction === SORTING_ORDER.DESC) {
return current <= expected
}

return current >= expected
}

/**
* Create a level comparison function based on `levelComparison`
* it could a default function which compares levels either in "ascending" or "descending" order or custom comparison function
*
* @param {SORTING_ORDER | Function} levelComparison sort levels order direction or custom comparison function
* @returns Function
*/
function genLevelComparison (levelComparison) {
if (typeof levelComparison === 'string') {
return compareLevel.bind(null, levelComparison)
}

return levelComparison
}

function mappings (customLevels = null, useOnlyCustomLevels = false) {
Expand All @@ -139,7 +165,7 @@ function mappings (customLevels = null, useOnlyCustomLevels = false) {
)
const values = Object.assign(
Object.create(Object.prototype, { silent: { value: Infinity } }),
useOnlyCustomLevels ? null : levels,
useOnlyCustomLevels ? null : DEFAULT_LEVELS,
customLevels
)
return { labels, values }
Expand All @@ -160,7 +186,7 @@ function assertDefaultLevelFound (defaultLevel, customLevels, useOnlyCustomLevel

const labels = Object.assign(
Object.create(Object.prototype, { silent: { value: Infinity } }),
useOnlyCustomLevels ? null : levels,
useOnlyCustomLevels ? null : DEFAULT_LEVELS,
customLevels
)
if (!(defaultLevel in labels)) {
Expand All @@ -180,6 +206,25 @@ function assertNoLevelCollisions (levels, customLevels) {
}
}

/**
* Validates whether `levelComparison` is correct
*
* @throws Error
* @param {SORTING_ORDER | Function} levelComparison - value to validate
* @returns
*/
function assertLevelComparison (levelComparison) {
if (typeof levelComparison === 'function') {
return
}

if (typeof levelComparison === 'string' && Object.values(SORTING_ORDER).includes(levelComparison)) {
return
}

throw new Error('Levels comparison should be one of "ASC", "DESC" or "function" type')
}

module.exports = {
initialLsCache,
genLsCache,
Expand All @@ -188,7 +233,8 @@ module.exports = {
setLevel,
isLevelEnabled,
mappings,
levels,
assertNoLevelCollisions,
assertDefaultLevelFound
assertDefaultLevelFound,
genLevelComparison,
assertLevelComparison
}
6 changes: 3 additions & 3 deletions lib/multistream.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
'use strict'

const metadata = Symbol.for('pino.metadata')
const { levels } = require('./levels')
const { DEFAULT_LEVELS } = require('./constants')

const DEFAULT_INFO_LEVEL = levels.info
const DEFAULT_INFO_LEVEL = DEFAULT_LEVELS.info

function multistream (streamsArray, opts) {
let counter = 0
streamsArray = streamsArray || []
opts = opts || { dedupe: false }

const streamLevels = Object.create(levels)
const streamLevels = Object.create(DEFAULT_LEVELS)
streamLevels.silent = Infinity
if (opts.levels && typeof opts.levels === 'object') {
Object.keys(opts.levels).forEach(i => {
Expand Down
2 changes: 2 additions & 0 deletions lib/symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const setLevelSym = Symbol('pino.setLevel')
const getLevelSym = Symbol('pino.getLevel')
const levelValSym = Symbol('pino.levelVal')
const levelCompSym = Symbol('pino.levelComp')
const useLevelLabelsSym = Symbol('pino.useLevelLabels')
const useOnlyCustomLevelsSym = Symbol('pino.useOnlyCustomLevels')
const mixinSym = Symbol('pino.mixin')
Expand Down Expand Up @@ -42,6 +43,7 @@ module.exports = {
setLevelSym,
getLevelSym,
levelValSym,
levelCompSym,
useLevelLabelsSym,
mixinSym,
lsCacheSym,
Expand Down
9 changes: 8 additions & 1 deletion pino.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,12 @@ declare namespace pino {
* The keys of the object correspond the namespace of the log level, and the values should be the numerical value of the level.
*/
customLevels?: { [level in CustomLevels]: number };
/**
* Use this option to define custom comparison of log levels.
* Usefull to compare custom log levels or non-standard level values.
* Default: "ASC"
*/
levelComparison?: "ASC" | "DESC" | ((current: number, expected: number) => boolean);
/**
* 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`
Expand Down Expand Up @@ -853,4 +859,5 @@ export { pino as default, pino };
// Export just the type side of the namespace as "P", allows
// `import {P} from "pino"; const log: P.Logger;`.
// (Legacy support for early 7.x releases, remove in 8.x.)
export type { pino as P };
export type { pino as P };

12 changes: 10 additions & 2 deletions pino.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ const time = require('./lib/time')
const proto = require('./lib/proto')
const symbols = require('./lib/symbols')
const { configure } = require('safe-stable-stringify')
const { assertDefaultLevelFound, mappings, genLsCache, levels } = require('./lib/levels')
const { assertDefaultLevelFound, mappings, genLsCache, genLevelComparison, assertLevelComparison } = require('./lib/levels')
const { DEFAULT_LEVELS, SORTING_ORDER } = require('./lib/constants')
const {
createArgsNormalizer,
asChindings,
Expand Down Expand Up @@ -36,6 +37,7 @@ const {
errorKeySym,
nestedKeySym,
mixinSym,
levelCompSym,
useOnlyCustomLevelsSym,
formattersSym,
hooksSym,
Expand All @@ -49,7 +51,8 @@ const hostname = os.hostname()
const defaultErrorSerializer = stdSerializers.err
const defaultOptions = {
level: 'info',
levels,
levelComparison: SORTING_ORDER.ASC,
levels: DEFAULT_LEVELS,
messageKey: 'msg',
errorKey: 'err',
nestedKey: null,
Expand Down Expand Up @@ -97,6 +100,7 @@ function pino (...args) {
name,
level,
customLevels,
levelComparison,
mixin,
mixinMergeStrategy,
useOnlyCustomLevels,
Expand Down Expand Up @@ -157,8 +161,12 @@ function pino (...args) {
assertDefaultLevelFound(level, customLevels, useOnlyCustomLevels)
const levels = mappings(customLevels, useOnlyCustomLevels)

assertLevelComparison(levelComparison)
const levelCompFunc = genLevelComparison(levelComparison)

Object.assign(instance, {
levels,
[levelCompSym]: levelCompFunc,
[useOnlyCustomLevelsSym]: useOnlyCustomLevels,
[streamSym]: stream,
[timeSym]: time,
Expand Down
Loading

0 comments on commit 384df31

Please sign in to comment.