Skip to content

Commit

Permalink
feat: Add label and colorized output for level customPrettifier func
Browse files Browse the repository at this point in the history
Same implementation as pinojs#493 but using object for extras argument for compatibility with future signature expansion
  • Loading branch information
FoxxMD committed Feb 26, 2024
1 parent b67b7c4 commit 1a30ae3
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 31 deletions.
16 changes: 4 additions & 12 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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:
Expand Down
10 changes: 8 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>;

Expand Down Expand Up @@ -179,7 +180,10 @@ interface PrettyOptions_ {
* }
* ```
*/
customPrettifiers?: Record<string, PinoPretty.Prettifier>;
customPrettifiers?: Record<string, PinoPretty.Prettifier> &
{
level?: PinoPretty.Prettifier<PinoPretty.LevelPrettifierExtras>
};
/**
* Change the level names and values to an user custom preset.
*
Expand All @@ -204,7 +208,9 @@ interface PrettyOptions_ {
declare function build(options: PrettyOptions_): PinoPretty.PrettyStream;

declare namespace PinoPretty {
type Prettifier = (inputData: string | object) => string;
type Prettifier<T = object> = (inputData: string | object, extras: PrettifierExtras<T>) => string;
type PrettifierExtras<T = object> = object & T;
type LevelPrettifierExtras = {label: string, labelColorized: string}
type MessageFormatFunc = (log: LogDescriptor, messageKey: string, levelLabel: string) => string;
type PrettyOptions = PrettyOptions_;
type PrettyStream = Transform & OnUnknown;
Expand Down
15 changes: 2 additions & 13 deletions lib/colors.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
'use strict'

const { LEVELS, LEVEL_NAMES } = require('./constants')

const nocolor = input => input
const plain = {
default: nocolor,
Expand All @@ -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

Expand Down Expand Up @@ -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)
}
Expand Down
29 changes: 29 additions & 0 deletions lib/utils/get-level-label-data.js
Original file line number Diff line number Diff line change
@@ -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]
}
}
3 changes: 2 additions & 1 deletion lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions lib/utils/parse-factory-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -135,6 +138,7 @@ function parseFactoryOptions (options) {
customProperties,
errorLikeObjectKeys,
errorProps,
getLevelLabelData,
hideObject,
ignoreKeys,
includeKeys,
Expand Down
10 changes: 8 additions & 2 deletions lib/utils/prettify-level.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 3 additions & 1 deletion lib/utils/prettify-level.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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 => {
Expand Down
42 changes: 42 additions & 0 deletions test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
3 changes: 3 additions & 0 deletions test/types/pino-pretty.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit 1a30ae3

Please sign in to comment.