diff --git a/Readme.md b/Readme.md index 41a504a4..e988a4a5 100644 --- a/Readme.md +++ b/Readme.md @@ -311,6 +311,9 @@ Additionally, `customPrettifiers` can be used to format the `time`, `hostname`, // on if the levelKey option is used or not. // By default this will be the same numerics as the Pino default: level: logLevel => `LEVEL: ${logLevel}`, + // Additionally, optional second and third arguments arguments can be provided + // to get the level label as a string and the colorized version (if `colorize: true`), respectively: + level: (logLevel, levelLabel, coloredLevelLabel) => `LEVEL: ${logLevel} LABEL: ${levelLabel} COLORIZED LABEL: ${coloredLevelLabel}`, // other prettifiers can be used for the other keys if needed, for example hostname: hostname => colorGreen(hostname), @@ -321,18 +324,7 @@ Additionally, `customPrettifiers` can be used to format the `time`, `hostname`, } ``` -Note that prettifiers do not include any coloring, if the stock coloring on -`level` is desired, it can be accomplished using the following: - -```js -const { colorizerFactory } = require('pino-pretty') -const levelColorize = colorizerFactory(true) -const levelPrettifier = logLevel => `LEVEL: ${levelColorize(logLevel)}` -//... -{ - customPrettifiers: { level: levelPrettifier } -} -``` +Note that prettifiers, other than `level`, do not include any coloring. `messageFormat` option allows you to customize the message output. A template `string` like this can define the format: diff --git a/index.d.ts b/index.d.ts index 614d3750..7c89ce5e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -10,6 +10,7 @@ import { Transform } from 'stream'; import { OnUnknown } from 'pino-abstract-transport'; // @ts-ignore fall back to any if pino is not available, i.e. when running pino tests import { DestinationStream, Level } from 'pino'; +import LevelPrettifierExtras = PinoPretty.LevelPrettifierExtras; type LogDescriptor = Record; @@ -179,7 +180,10 @@ interface PrettyOptions_ { * } * ``` */ - customPrettifiers?: Record; + customPrettifiers?: Record & + { + level?: PinoPretty.Prettifier + }; /** * Change the level names and values to an user custom preset. * @@ -204,7 +208,9 @@ interface PrettyOptions_ { declare function build(options: PrettyOptions_): PinoPretty.PrettyStream; declare namespace PinoPretty { - type Prettifier = (inputData: string | object) => string; + type Prettifier = (inputData: string | object, extras: PrettifierExtras) => string; + type PrettifierExtras = object & T; + type LevelPrettifierExtras = {label: string, labelColorized: string} type MessageFormatFunc = (log: LogDescriptor, messageKey: string, levelLabel: string) => string; type PrettyOptions = PrettyOptions_; type PrettyStream = Transform & OnUnknown; diff --git a/lib/colors.js b/lib/colors.js index 5bb38042..99ac94c1 100644 --- a/lib/colors.js +++ b/lib/colors.js @@ -1,7 +1,5 @@ 'use strict' -const { LEVELS, LEVEL_NAMES } = require('./constants') - const nocolor = input => input const plain = { default: nocolor, @@ -16,6 +14,7 @@ const plain = { } const { createColors } = require('colorette') +const getLevelLabelData = require('./utils/get-level-label-data') const availableColors = createColors({ useColor: true }) const { white, bgRed, red, yellow, green, blue, gray, cyan } = availableColors @@ -44,17 +43,7 @@ function resolveCustomColoredColorizer (customColors) { function colorizeLevel (useOnlyCustomProps) { return function (level, colorizer, { customLevels, customLevelNames } = {}) { - const levels = useOnlyCustomProps ? customLevels || LEVELS : Object.assign({}, LEVELS, customLevels) - const levelNames = useOnlyCustomProps ? customLevelNames || LEVEL_NAMES : Object.assign({}, LEVEL_NAMES, customLevelNames) - - let levelNum = 'default' - if (Number.isInteger(+level)) { - levelNum = Object.prototype.hasOwnProperty.call(levels, level) ? level : levelNum - } else { - levelNum = Object.prototype.hasOwnProperty.call(levelNames, level.toLowerCase()) ? levelNames[level.toLowerCase()] : levelNum - } - - const levelStr = levels[levelNum] + const [levelStr, levelNum] = getLevelLabelData(useOnlyCustomProps, customLevels, customLevelNames)(level) return Object.prototype.hasOwnProperty.call(colorizer, levelNum) ? colorizer[levelNum](levelStr) : colorizer.default(levelStr) } diff --git a/lib/utils/get-level-label-data.js b/lib/utils/get-level-label-data.js new file mode 100644 index 00000000..5b27cabb --- /dev/null +++ b/lib/utils/get-level-label-data.js @@ -0,0 +1,29 @@ +'use strict' + +module.exports = getLevelLabelData +const { LEVELS, LEVEL_NAMES } = require('../constants') + +/** + * Given initial settings for custom levels/names and use of only custom props + * get the level label that corresponds with a given level number + * + * @param {boolean} useOnlyCustomProps + * @param {object} customLevels + * @param {object} customLevelNames + * + * @returns {function} A function that takes a number level and returns the level's label string + */ +function getLevelLabelData (useOnlyCustomProps, customLevels, customLevelNames) { + const levels = useOnlyCustomProps ? customLevels || LEVELS : Object.assign({}, LEVELS, customLevels) + const levelNames = useOnlyCustomProps ? customLevelNames || LEVEL_NAMES : Object.assign({}, LEVEL_NAMES, customLevelNames) + return function (level) { + let levelNum = 'default' + if (Number.isInteger(+level)) { + levelNum = Object.prototype.hasOwnProperty.call(levels, level) ? level : levelNum + } else { + levelNum = Object.prototype.hasOwnProperty.call(levelNames, level.toLowerCase()) ? levelNames[level.toLowerCase()] : levelNum + } + + return [levels[levelNum], levelNum] + } +} diff --git a/lib/utils/index.js b/lib/utils/index.js index ec69d968..40a13e11 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -22,7 +22,8 @@ module.exports = { prettifyMetadata: require('./prettify-metadata.js'), prettifyObject: require('./prettify-object.js'), prettifyTime: require('./prettify-time.js'), - splitPropertyKey: require('./split-property-key.js') + splitPropertyKey: require('./split-property-key.js'), + getLevelLabelData: require('./get-level-label-data') } // The remainder of this file consists of jsdoc blocks that are difficult to diff --git a/lib/utils/parse-factory-options.js b/lib/utils/parse-factory-options.js index 33ffb374..0b8ec748 100644 --- a/lib/utils/parse-factory-options.js +++ b/lib/utils/parse-factory-options.js @@ -8,6 +8,7 @@ const { const colors = require('../colors') const handleCustomLevelsOpts = require('./handle-custom-levels-opts') const handleCustomLevelsNamesOpts = require('./handle-custom-levels-names-opts') +const handleLevelLabelData = require('./get-level-label-data') /** * A `PrettyContext` is an object to be used by the various functions that @@ -32,6 +33,7 @@ const handleCustomLevelsNamesOpts = require('./handle-custom-levels-names-opts') * should be considered as holding error objects. * @property {string[]} errorProps A list of error object keys that should be * included in the output. + * @property {function} getLevelLabelData Pass a numeric level to return [levelLabelString,levelNum] * @property {boolean} hideObject Indicates the prettifier should omit objects * in the output. * @property {string[]} ignoreKeys Set of log data keys to omit. @@ -84,6 +86,7 @@ function parseFactoryOptions (options) { : (options.useOnlyCustomProps === 'true') const customLevels = handleCustomLevelsOpts(options.customLevels) const customLevelNames = handleCustomLevelsNamesOpts(options.customLevels) + const getLevelLabelData = handleLevelLabelData(useOnlyCustomProps, customLevels, customLevelNames) let customColors if (options.customColors) { @@ -135,6 +138,7 @@ function parseFactoryOptions (options) { customProperties, errorLikeObjectKeys, errorProps, + getLevelLabelData, hideObject, ignoreKeys, includeKeys, diff --git a/lib/utils/prettify-level.js b/lib/utils/prettify-level.js index 213ba06c..2fdd6d25 100644 --- a/lib/utils/prettify-level.js +++ b/lib/utils/prettify-level.js @@ -26,10 +26,16 @@ function prettifyLevel ({ log, context }) { colorizer, customLevels, customLevelNames, - levelKey + levelKey, + getLevelLabelData } = context const prettifier = context.customPrettifiers?.level const output = getPropertyValue(log, levelKey) if (output === undefined) return undefined - return prettifier ? prettifier(output) : colorizer(output, { customLevels, customLevelNames }) + const labelColorized = colorizer(output, { customLevels, customLevelNames }) + if (prettifier) { + const [label] = getLevelLabelData(output) + return prettifier(output, { label, labelColorized }) + } + return labelColorized } diff --git a/lib/utils/prettify-level.test.js b/lib/utils/prettify-level.test.js index e735b0a9..e6e5a3c1 100644 --- a/lib/utils/prettify-level.test.js +++ b/lib/utils/prettify-level.test.js @@ -3,6 +3,7 @@ const tap = require('tap') const prettifyLevel = require('./prettify-level') const getColorizer = require('../colors') +const getLevelLabelData = require('./get-level-label-data') const { LEVEL_KEY } = require('../constants') @@ -12,7 +13,8 @@ const context = { customLevelNames: undefined, customLevels: undefined, levelKey: LEVEL_KEY, - customPrettifiers: undefined + customPrettifiers: undefined, + getLevelLabelData: getLevelLabelData(false, {}, {}) } tap.test('returns `undefined` for unknown level', async t => { diff --git a/test/basic.test.js b/test/basic.test.js index 9986811c..92fbfcf3 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -256,6 +256,48 @@ test('basic prettifier tests', (t) => { log.info({ msg: 'foo', bar: 'warn' }) }) + t.test('can use a customPrettifier to get final level label (no color)', (t) => { + t.plan(1) + const customPrettifiers = { + level: (level, { label }) => { + return `LEVEL: ${label}` + } + } + const pretty = prettyFactory({ customPrettifiers, colorize: false, useOnlyCustomProps: false }) + const log = pino({}, new Writable({ + write (chunk, enc, cb) { + const formatted = pretty(chunk.toString()) + t.equal( + formatted, + `[${formattedEpoch}] LEVEL: INFO (${pid}): foo\n` + ) + cb() + } + })) + log.info({ msg: 'foo' }) + }) + + t.test('can use a customPrettifier to get final level label (colorized)', (t) => { + t.plan(1) + const customPrettifiers = { + level: (level, { label, labelColorized }) => { + return `LEVEL: ${labelColorized}` + } + } + const pretty = prettyFactory({ customPrettifiers, colorize: true, useOnlyCustomProps: false }) + const log = pino({}, new Writable({ + write (chunk, enc, cb) { + const formatted = pretty(chunk.toString()) + t.equal( + formatted, + `[${formattedEpoch}] LEVEL: INFO (${pid}): foo\n` + ) + cb() + } + })) + log.info({ msg: 'foo' }) + }) + t.test('can use a customPrettifier on name output', (t) => { t.plan(1) const customPrettifiers = { diff --git a/test/types/pino-pretty.test-d.ts b/test/types/pino-pretty.test-d.ts index 75f4d31e..ee33506e 100644 --- a/test/types/pino-pretty.test-d.ts +++ b/test/types/pino-pretty.test-d.ts @@ -32,6 +32,9 @@ const options: PinoPretty.PrettyOptions = { customPrettifiers: { key: (value) => { return value.toString().toUpperCase(); + }, + level: (level, label, colorized) => { + return level.toString(); } }, customLevels: 'verbose:5',