Skip to content
Open
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
2 changes: 2 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ node app.js | pino-pretty
- `--minimumLevel` (`-L`): Hide messages below the specified log level. Accepts a number, `trace`, `debug`, `info`, `warn`, `error`, or `fatal`. If any more filtering is required, consider using [`jq`](https://stedolan.github.io/jq/).
- `--customLevels` (`-x`): Override default levels with custom levels, e.g. `-x err:99,info:1`
- `--customColors` (`-X`): Override default colors with custom colors, e.g. `-X err:red,info:blue`
- `--colorizeMessage` (`-z`): Applies the level label color to the entire message output
- `--useOnlyCustomProps` (`-U`): Only use custom levels and colors (if provided) (default: true); else fallback to default levels and colors, e.g. `-U false`
- `--messageFormat` (`-o`): Format output of message, e.g. `{levelLabel} - {pid} - url:{req.url}` will output message: `INFO - 1123 - url:localhost:3000/test`
Default: `false`
Expand Down Expand Up @@ -267,6 +268,7 @@ The options accepted have keys corresponding to the options described in [CLI Ar
singleLine: false, // --singleLine
customColors: 'err:red,info:blue', // --customColors
customLevels: 'err:99,info:1', // --customLevels (not required with pino >=8.21.0)
colorizeMessage: false, // --colorizeMessage
levelLabel: 'levelLabel', // --levelLabel
minimumLevel: 'info', // --minimumLevel
useOnlyCustomProps: true, // --useOnlyCustomProps
Expand Down
3 changes: 2 additions & 1 deletion bin.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ if (cmd.h || cmd.help) {
ignore: 'i',
include: 'I',
hideObject: 'H',
singleLine: 'S'
singleLine: 'S',
colorizeMessage: 'z'
},
default: {
messageKey: DEFAULT_VALUE,
Expand Down
6 changes: 6 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ declare namespace PinoPretty {
* @default true
*/
useOnlyCustomProps?: boolean;
/**
* Print the message same color of the level.
*
* @default false
*/
colorizeMessage?: boolean;
}

function build(options: PrettyOptions): PrettyStream;
Expand Down
5 changes: 4 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ const pretty = require('./lib/pretty')
* it must be a format string.
* @property {boolean} [useOnlyCustomProps=true] When true, only custom levels
* and colors will be used if they have been provided.
* @property {boolean} [colorizeMessage=false] When true, the message will
* get the same color of the level
*/

/**
Expand Down Expand Up @@ -99,7 +101,8 @@ const defaultOptions = {
singleLine: false,
timestampKey: TIMESTAMP_KEY,
translateTime: true,
useOnlyCustomProps: true
useOnlyCustomProps: true,
colorizeMessage: false
}

/**
Expand Down
3 changes: 3 additions & 0 deletions lib/colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ function plainColorizer (useOnlyCustomProps) {
customColoredColorizer.message = plain.message
customColoredColorizer.greyMessage = plain.greyMessage
customColoredColorizer.property = plain.property
customColoredColorizer.levelColors = plain
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is .levelColors being set to the hash of colors on all three colorizers? The other properties, e.g. .message, are set to the color that field will be colorized to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To preserve all color levels and make use of them, .message will remain cyan during tests.

The colorizer will initially select one of the two constants:

colored: message as cyan
or
plain: nocolor;

e.g:

const colored = {
  default: white,
  60: bgRed,
  50: red,
  40: yellow,
  30: green,
  20: blue,
  10: gray,
  message: cyan,
  greyMessage: gray,
  property: magenta
}

customColoredColorizer.colors = createColors({ useColor: false })
return customColoredColorizer
}
Expand All @@ -71,6 +72,7 @@ function coloredColorizer (useOnlyCustomProps) {
customColoredColorizer.message = colored.message
customColoredColorizer.property = colored.property
customColoredColorizer.greyMessage = colored.greyMessage
customColoredColorizer.levelColors = colored
customColoredColorizer.colors = availableColors
return customColoredColorizer
}
Expand All @@ -86,6 +88,7 @@ function customColoredColorizerFactory (customColors, useOnlyCustomProps) {
customColoredColorizer.colors = availableColors
customColoredColorizer.message = customColoredColorizer.message || customColored.message
customColoredColorizer.greyMessage = customColoredColorizer.greyMessage || customColored.greyMessage
customColoredColorizer.levelColors = customColored || colored

return customColoredColorizer
}
Expand Down
6 changes: 4 additions & 2 deletions lib/utils/parse-factory-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ function parseFactoryOptions (options) {
minimumLevel,
singleLine,
timestampKey,
translateTime
translateTime,
colorizeMessage
} = options
const errorProps = options.errorProps.split(',')
const useOnlyCustomProps = typeof options.useOnlyCustomProps === 'boolean'
Expand Down Expand Up @@ -168,6 +169,7 @@ function parseFactoryOptions (options) {
singleLine,
timestampKey,
translateTime,
useOnlyCustomProps
useOnlyCustomProps,
colorizeMessage
}
}
19 changes: 18 additions & 1 deletion lib/utils/prettify-message.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ function prettifyMessage ({ log, context }) {
levelLabel,
messageFormat,
messageKey,
useOnlyCustomProps
useOnlyCustomProps,
colorizeMessage
} = context

if (messageFormat && typeof messageFormat === 'string') {
const parsedMessageFormat = interpretConditionals(messageFormat, log)

Expand All @@ -51,13 +53,28 @@ function prettifyMessage ({ log, context }) {
// Parse nested key access, e.g. `{keyA.subKeyB}`.
return getPropertyValue(log, p1) || ''
})

if (colorizeMessage) {
return colorizer.levelColors[getPropertyValue(log, levelKey)](message)
}
Comment on lines +57 to +59
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have colorizer.message() to apply color to the message. Shouldn't that method be updated instead of adding this block multiple times?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Complementing the comment in lib/color.js, I thought it would be useful to expose the colors object to facilitate other implementations.

Also, using .message alone didn’t achieve the desired result in my tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand. The sole purpose of colorizer.message is to apply the configured color to the message string. If that is not happening, then something is either wrong with that method or this PR is incomplete.

Copy link
Contributor Author

@ademarsj ademarsj Oct 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually does, but only in Cyan (see #430 print).

I understand your point that this might seem contrary to the library’s design. However, the colorizer.message method ultimately delegates to a function from colorette.

In colors.js:

const { createColors } = require('colorette')
const availableColors = createColors({ useColor: true })
const { ..., cyan, ... } = availableColors

const colored = {
  default: white,
  60: bgRed,
  ....
  message: cyan,
  ...
}

function coloredColorizer (useOnlyCustomProps) {
  const newColoredColorizer = colorizeLevel(useOnlyCustomProps)
  const customColoredColorizer = function (level, opts) {
    return newColoredColorizer(level, colored, opts)
  }
  customColoredColorizer.message = colored.message
  ...
}

Changing colorette itself wouldn’t make sense, since the library is correctly fulfilling its purpose.

The behavior we want to adjust lies within pino-pretty. Modifying the existing factory methods to achieve this would require a broader refactor—essentially introducing middleware logic before calling colorette, which would affect the entire colorization flow.

This PR, instead, introduces an way to colorize the message using the same color as the log level, without overriding the default behavior. It preserves the current integration between pino-pretty and colorette, while giving users more flexibility in how messages are colorized—avoiding design breaks rather than causing them.


return colorizer.message(message)
}
if (messageFormat && typeof messageFormat === 'function') {
const msg = messageFormat(log, messageKey, levelLabel, { colors: colorizer.colors })

if (colorizeMessage) {
return colorizer.levelColors[getPropertyValue(log, levelKey)](msg)
}

return colorizer.message(msg)
}
if (messageKey in log === false) return undefined
if (typeof log[messageKey] !== 'string' && typeof log[messageKey] !== 'number' && typeof log[messageKey] !== 'boolean') return undefined

if (colorizeMessage) {
return colorizer.levelColors[getPropertyValue(log, levelKey)](log[messageKey])
}

return colorizer.message(log[messageKey])
}
28 changes: 28 additions & 0 deletions lib/utils/prettify-message.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,34 @@ test('returns message formatted by `messageFormat` option - levelLabel & useOnly
t.assert.strictEqual(str, '[30] INFO appModule - foo')
})

test('returns a default(cyan) colored message when `colorizeMessage` is false', t => {
const colorizer = getColorizer(true)

const str = prettifyMessage({
log: { msg: 'foo', level: 40 },
context: {
...context,
colorizer,
colorizeMessage: false
}
})
t.assert.strictEqual(str, '\x1B[36mfoo\x1B[39m')
})

test('returns a colored(yellow warning) message when `colorizeMessage` is true', t => {
const colorizer = getColorizer(true)

const str = prettifyMessage({
log: { msg: 'foo', level: 40 },
context: {
...context,
colorizer,
colorizeMessage: true
}
})
t.assert.strictEqual(str, '\x1B[33mfoo\x1B[39m')
})

test('returns message formatted by `messageFormat` option - levelLabel & useOnlyCustomProps true', t => {
const str = prettifyMessage({
log: { msg: 'foo', context: 'appModule', level: 30 },
Expand Down
14 changes: 14 additions & 0 deletions test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,20 @@ describe('cli', () => {
})
}

for (const optionName of ['--colorizeMessage', '-z']) {
test(`colorize messages via ${optionName}`, async (t) => {
t.plan(1)
const child = spawn(process.argv[0], [bin, optionName], { env })
child.on('error', t.assert.fail)
const endPromise = once(child.stdout, 'data', (data) => {
t.assert.strictEqual(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
})
child.stdin.write(logLine)
await endPromise
t.after(() => child.kill())
})
}

for (const optionName of ['--useOnlyCustomProps', '-U']) {
test(`customize levels via ${optionName} false and customColors`, async (t) => {
t.plan(1)
Expand Down
1 change: 1 addition & 0 deletions test/types/pino-pretty.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const options: PinoPretty.PrettyOptions = {
append: true,
mkdir: true,
useOnlyCustomProps: false,
colorizeMessage: false,
};

expectType<PrettyStream>(pretty()); // #326
Expand Down
Loading