node package manager



pino  Build Status Coverage Status TypeScript definitions on DefinitelyTyped

Extremely fast node.js logger, inspired by Bunyan. It also includes a shell utility to pretty-print its log files.



npm install pino --save

If you need support for Node.js v0.12 or v0.10, please install the latest 2.x release using the legacy tag:

npm install pino@legacy --save

Documentation for the legacy version 2.x is available on the v2.x.x branch.


'use strict'
var pino = require('pino')()'hello world')
pino.error('this is at error level')'the answer is %d', 42){ obj: 42 }, 'hello world'){ obj: 42, b: 2 }, 'hello world'){ obj: { aa: 'bbb' } }, 'another')
setImmediate(function () {'after setImmediate')
pino.error(new Error('an error'))
var child = pino.child({ a: 'property' })'hello child!')
var childsChild = child.child({ another: 'property' })'hello baby..')

This produces:

{"pid":94473,"hostname":"MacBook-Pro-3.home","level":30,"msg":"hello world","time":1459529098958,"v":1}
{"pid":94473,"hostname":"MacBook-Pro-3.home","level":50,"msg":"this is at error level","time":1459529098959,"v":1}
{"pid":94473,"hostname":"MacBook-Pro-3.home","level":30,"msg":"the answer is 42","time":1459529098960,"v":1}
{"pid":94473,"hostname":"MacBook-Pro-3.home","level":30,"msg":"hello world","time":1459529098960,"obj":42,"v":1}
{"pid":94473,"hostname":"MacBook-Pro-3.home","level":30,"msg":"hello world","time":1459529098960,"obj":42,"b":2,"v":1}
{"pid":94473,"hostname":"MacBook-Pro-3.home","level":50,"msg":"an error","time":1459529098961,"type":"Error","stack":"Error: an error\n    at Object.<anonymous> (/Users/davidclements/z/nearForm/pino/example.js:14:12)\n    at Module._compile (module.js:435:26)\n    at Object.Module._extensions..js (module.js:442:10)\n    at Module.load (module.js:356:32)\n    at Function.Module._load (module.js:311:12)\n    at Function.Module.runMain (module.js:467:10)\n    at startup (node.js:136:18)\n    at node.js:963:3","v":1}
{"pid":94473,"hostname":"MacBook-Pro-3.home","level":30,"msg":"hello child!","time":1459529098962,"a":"property","v":1}
{"pid":94473,"hostname":"MacBook-Pro-3.home","level":30,"msg":"hello baby..","time":1459529098962,"another":"property","a":"property","v":1}
{"pid":94473,"hostname":"MacBook-Pro-3.home","level":30,"msg":"after setImmediate","time":1459529098963,"v":1}


As far as we know, it is one of the fastest loggers in town:'hello world'):

benchBunyan*10000: 1355.229ms
benchWinston*10000: 2226.117ms
benchBole*10000: 291.727ms
benchDebug*10000: 445.291ms
benchLogLevel*10000: 322.181ms
benchPino*10000: 269.109ms
benchPinoExtreme*10000: 102.239ms{'hello': 'world'}):

benchBunyanObj*10000: 1464.568ms
benchWinstonObj*10000: 2177.602ms
benchBoleObj*10000: 322.105ms
benchLogLevelObject*10000: 1443.148ms
benchPinoObj*10000: 309.564ms
benchPinoUnsafeObj*10000: 301.308ms
benchPinoExtremeObj*10000: 130.343ms
benchPinoUnsafeExtremeObj*10000: 131.322ms

benchBunyanDeepObj*10000: 8749.174ms
benchWinstonDeepObj*10000: 17761.409ms
benchBoleDeepObj*10000: 5252.563ms
benchLogLevelDeepObj*10000: 43518.525ms
benchPinoDeepObj*10000: 5124.361ms
benchPinoUnsafeDeepObj*10000: 3539.253ms
benchPinoExtremeDeepObj*10000: 5138.457ms
benchPinoUnsafeExtremeDeepObj*10000: 3480.270ms'hello %s %j %d', 'world', {obj: true}, 4, {another: 'obj'}):

benchDebugInterpolateExtra*10000: 640.001ms
benchBunyanInterpolateExtra*10000: 2888.825ms
benchWinstonInterpolateExtra*10000: 2616.285ms
benchBoleInterpolateExtra*10000: 1313.470ms
benchLogLevelInterpolateExtra*10000: 1487.610ms
benchPinoInterpolateExtra*10000: 486.367ms
benchPinoUnsafeInterpolateExtra*10000: 457.778ms
benchPinoExtremeInterpolateExtra*10000: 314.635ms
benchPinoUnsafeExtremeInterpolateExtra*10000: 294.915ms

In many cases, pino is over 6x faster than alternatives.

For a fair comparison, LogLevel was extended to include a timestamp and bole had fastTime mode switched on.


A transport in most logging libraries is something that runs in-process to perform some operation with the finalized log line. For example, a transport might send the log line to a standard syslog server after processing the log line and reformatting it. For details on implementing, and some already written, transports, see our Transports⇗ document.

Pino does not natively support in-process transports.

Pino does not support in-process transports because Node processes are single threaded processes (ignoring some technical details). Given this restriction, one of the methods Pino employs to achieve its speed is to purposefully offload the handling of logs, and their ultimate destination, to external processes so that the threading capabilities of the OS can be used (or other CPUs).

One consequence of this methodology is that "error" logs do not get written to stderr. However, since Pino logs are in a parseable format, it is possible to use tools like pino-tee or jq to work with the logs. For example, to view only logs marked as "error" logs:

$ node an-app.js | jq 'select(.level == 50)'

In short, the way Pino generates logs:

  1. Reduces the impact of logging on your application to an extremely minimal amount.
  2. Gives greater flexibility in how logs are processed and stored.

Given all of the above, Pino clearly promotes out-of-process log processing. However, it is possible to wrap Pino and perform processing in-process. For an example of this, see pino-multi-stream.

Pino in the browser

Pino is compatible with browserify for browser side usage:

This can be useful with isomorphic/universal JavaScript code.

By default, in the browser, pino uses corresponding Log4j console methods (console.error, console.warn,, console.debug, console.trace) and uses console.error for any fatal level logs.

Browser Options

Pino can be passed a browser object in the options object, which can have a write property or an asObject property.

asObject (Boolean)

var pino = require('pino')({browser: {asObject: true}})

The asObject option will create a pino-like log object instead of passing all arguments to a console method, for instance:'hi') // creates and logs {msg: 'hi', level: 30, time: <ts>} 

When write is set, asObject will always be true.

write (Function | Object)

Instead of passing log messages to console.log they can be passed to a supplied function.

If write is set to a single function, all logging objects are passed to this function.

var pino = require('pino')({browser: {write: (o) => {
  // do something with o 

If write is an object, it can have methods that correspond to the levels. When a message is logged at a given level, the corresponding method is called. If a method isn't present, the logging falls back to using the console.

var pino = require('pino')({browser: {write: {
  info: function (o) {
    //process info log object 
  error: function (o) {
    //process error log object 


There's some fine points to be aware of, which are a result of worthwhile trade-offs:

11 Arguments

The logger functions (e.g. can take a maximum of 11 arguments.

If you need more than that to write a log entry, you're probably doing it wrong.

Duplicate Keys

It's possible for naming conflicts to arise between child loggers and children of child loggers.

This isn't as bad as it sounds, even if you do use the same keys between parent and child loggers Pino resolves the conflict in the sanest way.

For example, consider the following:

var pino = require('pino')
var fs = require('fs')
  .child({a: 'property'})
  .child({a: 'prop'})
$ cat my-log

Notice how there's two key's named a in the JSON output. The sub-childs properties appear after the parent child properties. This means if we run our logs through pino -t (or convert them to objects in any other way) we'll end up with one a property whose value corresponds to the lowest child in the hierarchy:

$ cat my-log | pino -t

This equates to the same log output that Bunyan supplies.

One of Pino's performance tricks is to avoid building objects and stringifying them, so we're building strings instead. This is why duplicate keys between parents and children will end up in log output.

The Team

Matteo Collina

David Mark Clements

James Sumners

Thomas Watson Steen

Chat on Gitter

Chat on IRC

You'll find an active group of Pino users in the #pino channel on Freenode, including some of the contributors to this project.


Pino is an OPEN Open Source Project. This means that:

Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.

See the file for more details.


This project was kindly sponsored by nearForm.

Logo and identity designed by Beibhinn Murphy O'Brien:


Licensed under MIT.