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

feat: Add context to level customPrettifier #493

Closed
wants to merge 2 commits into from
Closed
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
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
5 changes: 3 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ interface PrettyOptions_ {
* }
* ```
*/
customPrettifiers?: Record<string, PinoPretty.Prettifier>;
customPrettifiers?: Record<string, PinoPretty.Prettifier> & {level?: PinoPretty.LevelPrettifier};
/**
* Change the level names and values to an user custom preset.
*
Expand All @@ -204,7 +204,8 @@ interface PrettyOptions_ {
declare function build(options: PrettyOptions_): PinoPretty.PrettyStream;

declare namespace PinoPretty {
type Prettifier = (inputData: string | object) => string;
type Prettifier = (inputData: string | object, ...rest: any[]) => string;
type LevelPrettifier = ((level: string | object | number) => string) | ((level: string | object | number, label: string) => string) | ((level: string | object | number, label: string, colorized: string) => 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 colorized = colorizer(output, { customLevels, customLevelNames })
if (prettifier) {
const [levelLabel] = getLevelLabelData(output)
return prettifier(output, levelLabel, colorized)
}
return colorized
}
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, colorized) => {
return `LEVEL: ${colorized}`
}
}
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