pinojs / pino-pretty

🌲Basic prettifier for Pino log lines
MIT License
1.27k stars 150 forks source link

pino-pretty

NPM Package Version Build Status Coverage Status js-standard-style

This module provides a basic ndjson formatter to be used in development. If an incoming line looks like it could be a log line from an ndjson logger, in particular the Pino logging library, then it will apply extra formatting by considering things like the log level and timestamp.

A standard Pino log line like:

{"level":30,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo","v":1}

Will format to:

[17:35:28.992] INFO (42): hello world

If you landed on this page due to the deprecation of the prettyPrint option of pino, read the Programmatic Integration section.

Example

Using the example script from the Pino module, we can see what the prettified logs will look like:

demo

Install

$ npm install -g pino-pretty

Usage

It is recommended to use pino-pretty with pino by piping output to the CLI tool:

node app.js | pino-pretty

CLI Arguments

Programmatic Integration

We recommend against using pino-pretty in production and highly recommend installing pino-pretty as a development dependency.

Install pino-pretty alongside pino and set the transport target to 'pino-pretty':

const pino = require('pino')
const logger = pino({
  transport: {
    target: 'pino-pretty'
  },
})

logger.info('hi')

The transport option can also have an options object containing pino-pretty options:

const pino = require('pino')
const logger = pino({
  transport: {
    target: 'pino-pretty',
    options: {
      colorize: true
    }
  }
})

logger.info('hi')

Use it as a stream:

const pino = require('pino')
const pretty = require('pino-pretty')
const logger = pino(pretty())

logger.info('hi')

Options are also supported:

const pino = require('pino')
const pretty = require('pino-pretty')
const stream = pretty({
  colorize: true
})
const logger = pino(stream)

logger.info('hi')

See the Options section for all possible options.

Usage as a stream

If you are using pino-pretty as a stream and you need to provide options to pino, pass the options as the first argument and pino-pretty as second argument:

const pino = require('pino')
const pretty = require('pino-pretty')
const stream = pretty({
  colorize: true
})
const logger = pino({ level: 'info' }, stream)

// Nothing is printed
logger.debug('hi')

Usage with Jest

Logging with Jest is problematic, as the test framework requires no asynchronous operation to continue after the test has finished. The following is the only supported way to use this module with Jest:

import pino from 'pino'
import pretty from 'pino-pretty'

test('test pino-pretty', () => {
  const logger = pino(pretty({ sync: true }));
  logger.info('Info');
  logger.error('Error');
});

Handling non-serializable options

Using the new pino v7+ transports not all options are serializable, for example if you want to use messageFormat as a function you will need to wrap pino-pretty in a custom module.

Executing main.js below will log a colorized hello world message using a custom function messageFormat:

// main.js
const pino = require('pino')

const logger = pino({
  transport: {
    target: './pino-pretty-transport',
    options: {
      colorize: true
    }
  },
})

logger.info('world')
// pino-pretty-transport.js
module.exports = opts => require('pino-pretty')({
  ...opts,
  messageFormat: (log, messageKey) => `hello ${log[messageKey]}`
})

Checking color support in TTY

This boolean returns whether the currently used TTY supports colorizing the logs.

import pretty from 'pino-pretty'

if (pretty.isColorSupported) {
  ...
}

Options

The options accepted have keys corresponding to the options described in CLI Arguments:

{
  colorize: colorette.isColorSupported, // --colorize
  colorizeObjects: true, //--colorizeObjects
  crlf: false, // --crlf
  errorLikeObjectKeys: ['err', 'error'], // --errorLikeObjectKeys (not required to match custom errorKey with pino >=8.21.0)
  errorProps: '', // --errorProps
  levelFirst: false, // --levelFirst
  messageKey: 'msg', // --messageKey (not required with pino >=8.21.0)
  levelKey: 'level', // --levelKey
  messageFormat: false, // --messageFormat
  timestampKey: 'time', // --timestampKey
  translateTime: false, // --translateTime
  ignore: 'pid,hostname', // --ignore
  include: 'level,time', // --include
  hideObject: false, // --hideObject
  singleLine: false, // --singleLine
  customColors: 'err:red,info:blue', // --customColors
  customLevels: 'err:99,info:1', // --customLevels (not required with pino >=8.21.0)
  levelLabel: 'levelLabel', // --levelLabel
  minimumLevel: 'info', // --minimumLevel
  useOnlyCustomProps: true, // --useOnlyCustomProps
  // The file or file descriptor (1 is stdout) to write to
  destination: 1,

  // Alternatively, pass a `sonic-boom` instance (allowing more flexibility):
  // destination: new SonicBoom({ dest: 'a/file', mkdir: true })

  // You can also configure some SonicBoom options directly
  sync: false, // by default we write asynchronously
  append: true, // the file is opened with the 'a' flag
  mkdir: true, // create the target destination

  customPrettifiers: {}
}

The colorize default follows colorette.isColorSupported.

The defaults for sync, append, mkdir inherit from SonicBoom(opts).

customPrettifiers option provides the ability to add a custom prettify function for specific log properties. customPrettifiers is an object, where keys are log properties that will be prettified and value is the prettify function itself. For example, if a log line contains a query property, you can specify a prettifier for it:

{
  customPrettifiers: {
    query: prettifyQuery
  }
}
//...
const prettifyQuery = value => {
  // do some prettify magic
}

All prettifiers use this function signature:

['logObjKey']: (output, keyName, logObj, extras) => string

Additionally, customPrettifiers can be used to format the time, hostname, pid, name, caller and level outputs AS WELL AS any arbitrary key-value that exists on a given log object.

An example usage of customPrettifiers using all parameters from the function signature:

{
  customPrettifiers: {
    // The argument for this function will be the same
    // string that's at the start of the log-line by default:
    time: timestamp => `🕰 ${timestamp}`,

    // The argument for the level-prettifier may vary depending
    // 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}`,
    // level provides additional data in `extras`:
    // * label => derived level label string
    // * labelColorized => derived level label string with colorette colors applied based on customColors and whether colors are supported
    level: (logLevel, key, log, { label, labelColorized, colors }) => `LEVEL: ${logLevel} LABEL: ${levelLabel} COLORIZED LABEL: ${labelColorized}`,

    // other prettifiers can be used for the other keys if needed, for example
    hostname: hostname => `MY HOST: ${hostname}`,
    pid: pid => pid,
    name: (name, key, log, { colors }) => `${colors.blue(name)}`,
    caller: (caller, key, log, { colors }) => `${colors.greenBright(caller)}`,
    myCustomLogProp: (value, key, log, { colors }) => `My Prop -> ${colors.bold(value)} <--`
  }
}

messageFormat option allows you to customize the message output. A template string like this can define the format:

{
  messageFormat: '{levelLabel} - {pid} - url:{req.url}'
}

In addition to this, if / end statement blocks can also be specified. Else statements and nested conditions are not supported.

{
  messageFormat: '{levelLabel} - {if pid}{pid} - {end}url:{req.url}'
}

This option can also be defined as a function with this function signature:

{
  messageFormat: (log, messageKey, levelLabel, { colors }) => {
    // do some log message customization
    //
    // `colors` is a Colorette object with colors enabled based on `colorize` option
    return `This is a ${color.red('colorized')}, custom message: ${log[messageKey]}`;
  }
}

Limitations

Because pino-pretty uses stdout redirection, in some cases the command may terminate with an error due to shell limitations.

For example, currently, mingw64 based shells (e.g. Bash as supplied by git for Windows) are affected and terminate the process with a stdout is not a tty error message.

Any PRs are welcomed!

License

MIT License