
Pino
- 55 installs
- 2 repo stars
- Updated August 3, 2026
- fandhe-ai/agent-reference-skills
Helps with ai & agent building tasks.
About
pino is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- pino
- AI & Agent Building
- AI-coding skill
Pino by the numbers
- 55 all-time installs (skills.sh)
- Ranked #6,837 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/fandhe-ai/agent-reference-skills --skill pinoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 55 |
|---|---|
| repo stars | ★ 2 |
| Last updated | August 3, 2026 |
| Repository | fandhe-ai/agent-reference-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Pino — Super Fast JSON Logger リファレンス
Pino 公式ドキュメントの全 API を網羅したスキル。 ユーザーのタスクに応じて適切な README.md を読み、そこから個別ファイルへ辿ること。
ディレクトリ構成
skills/pino/
SKILL.md
references/
api/
README.md
pino-function.md
options.md
destination.md
logger-instance.md
logger-methods.md
logger-child.md
logging-method-parameters.md
statics.md
interfaces-and-types.md
features/
README.md
browser.md
redaction.md
child-loggers.md
transports.md
asynchronous.md
pretty-printing.md
diagnostics.md
integrations/
README.md
web-frameworks.md
ecosystem.md
help/
README.md
avoid-message-conflict.md
best-performance-stdout.md
duplicate-keys.md
google-cloud-logging.md
log-filtering.md
log-levels-as-labels.md
log-rotation.md
lts.md
pino-with-debug.md
reopening-log-files.md
saving-to-multiple-files.md
testing.md
transports-and-systemd.md
unicode-and-windows.md
samples/
README.md
basic-logging.md
child-loggers.md
log-levels.md
redaction.md
transports.md
async-logging.md
web-frameworks.md
testing.md
scripts/
README.md
install.md
pretty-printing.md
transports.md
filtering.md
testing.md
log-rotation.md探索手順
タスクからカテゴリを引き、カテゴリの README.md で目的のページを特定する:
1. 下記マッピング表でタスクに対応するカテゴリを探す 2. そのカテゴリの references/{category}/README.md を参照して目的のページを特定する 3. 該当ページの .md を Read して詳細を確認する
タスク → カテゴリ マッピング
| タスク | カテゴリ | 参照 README |
|---|---|---|
| pino() 関数の使い方を知りたい、options を確認したい | api | references/api/README.md |
| Logger インスタンスのメソッド、child logger、Statics を調べたい | api | references/api/README.md |
| 型定義・インターフェースを確認したい | api | references/api/README.md |
| redaction で機密情報をマスクしたい | features | references/features/README.md |
| transport で出力先を切り替えたい、pino-pretty を使いたい | features | references/features/README.md |
| 非同期ロギング・ブラウザ対応・diagnostics を調べたい | features | references/features/README.md |
| Fastify / Express との統合方法を知りたい | integrations | references/integrations/README.md |
| エコシステム・関連パッケージを把握したい | integrations | references/integrations/README.md |
| ログローテーション・フィルタリング・テストを知りたい | help | references/help/README.md |
| トラブルシューティング(重複キー・文字コード・systemd)を解決したい | help | references/help/README.md |
| 典型的な使い方を知りたい | samples | samples/README.md |
| インストール・CLI コマンドを知りたい | scripts | scripts/README.md |
Destination
The destination parameter is the second optional argument to pino(). It controls where log output is written, supporting file descriptors, file paths, streams, and SonicBoom instances.
Signature
pino(options?, destination?: Number | String | Object | DestinationStream | SonicBoomOpts | WritableStream) => LoggerDefault
pino.destination(1) (STDOUT)
However, when something (e.g. a process manager) has monkey-patched process.stdout.write, process.stdout is used instead.
Usage
// pino.destination(1) by default
const stdoutLogger = require('pino')()
// destination param may be in first position when no options:
const fileLogger = require('pino')( pino.destination('/log/path'))
// use the stderr file handle to log to stderr:
const opts = {name: 'my-logger'}
const stderrLogger = require('pino')(opts, pino.destination(2))
// automatic wrapping in pino.destination
const fileLogger = require('pino')('/log/path')
// Asynchronous logging
const fileLogger = pino(pino.destination({ dest: '/log/path', sync: false }))Destination Types
- File descriptor (Number): e.g.
1for STDOUT,2for STDERR - File path (String): Absolute or relative path to a log file
- Object with `dest` property: Pointing to a file descriptor or path, plus additional SonicBoom options
- DestinationStream: Any object implementing the
write(msg)method - SonicBoomOpts: Options passed to SonicBoom (e.g.
{ dest, minLength, sync }) - WritableStream: Standard Node.js writable stream
For peak log writing performance, it is strongly recommended to use pino.destination to create the destination stream rather than an ordinary Node.js stream.
The destination parameter can also be the result of pino.transport().
destination[Symbol.for('pino.metadata')]
Default: false
Setting the global symbol Symbol.for('pino.metadata') as a key on the destination object to true causes the following properties to be set on the destination object after each log line is written:
destination.lastLevel- the last logging leveldestination.lastMsg- the last logging messagedestination.lastObj- the last logging objectdestination.lastTime- the last time (partial string returned by the time function)destination.lastLogger- the last logger instance (to support child loggers)
const dest = pino.destination('/dev/null')
dest[Symbol.for('pino.metadata')] = true
const logger = pino(dest)
logger.info({a: 1}, 'hi')
const { lastMsg, lastLevel, lastObj, lastTime} = dest
console.log(
'Logged message "%s" at level %d with object %o at time %s',
lastMsg, lastLevel, lastObj, lastTime
) // Logged message "hi" at level 30 with object { a: 1 } at time 1531590545089Notes
- If the parameter is a string integer (e.g.
'1'), it will be coerced to a number and used as a file descriptor. To avoid this, provide a full path (e.g./tmp/1). - If the
transportoption is supplied in the options object, a separatedestinationargument must NOT be passed -- an Error will be thrown. - The destination can be passed as the first argument when no options object is needed.
Related
- pino() function
- options
- statics - pino.destination()
- interfaces and types - DestinationStream
Interfaces and Types
TypeScript interfaces, types, and module augmentation capabilities provided by Pino.
Interfaces
MultiStreamRes
Properties:
- `write(data)`
data: Object | string- Returns: void
- Write
dataonto the streams held by the current instance.
- `add(dest)`
dest: StreamEntry | DestinationStream- Returns: MultiStreamRes
- Add
deststream to the array of streams of the current instance.
- `flushSync()`
- Returns:
undefined - Call
flushSyncon each stream held by the current instance.
- `lastId`
- number
- The ID assigned to the last stream assigned to the current instance.
- `minLevel`
- number
- The minimum level amongst all the streams held by the current instance.
- `remove(id)`
id: number- Removes a stream from the array of streams of the current instance using its assigned ID.
- `streams`
- Returns: StreamEntry[]
- The array of streams currently held by the current instance.
- `clone(level)`
level: Level- Returns: MultiStreamRes
- Returns a cloned object of the current instance but with the provided
level.
StreamEntry
Properties:
- `stream`: DestinationStream
- `level`: Optional, Level
DestinationStream
Properties:
- `write(msg)`
msg: string
Types
Level
type Level = "fatal" | "error" | "warn" | "info" | "debug" | "trace"TypeScript
Module Augmentation
Pino supports TypeScript module augmentation to extend its type definitions. This allows customizing the logging behavior to fit application-specific requirements.
LogFnFields Interface
The LogFnFields interface can be augmented to control what fields are allowed in logging method objects. This is useful for:
- Preventing certain fields from being logged (for security or compliance reasons)
- Enforcing specific field types across the application
- Enforcing consistent structured logging
Banning Fields
Ban specific fields by setting them to never. This prevents users from unintentionally overriding fields already set in the logger's base option.
declare module "pino" {
interface LogFnFields {
service?: never;
version?: never;
}
}
// These will now cause TypeScript errors
logger.info({ service: 'other-api', message: 'success' }) // Error
logger.info({ message: 'success' }) // OKEnforcing Field Types
Enforce specific types for certain fields:
declare module "pino" {
interface LogFnFields {
userId?: string;
requestId?: string;
}
}
// These will cause TypeScript errors
logger.info({ userId: 123 }) // Error: userId must be string
logger.info({ requestId: null }) // Error: requestId must be string
// This works fine
logger.info({ userId: '123' }) // OKEnforcing Structured Logging
Required fields (non-optional) enforce consistent structured logging by requiring specific fields in all log objects:
declare module "pino" {
interface LogFnFields {
userId: string
}
}
logger.info({ userId: '123' }) // OK
logger.info({}) // Error: Property 'userId' is missing in type '{}'Note: Required fields will cause TypeScript errors when logging certain types like Error objects that don't contain the required properties:
logger.error(new Error('test')) // Error: Property 'userId' is missing in type 'Error'This ensures that all log entries include required context fields, promoting consistent logging practices.
Notes
MultiStreamResis the return type ofpino.multistream().StreamEntryis used in the array passed topino.multistream().DestinationStreamis the minimal interface a destination must implement (just awrite(msg)method).LogFnFieldsaugmentation applies to all log method calls across the application.- Required (non-optional) fields in
LogFnFieldswill conflict withErrorobjects passed directly to log methods.
Related
- statics - pino.multistream()
- destination
- options
logger.child()
The logger.child method creates stateful child loggers where key-value pairs are pinned to the logger and output on every log line. Child loggers share the parent's output stream and inherit the parent's current log level at spawn time.
Signature
logger.child(bindings: object, options?: object) => LoggerParameters
bindings (Object)
An object of key-value pairs to include in every log line output via the returned child logger.
const child = logger.child({ MIX: {IN: 'always'} })
child.info('hello')
// {"level":30,"time":1531258616689,"msg":"hello","pid":64849,"hostname":"x","MIX":{"IN":"always"}}
child.info('child!')
// {"level":30,"time":1531258617401,"msg":"child!","pid":64849,"hostname":"x","MIX":{"IN":"always"}}The bindings object may contain any key except for reserved configuration keys level and serializers.
`bindings.serializers` (Object) - DEPRECATED: Use options.serializers instead.options (Object)
Options for the child logger. These options override the parent logger options.
options.level (String)
Overrides the log level of the child logger. By default, the parent log level is inherited. After creation, also accessible via logger.level.
const logger = pino()
logger.debug('nope') // will not log, since default level is info
const child = logger.child({foo: 'bar'}, {level: 'debug'})
child.debug('debug!') // will log as the `level` property set the level to debugoptions.msgPrefix (String)
Default: undefined
A prefix for every message of the child logger. By default, the parent prefix is inherited. If the parent already has a prefix, the parent's prefix appears first, then the child's.
const logger = pino({
msgPrefix: '[HTTP] '
})
logger.info('got new request!')
// > [HTTP] got new request!
const child = logger.child({avengers: 'assemble'}, {msgPrefix: '[Proxy] '})
child.info('message proxied!')
// > [HTTP] [Proxy] message proxied!options.redact (Array | Object)
Setting options.redact to an array or object overrides the parent redact options. To remove redact options inherited from the parent, set this value to an empty array ([]).
const logger = require('pino')({ redact: ['hello'] })
logger.info({ hello: 'world' })
// {"level":30,"time":1625794363403,"pid":67930,"hostname":"x","hello":"[Redacted]"}
const child = logger.child({ foo: 'bar' }, { redact: ['foo'] })
logger.info({ hello: 'world' })
// {"level":30,"time":1625794553558,"pid":67930,"hostname":"x","hello":"world", "foo": "[Redacted]" }options.serializers (Object)
Child loggers inherit serializers from the parent logger. Setting serializers in the options object overrides any configured parent serializers.
const logger = require('pino')()
logger.info({test: 'will appear'})
// {"level":30,"time":1531259759482,"pid":67930,"hostname":"x","test":"will appear"}
const child = logger.child({}, {serializers: {test: () => `child-only serializer`}})
child.info({test: 'will be overwritten'})
// {"level":30,"time":1531259784008,"pid":67930,"hostname":"x","test":"child-only serializer"}Notes
- Child loggers use the same output stream as the parent.
- The log level of a child is mutable and can be set independently of the parent.
- Child loggers inherit the parent's current log level at spawn time.
- The
bindingsobject may not contain the reserved keyslevelorserializers. - Setting
options.redactto[]removes all redaction inherited from the parent.
Related
- logger instance
- logger methods
- options - redact
- options - serializers
Logger Instance
The logger instance is the object returned by the main exported pino() function. Its primary purpose is to provide logging methods.
Overview
The default logging methods are trace, debug, info, warn, error, and fatal. Each logging method has the following signature:
logger.<level>([mergingObject], [message], [...interpolationValues])Log Methods
logger.trace([mergingObject], [message], [...interpolationValues])
Write a 'trace' level log, if the configured level allows for it.
logger.debug([mergingObject], [message], [...interpolationValues])
Write a 'debug' level log, if the configured level allows for it.
logger.info([mergingObject], [message], [...interpolationValues])
Write an 'info' level log, if the configured level allows for it.
logger.warn([mergingObject], [message], [...interpolationValues])
Write a 'warn' level log, if the configured level allows for it.
logger.error([mergingObject], [message], [...interpolationValues])
Write an 'error' level log, if the configured level allows for it.
logger.fatal([mergingObject], [message], [...interpolationValues])
Write a 'fatal' level log, if the configured level allows for it.
Since 'fatal' level messages are intended to be logged just before the process exits, the fatal method will always sync flush the destination. It is important not to misuse fatal since it will cause performance overhead if used for any other purpose than writing final log messages before the process crashes or exits.
logger.silent()
Noop function.
Usage
const pino = require('pino')
const logger = pino()
logger.info('hello world')
// {"level":30,"time":1531257112193,"msg":"hello world","pid":55956,"hostname":"x"}
logger.info({MIX: {IN: true}})
// {"level":30,"time":1531254555820,"pid":55956,"hostname":"x","MIX":{"IN":true}}
logger.error(new Error('something broke'))
// {"level":50,"time":...,"msg":"something broke","stack":"...","type":"Error",...}
logger.fatal('process crashing')
// always sync flushes before exitingLevel Values
| Level | trace | debug | info | warn | error | fatal | silent |
|---|---|---|---|---|---|---|---|
| Value | 10 | 20 | 30 | 40 | 50 | 60 | Infinity |
Notes
- The logging level is a minimum level. If
logger.levelisinfo(30), theninfo,warn,error, andfatalmethods are enabled, buttraceanddebugare not. - The
silentlevel disables all logging; its method is a noop. fatalalways sync flushes the destination -- do not use it for general-purpose logging.- All log methods share the same parameter signature:
([mergingObject], [message], [...interpolationValues]).
Related
- logging method parameters
- logger child
- logger methods
- options
Logger Methods and Properties
Additional methods and properties available on the logger instance beyond the core logging methods.
logger.bindings()
Returns an object containing all the current bindings, cloned from the ones passed in via logger.child().
const child = logger.child({ foo: 'bar' })
console.log(child.bindings())
// { foo: 'bar' }
const anotherChild = child.child({ MIX: { IN: 'always' } })
console.log(anotherChild.bindings())
// { foo: 'bar', MIX: { IN: 'always' } }logger.setBindings(bindings)
Adds to the bindings of this logger instance.
Note: Does not overwrite bindings. Can potentially result in duplicate keys in log lines.
logger.flush([cb])
Flushes the content of the buffer when using pino.destination({ sync: false }).
This is an asynchronous, best used as fire-and-forget, operation. If there is a need to wait for the logs to be flushed, a callback should be used.
The use case is primarily for asynchronous logging, which may buffer log lines while others are being written. logger.flush can be used to flush on a long interval (e.g. ten seconds) for an optimum balance between efficient logging at high demand and safer logging at low demand.
Note: flush() does not work when using pino-pretty.
logger.level (String) [Getter/Setter]
Set this property to the desired logging level.
The core levels and their values:
| Level | trace | debug | info | warn | error | fatal | silent |
|---|---|---|---|---|---|---|---|
| Value | 10 | 20 | 30 | 40 | 50 | 60 | Infinity |
The logging level is a _minimum_ level based on the associated value. For instance if logger.level is info (30), then info (30), warn (40), error (50), and fatal (60) methods will be enabled but trace (10) and debug (20) will not.
The silent logging level disables all logging; the silent log method is a noop function.
logger.isLevelEnabled(level)
A utility method for determining if a given log level will write to the destination.
Parameters
- `level` (String): The level to check against.
if (logger.isLevelEnabled('debug')) logger.debug('conditional log')Additional level-related parameters
- `levelLabel` (String): Defines the method name of the new level.
- `levelValue` (Number): Defines the associated minimum threshold value for the level, determining its priority among other levels.
logger.levelVal (Number)
Supplies the integer value for the current logging level.
if (logger.levelVal === 30) {
console.log('logger level is `info`')
}logger.levels (Object)
Holds the mappings between levels and values, and vice versa.
$ node -p "require('pino')().levels"{ labels:
{ '10': 'trace',
'20': 'debug',
'30': 'info',
'40': 'warn',
'50': 'error',
'60': 'fatal' },
values:
{ fatal: 60, error: 50, warn: 40, info: 30, debug: 20, trace: 10 } }logger[Symbol.for('pino.serializers')]
Returns the serializers as applied to the current logger instance. If a child logger did not register its own serializer upon instantiation, the serializers of the parent will be returned.
Event: 'level-change'
The logger instance is also an EventEmitter. A listener function can be attached via the level-change event.
The listener is passed five arguments:
1. levelLabel - the new level string, e.g. trace 2. levelValue - the new level number, e.g. 10 3. previousLevelLabel - the prior level string, e.g. info 4. previousLevelValue - the prior level number, e.g. 30 5. logger - the logger instance from which the event originated
const logger = require('pino')()
logger.on('level-change', (lvl, val, prevLvl, prevVal) => {
console.log('%s (%d) was changed to %s (%d)', prevLvl, prevVal, lvl, val)
})
logger.level = 'trace' // trigger eventDue to a known bug, every logger.child() call will fire a level-change event. These events can be ignored:
const logger = require('pino')()
logger.on('level-change', function (lvl, val, prevLvl, prevVal, instance) {
if (logger !== instance) {
return
}
console.log('%s (%d) was changed to %s (%d)', prevLvl, prevVal, lvl, val)
})
logger.child({}); // trigger an event by creating a child instance, notice no console.log
logger.level = 'trace' // trigger event using actual value change, notice console.loglogger.version (String)
Exposes the Pino package version. Also available on the exported pino function.
logger.msgPrefix (String | Undefined)
Exposes the cumulative msgPrefix of the logger.
Notes
setBindings()adds to existing bindings rather than replacing them, which can lead to duplicate keys.flush()is primarily useful for asynchronous logging withpino.destination({ sync: false }).flush()does not work withpino-pretty.- The
level-changeevent fires on child creation as well as actual level changes. - The
levelsobject contains bothlabels(number-to-string) andvalues(string-to-number) mappings.
Related
- logger instance
- logger child
- options
- statics
Logging Method Parameters
Each logging method (trace, debug, info, warn, error, fatal) shares the same signature and parameter behavior. This reference describes the parameters common to all logging methods.
Signature
logger.<level>([mergingObject], [message], [...interpolationValues])Parameters
mergingObject (Object)
An object can optionally be supplied as the first parameter. Each enumerable key and value of the mergingObject is copied into the JSON log line.
logger.info({MIX: {IN: true}})
// {"level":30,"time":1531254555820,"pid":55956,"hostname":"x","MIX":{"IN":true}}If the object is of type Error, it is wrapped in an object containing a property err ({ err: mergingObject }), allowing for a unified error handling flow.
Options serializers and errorKey can be used at instantiation time to change the namespace from err to another string.
message (String)
A message string can optionally be supplied as the first parameter, or as the second parameter after supplying a mergingObject.
By default, the contents of the message parameter will be merged into the JSON log line under the msg key:
logger.info('hello world')
// {"level":30,"time":1531257112193,"msg":"hello world","pid":55956,"hostname":"x"}The message parameter takes precedence over the mergingObject. If a mergingObject contains a msg property and a message parameter is also supplied, the msg property in the output will be the value of the message parameter.
If no message parameter is provided and the mergingObject is of type Error or has a property named err, the message parameter is set to the message value of the error. See option errorKey to change the namespace.
The messageKey option can change the namespace from msg to another string.
The message string may contain printf-style placeholders:
%s- string placeholder, non-string values will have.toString()called%d- digit placeholder%O,%o,%j- object placeholder
...interpolationValues (Any)
All arguments supplied after message are serialized and interpolated according to any supplied printf-style placeholders (%s, %d, %o|%O|%j) to form the final output msg value.
logger.info('%o hello %s', {worldly: 1}, 'world')
// {"level":30,"time":1531257826880,"msg":"{\"worldly\":1} hello world","pid":55956,"hostname":"x"}Since pino v6, consecutive parameters are NOT automatically concatenated:
logger.info('hello', 'world')
// {"level":30,"time":1531257618044,"msg":"hello","pid":55956,"hostname":"x"}
// world is missingA hook can be injected to modify this behavior:
const pinoOptions = {
hooks: { logMethod }
}
function logMethod (args, method) {
if (args.length === 2) {
args[0] = `${args[0]} %j`
}
method.apply(this, args)
}
const logger = pino(pinoOptions)Errors
Errors can be supplied as either the first parameter or as the err property on the mergingObject.
Options serializers and errorKey can be used at instantiation time to change the namespace from err to another string.
Note: This section describes the default configuration. The error serializer can be mapped to a different key using the serializers option.logger.info(new Error("test"))
// {"level":30,"time":1531257618044,"msg":"test","stack":"...","type":"Error","pid":55956,"hostname":"x"}
logger.info({ err: new Error("test"), otherkey: 123 }, "some text")
// {"level":30,"time":1531257618044,"err":{"msg": "test", "stack":"...","type":"Error"},"msg":"some text","pid":55956,"hostname":"x","otherkey":123}Notes
- The
mergingObjectparameter is always optional and positional (first argument). - Printf-style interpolation only works when explicit placeholders are present in the message string.
- Since pino v6, extra string arguments without placeholders are silently dropped, not concatenated.
- The
logMethodhook can be used to restore pre-v6 concatenation behavior.
Related
- options
- logger instance
- logger child
Options
The options object is the first optional parameter to the pino() function. It controls all aspects of logger behavior including log levels, formatting, serialization, redaction, and more.
Signature
pino(options?: object, destination?) => LoggerOptions Reference
name (String)
Default: undefined
The name of the logger. When set, adds a name field to every JSON line logged.
level (String)
Default: 'info'
The minimum level to log. Pino will not log messages with a lower level. One of 'fatal', 'error', 'warn', 'info', 'debug', 'trace' or 'silent'.
Additional levels can be added via the customLevels option.
levelComparison ("ASC" | "DESC" | Function)
Default: 'ASC'
Customize levels order. Pass 'DESC' for descending order, or a function accepting current and expected values that returns a boolean indicating whether the current level should be shown.
const logger = pino({
levelComparison: 'DESC',
customLevels: {
foo: 20, // `foo` is more valuable than `bar`
bar: 10
},
})
// OR
const logger = pino({
levelComparison: function(current, expected) {
return current >= expected;
}
})customLevels (Object)
Default: undefined
Define additional logging levels. Keys are the level namespace, values are the numerical value.
const logger = pino({
customLevels: {
foo: 35
}
})
logger.foo('hi')useOnlyCustomLevels (Boolean)
Default: false
Use only defined customLevels and omit Pino's built-in levels. The logger's default level must be changed to a value in customLevels.
Warning: this option may not be supported by downstream transports.
const logger = pino({
customLevels: {
foo: 35
},
useOnlyCustomLevels: true,
level: 'foo'
})
logger.foo('hi')
logger.info('hello') // Will throw an error saying info is not found in logger objectdepthLimit (Number)
Default: 5
Limit stringification at a specific nesting depth when logging circular objects.
edgeLimit (Number)
Default: 100
Limit stringification of properties/elements when logging a specific object/array with circular references.
mixin (Function)
Default: undefined
Called each time an active logging method is called. The function receives three parameters: 1. mergeObject or an empty object 2. The log level number 3. The logger or child logger instance
Must synchronously return an object whose properties will be added to the logged JSON.
let n = 0
const logger = pino({
mixin () {
return { line: ++n }
}
})
logger.info('hello')
// {"level":30,"time":1573664685466,"pid":78742,"hostname":"x","line":1,"msg":"hello"}
logger.info('world')
// {"level":30,"time":1573664685469,"pid":78742,"hostname":"x","line":2,"msg":"world"}The result of mixin() is supposed to be a _new_ object. For performance reasons, the object returned by mixin() will be mutated by pino:
const mixin = {
appName: 'My app'
}
const logger = pino({
mixin() {
return mixin;
}
})
logger.info({
description: 'Ok'
}, 'Message 1')
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","appName":"My app","description":"Ok","msg":"Message 1"}
logger.info('Message 2')
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","appName":"My app","description":"Ok","msg":"Message 2"}
// Note: the second log contains "description":"Ok" text, even if it was not provided.Using mixin with the level label:
const logger = pino({
mixin(_context, level) {
return { 'level-label': logger.levels.labels[level] }
}
})
logger.info({
description: 'Ok'
}, 'Message 1')
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","description":"Ok","level-label":"info","msg":"Message 1"}
logger.error('Message 2')
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","level-label":"error","msg":"Message 2"}If the mixin feature is being used merely to add static metadata, a child logger should be used instead. The mixin approach is useful to avoid the duplicate keys caveat when concatenating values for a specific key:
const logger = pino({
mixin (obj, num, logger) {
return {
tags: logger.tags
}
}
})
logger.tags = {}
logger.addTag = function (key, value) {
logger.tags[key] = value
}
function createChild (parent, ...context) {
const newChild = logger.child(...context)
newChild.tags = { ...logger.tags }
newChild.addTag = function (key, value) {
newChild.tags[key] = value
}
return newChild
}
logger.addTag('foo', 1)
const child = createChild(logger, {})
child.addTag('bar', 2)
logger.info('this will only have `foo: 1`')
child.info('this will have both `foo: 1` and `bar: 2`')
logger.info('this will still only have `foo: 1`')As of pino 7.x, when mixin is used with the nestedKey option, the object returned from mixin() will also be nested (prior versions mixed into the root):
const logger = pino({
nestedKey: 'payload',
mixin() {
return { requestId: requestId.currentId() }
}
})
logger.info({
description: 'Ok'
}, 'Message 1')
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","payload":{"requestId":"dfe9a9014b","description":"Ok"},"msg":"Message 1"}mixinMergeStrategy (Function)
Default: undefined
Called each time an active logging method is called. Receives two parameters: 1. mergeObject or empty object 2. The result from mixin() or empty object
Must synchronously return an object.
// Default strategy, `mergeObject` has priority
const logger = pino({
mixin() {
return { tag: 'docker' }
},
// mixinMergeStrategy(mergeObject, mixinObject) {
// return Object.assign(mixinMeta, mergeObject)
// }
})
logger.info({
tag: 'local'
}, 'Message')
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","tag":"local","msg":"Message"}// Custom mutable strategy, `mixin` has priority
const logger = pino({
mixin() {
return { tag: 'k8s' }
},
mixinMergeStrategy(mergeObject, mixinObject) {
return Object.assign(mergeObject, mixinObject)
}
})
logger.info({
tag: 'local'
}, 'Message')
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","tag":"k8s","msg":"Message"}// Custom immutable strategy, `mixin` has priority
const logger = pino({
mixin() {
return { tag: 'k8s' }
},
mixinMergeStrategy(mergeObject, mixinObject) {
return Object.assign({}, mergeObject, mixinObject)
}
})
logger.info({
tag: 'local'
}, 'Message')
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","tag":"k8s","msg":"Message"}redact (Array | Object)
Default: undefined
As an array, specifies paths that should have their values redacted from any log output. Each path must be a string using JavaScript dot and bracket notation.
If an object is supplied, three options can be specified:
- `paths` (Array): Required. An array of paths.
- `censor` (String | Function | undefined): Optional. When a String, overwrites redacted keys. When
undefined, the key is removed entirely. Can also be a mapping function with signature(value, path) => redactedValue. Default:'[Redacted]' - `remove` (Boolean): Optional. Instead of censoring, remove both the key and the value. Default:
false
WARNING: Never allow user input to define redacted paths.
hooks (Object)
An object mapping to hook functions. Hook functions must be synchronous.
logMethod
Allows manipulating the parameters passed to logger methods. Signature: logMethod(args, method, level).
This hook must invoke method using apply: method.apply(this, newArgumentsArray).
const hooks = {
logMethod (inputArgs, method, level) {
if (inputArgs.length >= 2) {
const arg1 = inputArgs.shift()
const arg2 = inputArgs.shift()
return method.apply(this, [arg2, arg1, ...inputArgs])
}
return method.apply(this, inputArgs)
}
}streamWrite
Allows manipulating the stringified JSON log data just before writing to transports. Receives the stringified JSON and must return valid stringified JSON.
const hooks = {
streamWrite (s) {
return s.replaceAll('sensitive-api-key', 'XXX')
}
}formatters (Object)
An object containing functions for formatting the shape of log lines. These functions should return a JSONifiable object and should never throw.
level
Changes the shape of the log level. Default shape is { level: number }. Takes two arguments: the label (e.g. 'info') and the numeric value (e.g. 30).
Note: The log level cannot be customized when using multiple transports.
const formatters = {
level (label, number) {
return { level: number }
}
}bindings
Changes the shape of the bindings. Default shape is { pid, hostname }. Takes a single argument: the bindings object. Called once when creating logger.
const formatters = {
bindings (bindings) {
return { pid: bindings.pid, hostname: bindings.hostname }
}
}log
Changes the shape of the log object. Called every time a log method is invoked. All arguments passed to the log method except the message are passed to this function. By default does not change the shape.
const formatters = {
log (object) {
return object
}
}serializers (Object)
Default: {err: pino.stdSerializers.err}
An object containing functions for custom serialization of objects. These functions should return a JSONifiable object and should never throw. When logging an object, each top-level property matching the exact key of a serializer will be serialized using the defined serializer.
The err serializer is also applied when the object is an instance of Error (e.g. logger.info(new Error('kaboom'))). See errorKey option to change the err namespace.
msgPrefix (String)
Default: undefined
A prefix for every message of the logger and its children.
const logger = pino({
msgPrefix: '[HTTP] '
})
logger.info('got new request!')
// > [HTTP] got new request!
const child = logger.child({})
child.info('User authenticated!')
// > [HTTP] User authenticated!base (Object)
Default: {pid: process.pid, hostname: os.hostname()}
Key-value object added as child logger to each log line. Set to undefined to avoid adding pid and hostname properties.
enabled (Boolean)
Default: true
Set to false to disable logging.
crlf (Boolean)
Default: false
Set to true to log newline delimited JSON with \r\n instead of \n.
timestamp (Boolean | Function)
Default: true
Enables or disables the inclusion of a timestamp. If a function is supplied, it must synchronously return a partial JSON string representation of the time, e.g. ,"time":1493426328206 (the default).
If set to false, no timestamp will be included.
timestamp: () => `,"time":"${new Date(Date.now()).toISOString()}"`
// which is equivalent to:
// timestamp: stdTimeFunctions.isoTimeCaution: attempting to format time in-process will significantly impact logging performance.
messageKey (String)
Default: 'msg'
The string key for the 'message' in the JSON object.
errorKey (String)
Default: 'err'
The string key for the 'error' in the JSON object.
nestedKey (String)
Default: null
If there's a chance that objects being logged have properties that conflict with pino's own (level, timestamp, pid, etc), pino can be configured with nestedKey to place logged objects under a specific key.
const logger = require('pino')({
nestedKey: 'payload'
})
const thing = { level: 'hi', time: 'never', foo: 'bar'} // has pino-conflicting properties!
logger.info(thing)
// logs the following:
// {"level":30,"time":1578357790020,"pid":91736,"hostname":"x","payload":{"level":"hi","time":"never","foo":"bar"}}browser (Object)
Browser only. May have asObject and write keys. Separately documented in the Browser API documentation.
transport (Object)
Shorthand for the pino.transport() function. Supports the same input options:
require('pino')({
transport: {
target: '/absolute/path/to/my-transport.mjs'
}
})
// or multiple transports
require('pino')({
transport: {
targets: [
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
{ target: 'some-file-transport', options: { destination: '/dev/null' }
]
}
})If the transport option is supplied, a destination parameter may NOT also be passed as a separate argument:
pino({ transport: {}}, '/path/to/somewhere') // THIS WILL NOT WORK, DO NOT DO THIS
pino({ transport: {}}, process.stderr) // THIS WILL NOT WORK, DO NOT DO THISonChild (Function)
A synchronous callback called on each creation of a new child, receiving the child instance as its first argument. Any error thrown inside the callback will be uncaught and should be handled inside the callback.
const parent = require('pino')({ onChild: (instance) => {
// Execute call back code for each newly created child.
}})
// `onChild` will now be executed with the new child.
parent.child(bindings)Related
- pino() function
- destination
- statics
- logger child
pino() Function
The exported pino function is the main entry point for creating a Pino logger instance. It accepts two optional parameters and returns a logger.
Signature
pino([options], [destination]) => LoggerParameters
- `options` (Object) - Optional. Configuration object controlling logger behavior such as log level, serializers, formatters, redaction, and more.
- `destination` (Number | String | Object | DestinationStream | SonicBoomOpts | WritableStream) - Optional. Where log lines are written. Defaults to
pino.destination(1)(STDOUT).
Usage
const pino = require('pino')
// Basic usage with defaults (logs to STDOUT at 'info' level)
const logger = pino()
// With options only
const logger = pino({ level: 'debug', name: 'my-app' })
// With destination only (no options)
const logger = pino(pino.destination('/log/path'))
// With both options and destination
const logger = pino(
{ name: 'my-logger' },
pino.destination(2) // stderr
)
// Shorthand: string path auto-wrapped in pino.destination
const logger = pino('/log/path')Notes
- When only a destination is needed (no options), it can be passed as the first argument.
- A string path passed as the destination is automatically wrapped with
pino.destination(). - If the
transportoption is supplied, a separatedestinationparameter must NOT also be passed -- anErrorwill be thrown. - A string integer (e.g.
'1') passed as destination will be coerced to a number and used as a file descriptor. To avoid this, provide a full path (e.g./tmp/1).
Related
- options
- destination
- Logger Instance
API
| Name | Description | Path |
|---|---|---|
| Destination | The destination parameter is the second optional argument to pino(). It controls where log output is written, supporting file descriptors, file paths, streams, and SonicBoom instances. | destination.md |
| Interfaces and Types | TypeScript interfaces, types, and module augmentation capabilities provided by Pino. | interfaces-and-types.md |
| logger.child() | The logger.child method creates stateful child loggers where key-value pairs are pinned to the logger and output on every log line. | logger-child.md |
| Logger Instance | The logger instance is the object returned by the main exported pino() function. Its primary purpose is to provide logging methods. | logger-instance.md |
| Logger Methods and Properties | Additional methods and properties available on the logger instance beyond the core logging methods. | logger-methods.md |
| Logging Method Parameters | Each logging method (trace, debug, info, warn, error, fatal) shares the same signature and parameter behavior. | logging-method-parameters.md |
| Options | The options object is the first optional parameter to the pino() function. It controls all aspects of logger behavior including log levels, formatting, serialization, redaction, and more. | options.md |
| pino() Function | The exported pino function is the main entry point for creating a Pino logger instance. It accepts two optional parameters and returns a logger. | pino-function.md |
| Statics | Static methods and properties available on the exported pino function itself (not on logger instances). | statics.md |
Statics
Static methods and properties available on the exported pino function itself (not on logger instances).
pino.destination([opts]) => SonicBoom
Create a Pino Destination instance: a stream-like object with significantly more throughput than a standard Node.js stream.
const pino = require('pino')
const logger = pino(pino.destination('./my-file'))
const logger2 = pino(pino.destination())
const logger3 = pino(pino.destination({
dest: './my-file',
minLength: 4096, // Buffer before writing
sync: false // Asynchronous logging, the default
}))
const logger4 = pino(pino.destination({
dest: './my-file2',
sync: true // Synchronous logging
}))The method may be passed a file path or a numerical file descriptor. By default, pino.destination will use process.stdout.fd (1) as the file descriptor.
pino.destination is implemented on `sonic-boom`.
A pino.destination instance can also be used to reopen closed files (for example, for log rotation scenarios).
pino.transport(options) => ThreadStream
Create a stream that routes logs to a worker thread wrapping a Pino Transport.
Single transport
const pino = require('pino')
const transport = pino.transport({
target: 'some-transport',
options: { some: 'options for', the: 'transport' }
})
pino(transport)Multiple transports with levels
const pino = require('pino')
const transport = pino.transport({
targets: [{
level: 'info',
target: 'pino-pretty' // must be installed separately
}, {
level: 'trace',
target: 'pino/file',
options: { destination: '/path/to/store/logs' }
}]
})
pino(transport)Pipeline
const pino = require('pino')
const transport = pino.transport({
pipeline: [{
target: 'pino-syslog' // must be installed separately
}, {
target: 'pino-socket' // must be installed separately
}]
})
pino(transport)Multiple transports including pipelines
const pino = require('pino')
const transport = pino.transport({
targets: [{
level: 'info',
target: 'pino-pretty' // must be installed separately
}, {
level: 'trace',
target: 'pino/file',
options: { destination: '/path/to/store/logs' }
}, {
pipeline: [{
target: 'pino-syslog' // must be installed separately
}, {
target: 'pino-socket' // must be installed separately
}]
}
]
})
pino(transport)Framework integration
If embedding/integrating pino within a framework, make pino aware of the calling script:
const pino = require('pino')
const getCaller = require('get-caller-file')
module.exports = function build () {
const logger = pino({
transport: {
caller: getCaller(),
target: 'transport',
options: { destination: './destination' }
}
})
return logger
}Options
- `target`: The transport to pass logs through. May be an installed module name or an absolute path.
- `options`: An options object which is serialized (Structured Clone Algorithm), passed to the worker thread, parsed, and then passed to the exported transport function.
- `worker`: Worker thread configuration options. Additionally supports
worker.autoEnd-- if set tofalse, logs will not be flushed on process exit. The developer must then calltransport.end()to flush logs. - `targets`: May be specified instead of
target. Must be an array of transport configurations and/or pipelines. Each includesoptions,target, and an optionallevel(defaults toinfo). - `pipeline`: May be specified instead of
target. Must be an array of transport configurations. All intermediate steps must beTransformstreams, notWritable. - `dedupe`: See
pino.multistreamoptions.
Notes on level filtering
- The top-level
levelinpino.transport({ ... })is not used for filtering. - With a single
target(or a singlepipeline), filtering is controlled bylogger.level. - Per-transport level filtering is applied when using
targets(multi-destination mode).
Thread lifecycle
If WeakRef, WeakMap, and FinalizationRegistry are available (v14.5.0+), the thread will be automatically terminated when the stream or logger goes out of scope. The transport() function adds listeners to process.on('beforeExit') and process.on('exit') to ensure the worker is flushed and data synced before the process exits.
Any 'error' event emitted by the transport must be considered fatal and the process must be terminated. Error events are not recoverable.
pino.multistream(streamsArray, opts) => MultiStreamRes
Create a stream composed of multiple destination streams. Returns an object implementing the MultiStreamRes interface.
var fs = require('node:fs')
var pino = require('pino')
var pretty = require('pino-pretty')
var streams = [
{stream: fs.createWriteStream('/tmp/info.stream.out')},
{stream: pretty() },
{level: 'debug', stream: fs.createWriteStream('/tmp/debug.stream.out')},
{level: 'fatal', stream: fs.createWriteStream('/tmp/fatal.stream.out')}
]
var log = pino({
level: 'debug' // this MUST be set at the lowest level of the
// destinations
}, pino.multistream(streams))
log.debug('this will be written to /tmp/debug.stream.out')
log.info('this will be written to /tmp/debug.stream.out and /tmp/info.stream.out')
log.fatal('this will be written to /tmp/debug.stream.out, /tmp/info.stream.out and /tmp/fatal.stream.out')In order for multistream to work, the log level must be set to the lowest level used in the streams array. Default is info.
Options
- `levels`: Pass custom log level definitions as an object.
- `dedupe`: Set to
trueto send logs only to the stream with the higher level. Default:false.
dedupe is useful for redirecting error logs to process.stderr and others to process.stdout:
var pino = require('pino')
var multistream = pino.multistream
var streams = [
{level: 'debug', stream: process.stdout},
{level: 'error', stream: process.stderr},
]
var opts = {
levels: {
silent: Infinity,
fatal: 60,
error: 50,
warn: 50,
info: 30,
debug: 20,
trace: 10
},
dedupe: true,
}
var log = pino({
level: 'debug' // this MUST be set at the lowest level of the
// destinations
}, multistream(streams, opts))
log.debug('this will be written ONLY to process.stdout')
log.info('this will be written ONLY to process.stdout')
log.error('this will be written ONLY to process.stderr')
log.fatal('this will be written ONLY to process.stderr')pino.stdSerializers (Object)
Provides functions for serializing objects common to many projects. The standard serializers are directly imported from `pino-std-serializers`.
pino.stdTimeFunctions (Object)
The timestamp option can accept a function that determines the timestamp value in a log line.
Available functions:
- `pino.stdTimeFunctions.epochTime`: Milliseconds since Unix epoch (Default)
- `pino.stdTimeFunctions.unixTime`: Seconds since Unix epoch
- `pino.stdTimeFunctions.nullTime`: Clears timestamp property (Used when
timestamp: false) - `pino.stdTimeFunctions.isoTime`: ISO 8601-formatted time in UTC
- `pino.stdTimeFunctions.isoTimeNano`: RFC 3339-formatted time in UTC with nanosecond precision
pino.symbols (Object)
For integration purposes with ecosystem and third-party libraries, pino.symbols exposes the symbols used to hold non-public state and methods on the logger instance.
Access to the symbols allows logger state to be adjusted, and methods to be overridden or proxied for performant integration where necessary.
The pino.symbols object is intended for library implementers and should not be utilized for general use.
pino.version (String)
Exposes the Pino package version. Also available on the logger instance.
Notes
pino.destination()provides significantly better throughput than standard Node.js streams.pino.transport()runs transports in a worker thread for non-blocking I/O.- When using
pino.multistream(), the logger's level MUST be set to the lowest level among all streams. pino.symbolsis for library authors; general application code should not rely on it.console.logandprocess.stdoutwill not produce output afterprocess.exit()is called on the main thread, even if the transport worker has not finished flushing.
Related
- pino() function
- options
- destination
- interfaces and types
Asynchronous Logging
Asynchronous logging enables the minimum overhead of Pino by buffering log messages and writing them in larger chunks rather than performing blocking writes for each log line.
Basic Usage
const pino = require('pino')
const logger = pino(pino.destination({
dest: './my-file', // omit for stdout
minLength: 4096, // Buffer before writing
sync: false // Asynchronous logging
}))Synchronous logging can be turned on by passing sync: true. In synchronous mode, log messages are directly written to the output stream as they are generated with a blocking operation.
Implementation
pino.destinationis implemented on top of `sonic-boom`.pino.destinationruns in the main thread (as opposed topino/filetransport which runs in a worker thread).
AWS Lambda
Asynchronous logging is disabled by default on AWS Lambda or any other environment that modifies process.stdout. If forcefully turned on, call dest.flushSync() at the end of each function execution to avoid losing data.
Caveats
- There is not a one-to-one relationship between calls to logging methods (e.g.
logger.info) and writes to a log file. - There is a possibility of the most recently buffered log messages being lost in case of a system failure (e.g. a power cut).
Flush Limitations with pino-pretty
The logger.flush() method does not work when using pino-pretty because:
1. Transport Architecture: pino-pretty runs in a separate worker thread via the transport mechanism. 2. Buffer Flow: When you call logger.flush(), it flushes the SonicBoom destination in the main thread, but the logs remain queued in the thread-stream worker waiting to be processed by pino-pretty. 3. No Cross-Thread Flush: The flush operation never propagates through to the worker thread where the pretty printer is processing the output.
Even with logger.flush(), formatted logs may not appear immediately. The flush only ensures the main thread buffer is written, not the formatted output.
Notes
- Asynchronous logging provides the best performance by buffering writes.
- Use
sync: falseinpino.destination()to enable async mode. minLengthcontrols the buffer size before flushing to disk.- Always call
dest.flushSync()on AWS Lambda before the function returns. - The most recent buffered messages may be lost on unexpected system failure.
logger.flush()does not work across worker thread boundaries (e.g. withpino-pretty).
Related
- Transports
- Pretty printing
- API options
Browser API
Pino is compatible with browserify for browser-side usage, making it useful for isomorphic/universal JavaScript code. By default, in the browser, Pino uses corresponding Log4j console methods (console.error, console.warn, console.info, 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 with the following properties.
asObject (Boolean)
Creates a pino-like log object instead of passing all arguments to a console method.
const pino = require('pino')({browser: {asObject: true}})
pino.info('hi') // creates and logs {msg: 'hi', level: 30, time: <ts>}When write is set, asObject will always be true.
asObjectBindingsOnly (Boolean)
Similar to asObject but keeps the message and arguments unformatted. This allows deferring formatting to the console methods, where browsers have richer formatting in their devtools.
const pino = require('pino')({browser: {asObjectBindingsOnly: true}})
pino.info('hello %s', 'world') // creates and logs {level: 30, time: <ts>}, 'hello %s', 'world'formatters (Object)
An object containing functions for formatting the shape of the log lines. Currently supports formatting for the level object only.
level
Changes the shape of the log level. The default shape is { level: number }. The function takes two arguments: the label of the level (e.g. 'info') and the numeric value (e.g. 30).
const formatters = {
level (label, number) {
return { level: number }
}
}reportCaller (Boolean)
Attempts to capture and include the originating callsite (file:line:column) for each log call.
- When used with
asObject(or whenformattersare provided), the callsite is added as acallerstring property on the emitted log object. - In the default mode (non-object), the callsite string is appended as the last argument passed to the corresponding
consolemethod.
// Object mode: adds `caller` to the log object
const pino = require('pino')({
browser: {
asObject: true,
reportCaller: true
}
})
pino.info('hello')
// -> { level: 30, msg: 'hello', time: <ts>, caller: '/path/to/file.js:10:15' }
// Default mode: appends the caller string as the last console argument
const pino2 = require('pino')({
browser: {
reportCaller: true
}
})
pino2.info('hello')
// -> console receives: 'hello', '/path/to/file.js:10:15'This is a best-effort feature that parses the JavaScript Error stack. Stack formats vary across engines. The clickable link shown by devtools for a console message is determined by where console.* is invoked and cannot be changed by libraries.
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:
const 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 is not present, the logging falls back to using the console:
const pino = require('pino')({
browser: {
write: {
info: function (o) {
//process info log object
},
error: function (o) {
//process error log object
}
}
}
})serialize (Boolean | Array)
Serializers provided to Pino are ignored by default in the browser, including the standard serializers. Since the default destination is the console, values such as Error objects are enhanced for inspection, which they otherwise would not be if the Error serializer was enabled.
Turn all serializers on:
const pino = require('pino')({
browser: {
serialize: true
}
})Selectively enable via an array:
const pino = require('pino')({
serializers: {
custom: myCustomSerializer,
another: anotherSerializer
},
browser: {
serialize: ['custom']
}
})
// following will apply myCustomSerializer to the custom property,
// but will not apply anotherSerializer to another key
pino.info({custom: 'a', another: 'b'})When serialize is true, the standard error serializer is also enabled. If serialize is an array, the standard error serializer is automatically enabled but can be explicitly disabled:
const pino = require('pino')({
serializers: {
custom: myCustomSerializer,
another: anotherSerializer
},
browser: {
serialize: ['!stdSerializers.err', 'custom'] //will not serialize Errors, will serialize `custom` keys
}
})The serialize array also applies to any child logger serializers. Unlike server pino, the serializers apply to every object passed to the logger method. If the asObject option is true, the serializers apply to the first object (as in server pino).
transmit (Object)
An object with send and level properties for remotely recording log messages.
The transmit.level property specifies the minimum level (inclusive) of when the send function should be called. If not supplied, the send function is called based on the main logging level (defaulting to info).
The send function is passed the level of the log message and a logEvent object:
{
ts = Number,
messages = Array,
bindings = Array,
level: { label = String, value = Number}
}ts: Unix epoch timestamp in milliseconds, taken from the moment the logger method is called.messages: All arguments passed to the logger method (e.g.logger.info('a', 'b', 'c')results in['a', 'b', 'c']).bindings: Represents each child logger (if any) and its relevant bindings. Forlogger.child({a: 1}).child({b: 2}).info({c: 3}), the bindings array holds[{a: 1}, {b: 2}]and messages holds[{c: 3}]. Bindings are ordered by position in the child hierarchy (lowest index = top).level: Holds the label (e.g.info) and the corresponding numerical value (e.g.30).
Serializers are always applied to messages and bindings in the logEvent object, even when they are not applied to console output.
const pino = require('pino')({
browser: {
transmit: {
level: 'warn',
send: function (level, logEvent) {
if (level === 'warn') {
// maybe send the logEvent to a separate endpoint
// or maybe analyze the messages further before sending
}
// we could also use the `logEvent.level.value` property to determine
// numerical value
if (logEvent.level.value >= 50) { // covers error and fatal
// send the logEvent somewhere
}
}
}
}
})disabled (Boolean)
Disables logging in the browser when set to true. By default it is false.
const pino = require('pino')({browser: {disabled: true}})Notes
- By default, Pino maps to Log4j-style
consolemethods in the browser. asObjectis automaticallytruewhenwriteis set.- Serializers are ignored by default in the browser but are always applied within
transmitlogEvent objects. reportCalleris best-effort and depends on JavaScript Error stack format, which varies across engines.- The
transmit.sendfunction is the primary mechanism for remotely recording browser logs.
Related
- API options
- Child loggers
Child Loggers
Child loggers allow adding persistent bindings (key-value pairs) to every log line produced within a particular context, such as a module, request, or component. They are created from a parent logger and inherit its configuration while adding their own properties.
Creating a Child Logger
Use .child() on an existing logger to create a child with bound properties:
'use strict'
// imports a pino logger instance of `require('pino')()`
const parentLogger = require('./lib/logger')
const log = parentLogger.child({module: 'foo'})
function doSomething () {
log.info('doSomething invoked')
}
module.exports = {
doSomething
}Every log line from log will include "module":"foo" automatically.
Cost of Child Logging
Child logger creation is fast:
benchBunyanCreation*10000: 564.514ms
benchBoleCreation*10000: 283.276ms
benchPinoCreation*10000: 258.745ms
benchPinoExtremeCreation*10000: 150.506msLogging through a child logger has little performance penalty:
benchBunyanChild*10000: 556.275ms
benchBoleChild*10000: 288.124ms
benchPinoChild*10000: 231.695ms
benchPinoExtremeChild*10000: 122.117msLogging via the child of a child logger also has negligible overhead:
benchBunyanChildChild*10000: 559.082ms
benchPinoChildChild*10000: 229.264ms
benchPinoExtremeChildChild*10000: 127.753msDuplicate Keys Caveat
Naming conflicts can arise between child loggers and children of child loggers. Pino resolves the conflict by including both keys in the JSON output:
const pino = require('pino')
pino(pino.destination('./my-log'))
.child({a: 'property'})
.child({a: 'prop'})
.info('howdy')$ cat my-log
{"pid":95469,"hostname":"MacBook-Pro-3.home","level":30,"msg":"howdy","time":1459534114473,"a":"property","a":"prop"}The sub-child's properties appear after the parent child properties. When parsed with JSON.parse, the conflicting namespace holds the final value assigned to it:
$ cat my-log | node -e "process.stdin.once('data', (line) => console.log(JSON.stringify(JSON.parse(line))))"
{"pid":95469,"hostname":"MacBook-Pro-3.home","level":30,"msg":"howdy","time":"2016-04-01T18:08:34.473Z","a":"prop"}The conflict is resolved by taking the last value, which aligns with Bunyan's child logging behavior.
Notes
- Child loggers inherit the parent's configuration (level, serializers, etc.) and add their own bindings.
- One of Pino's performance tricks is building strings instead of building objects and stringifying them, which is why duplicate keys between parents and children appear in raw output.
- Be conscious of namespace conflicts with child loggers, especially considering your expected log processing approach.
- Some JSON parsers may handle duplicate keys differently than
JSON.parse.
Related
- Transports
- Browser API
- API options
Diagnostics
Pino provides Node.js tracing channel events that allow insight into the internal workings of the library during log serialization.
Tracing Channel Events
tracing:pino_asJson:start
Emitted when the final serialization process of logs is started.
Payload fields:
| Field | Description |
|---|---|
instance | The Pino instance associated with the function |
arguments | The arguments passed to the function |
tracing:pino_asJson:end
Emitted at the end of the final serialization process.
Payload fields:
| Field | Description |
|---|---|
instance | The Pino instance associated with the function |
arguments | The arguments passed to the function |
result | The finalized, newline-delimited log line as a string |
Notes
- These events use the Node.js
diagnostics_channelTracingChannel API. - The
startevent fires before serialization and theendevent fires after, providing timing and inspection capabilities. - The
resultfield in theendevent contains the complete, ready-to-write log line.
Related
- Transports
- API options
Pretty Printing
By default, Pino log lines are newline-delimited JSON (NDJSON), which is ideal for production usage and long-term storage but not for development environments. Pino logs can be prettified using the pino-pretty module.
Setup
1. Install pino-pretty as a separate dependency:
npm install pino-pretty2. Instantiate the logger with the transport.target option set to 'pino-pretty':
const pino = require('pino')
const logger = pino({
transport: {
target: 'pino-pretty'
},
})
logger.info('hi')3. The transport option can also include a pino-pretty options object:
const pino = require('pino')
const logger = pino({
transport: {
target: 'pino-pretty',
options: {
colorize: true
}
}
})
logger.info('hi')Flush Limitations
The logger.flush() method does not work when using pino-pretty because:
1. pino-pretty runs in a separate worker thread via the transport mechanism. 2. When logger.flush() is called, it flushes the SonicBoom destination in the main thread, but logs remain queued in the thread-stream worker. 3. The flush operation never propagates through to the worker thread where the pretty printer processes output.
This means formatted logs may not appear immediately even with logger.flush().
Notes
pino-prettyis intended for development environments, not production.- Install it as a separate dependency (
npm install pino-pretty). - Use
options.colorizeto enable/disable colored output. logger.flush()only ensures the main thread buffer is written, not the formatted output.
Related
- Transports
- Asynchronous logging
- API options
Features
| Name | Description | Path |
|---|---|---|
| Asynchronous Logging | Asynchronous logging enables the minimum overhead of Pino by buffering log messages and writing them in larger chunks… | ./asynchronous.md |
| Browser API | Pino is compatible with browserify for browser-side usage, making it useful for isomorphic/universal JavaScript… | ./browser.md |
| Child Loggers | Child loggers allow adding persistent bindings to every log line produced within a particular context, such as a… | ./child-loggers.md |
| Diagnostics | Pino provides Node.js tracing channel events that allow insight into the internal workings of the library during… | ./diagnostics.md |
| Pretty Printing | By default, Pino log lines are newline-delimited JSON (NDJSON), which is ideal for production usage and long-term… | ./pretty-printing.md |
| Redaction | Pino supports redacting sensitive information from log output using the redact option. Paths to keys containing… | ./redaction.md |
| Transports | Pino transports are used for both transmitting and transforming log output. Pino's log generation approach reduces… | ./transports.md |
Redaction
Pino supports redacting sensitive information from log output using the redact option. Paths to keys containing sensitive data are specified, and their values are replaced with a censor string, removed entirely, or handled with a custom censor function.
Basic Usage
Supply paths to keys that hold sensitive data using the redact option as an array:
const logger = require('pino')({
redact: ['key', 'path.to.key', 'stuff.thats[*].secret', 'path["with-hyphen"]']
})
logger.info({
key: 'will be redacted',
path: {
to: {key: 'sensitive', another: 'thing'}
},
stuff: {
thats: [
{secret: 'will be redacted', logme: 'will be logged'},
{secret: 'as will this', logme: 'as will this'}
]
}
})Output:
{"level":30,"time":1527777350011,"pid":3186,"hostname":"Davids-MacBook-Pro-3.local","key":"[Redacted]","path":{"to":{"key":"[Redacted]","another":"thing"}},"stuff":{"thats":[{"secret":"[Redacted]","logme":"will be logged"},{"secret":"[Redacted]","logme":"as will this"}]}}Object Form with Custom Censor
The redact option can also take an object with paths, censor, and remove properties for finer control:
const logger = require('pino')({
redact: {
paths: ['key', 'path.to.key', 'stuff.thats[*].secret'],
censor: '**GDPR COMPLIANT**'
}
})
logger.info({
key: 'will be redacted',
path: {
to: {key: 'sensitive', another: 'thing'}
},
stuff: {
thats: [
{secret: 'will be redacted', logme: 'will be logged'},
{secret: 'as will this', logme: 'as will this'}
]
}
})Output:
{"level":30,"time":1527778563934,"pid":3847,"hostname":"Davids-MacBook-Pro-3.local","key":"**GDPR COMPLIANT**","path":{"to":{"key":"**GDPR COMPLIANT**","another":"thing"}},"stuff":{"thats":[{"secret":"**GDPR COMPLIANT**","logme":"will be logged"},{"secret":"**GDPR COMPLIANT**","logme":"as will this"}]}}Removing Keys Entirely
The redact.remove option removes the key and value from output entirely:
const logger = require('pino')({
redact: {
paths: ['key', 'path.to.key', 'stuff.thats[*].secret'],
remove: true
}
})
logger.info({
key: 'will be redacted',
path: {
to: {key: 'sensitive', another: 'thing'}
},
stuff: {
thats: [
{secret: 'will be redacted', logme: 'will be logged'},
{secret: 'as will this', logme: 'as will this'}
]
}
})Output:
{"level":30,"time":1527782356751,"pid":5758,"hostname":"Davids-MacBook-Pro-3.local","path":{"to":{"another":"thing"}},"stuff":{"thats":[{"logme":"will be logged"},{"logme":"as will this"}]}}Path Syntax
The syntax for paths conforms to standard ECMAScript path lookups with two additions:
- Paths may start with bracket notation
- Paths may contain the asterisk
*to denote a wildcard - Paths are case sensitive
Valid path examples:
| Path | Description |
|---|---|
a.b.c | Dot notation |
a["b-c"].d | Bracket notation for hyphenated keys |
["a-b"].c | Path starting with bracket notation |
a.b.* | Wildcard matching all keys under a.b |
a[*].b | Wildcard matching all array elements |
Overhead
Pino's redaction is built on top of `fast-redact`:
- Paths without wildcards add about 2% overhead to
JSON.stringify. - With a single redacted path in pino, overhead is within noise (not a measurable bottleneck).
- Wildcard redaction carries non-trivial cost relative to explicit keys (approximately 50% in a case where four keys are redacted across two objects).
Safety
The redact option is intended as an initialization-time configuration option. Path strings must not originate from user input. The fast-redact module uses a VM context to syntax-check the paths, so user input should never be combined with this approach.
Notes
- Default censor value is
[Redacted]when using the array form. - Use the object form (
paths,censor,remove) for custom censor strings or key removal. - Wildcard paths (
*) are powerful but carry higher overhead than explicit paths. - Path strings must never come from user input for security reasons.
Related
- API options
- Child loggers
Transports
Pino transports are used for both transmitting and transforming log output. Pino's log generation approach reduces logging impact on applications to the absolute minimum and gives greater flexibility in how logs are processed and stored. It is recommended that any log transformation or transmission is performed in a separate thread or process.
v7+ Transports
From Pino v7 and upwards, transports operate inside a Worker Thread and can be configured via the options object passed to pino on initialization. They always operate asynchronously (unless options.sync is set to true) and logs are flushed as quickly as possible.
A transport is a module that exports a default function returning a writable stream:
import { createWriteStream } from 'node:fs'
export default (options) => {
return createWriteStream(options.destination)
}Setting Up a Transport
Use pino.transport to create a transport stream and pass it to pino:
const pino = require('pino')
const transport = pino.transport({
target: '/absolute/path/to/my-transport.mjs'
})
pino(transport)The transport code executes in a separate worker thread. ESM files are supported even if the project is written in CJS.
Async Transport Functions
The exported function can be async, allowing early errors if the transport could not be opened:
import fs from 'node:fs'
import { once } from 'events'
export default async (options) => {
const stream = fs.createWriteStream(options.destination)
await once(stream, 'open')
return stream
}Passing Options to Transports
Options are serialized and injected into the transport worker thread. This means the options object can only contain types supported by the Structured Clone Algorithm.
const pino = require('pino')
const transport = pino.transport({
target: 'some-file-transport',
options: { destination: '/dev/null' }
})
pino(transport)Multiple Transports
Send logs to multiple destinations with per-target level filtering:
const pino = require('pino')
const transport = pino.transport({
targets: [
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
{ target: 'some-file-transport', options: { destination: '/dev/null' } }
]
})
pino(transport)How Level Filtering Works
Single target (or single pipeline)
Main thread Worker thread
──────────────────────────────────────────── ─────────────────────────────────
logger.debug()/info()/... transport target (stream)
│ ▲
▼ │
logger.level gate (enabled methods) ──ThreadStream─────┘Only logger.level decides what is emitted. transport.level is not used.
Multiple targets
Main thread Worker thread
──────────────────────────────────────────── ─────────────────────────────────
logger.debug()/info()/... pino.multistream
│ (per-target level filter)
▼ │
logger.level gate (enabled methods) ──ThreadStream────┼──> target #1 (level: ...)
├──> target #2 (level: ...)
└──> target #N (default: info)logger.levelis the first gate.- Each
targets[i].levelis the second gate. - Missing
targets[i].leveldefaults toinfo.
If you need debug (or lower) logs to reach targets, set logger.level low enough AND set level on each target that should receive those messages.
Custom Levels with Multiple Transports
Pass custom levels when using more than one transport:
const pino = require('pino')
const transport = pino.transport({
targets: [
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
{ target: 'some-file-transport', options: { destination: '/dev/null' } }
],
levels: { foo: 35 }
})
pino(transport)Deduplication
Use the dedupe option to send logs only to the stream with the higher level:
const pino = require('pino')
const transport = pino.transport({
targets: [
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
{ target: 'some-file-transport', options: { destination: '/dev/null' } }
],
dedupe: true
})
pino(transport)Synchronous Transport
Pass sync: true to transport options for synchronous logging:
const pino = require('pino')
const transport = pino.transport({
targets: [
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
],
dedupe: true,
sync: true,
})
pino(transport)Using Transports with --import or --require Preloads
Pino transports work correctly when loaded via Node.js preload flags. Pino automatically detects the preload phase and filters out preload flags from the transport worker's execArgv to prevent infinite worker spawning.
// preload.mjs
import pino from 'pino'
export const logger = pino({
transport: {
target: 'pino-pretty'
}
})node --import=./preload.mjs app.jsWriting a Transport
The module pino-abstract-transport provides a simple utility to parse each line. Its usage is highly recommended.
Using Async Iterators (ESM)
import build from 'pino-abstract-transport'
import SonicBoom from 'sonic-boom'
import { once } from 'events'
export default async function (opts) {
const destination = new SonicBoom({ dest: opts.destination || 1, sync: false })
await once(destination, 'ready')
return build(async function (source) {
for await (let obj of source) {
const toDrain = !destination.write(obj.msg.toUpperCase() + '\n')
if (toDrain) {
await once(destination, 'drain')
}
}
}, {
async close (err) {
destination.end()
await once(destination, 'close')
}
})
}Using Node.js Streams (CommonJS)
'use strict'
const build = require('pino-abstract-transport')
const SonicBoom = require('sonic-boom')
module.exports = function (opts) {
const destination = new SonicBoom({ dest: opts.destination || 1, sync: false })
return build(function (source) {
source.pipe(destination)
}, {
close (err, cb) {
destination.end()
destination.on('close', cb.bind(null, err))
}
})
}The close() function is needed to ensure the stream is closed and flushed when its callback is called or the returned promise resolves. Otherwise, log lines will be lost.
For consuming async iterators in batches, consider using the hwp library.
Writing to a Custom Transport and stdout
Use pino/file transport with destination: 1 to output to STDOUT alongside a custom transport:
const transports = [
{
target: 'pino/file',
options: { destination: 1 } // this writes to STDOUT
},
{
target: 'my-custom-transport',
options: { someParameter: true }
}
]
const logger = pino(pino.transport({ targets: transports }))Creating a Transport Pipeline
A transport can return a Transform stream for use in a pipeline:
import build from 'pino-abstract-transport'
import { pipeline, Transform } from 'node:stream'
export default async function (options) {
return build(function (source) {
const myTransportStream = new Transform({
autoDestroy: true,
objectMode: true,
transform (chunk, enc, cb) {
chunk.service = 'pino'
this.push(`${JSON.stringify(chunk)}\n`)
cb()
}
})
pipeline(source, myTransportStream, () => {})
return myTransportStream
}, {
enablePipelining: true
})
}Then pipeline them:
import pino from 'pino'
const logger = pino({
transport: {
pipeline: [{
target: './my-transform.js'
}, {
target: 'pino/file',
options: { destination: 1 }
}]
}
})
logger.info('hello world')There is no "default" destination for a pipeline -- a terminating target (a Writable stream) is required.
TypeScript Compatibility
Node.js 22+ with Type Stripping
Starting with Node.js 22.6.0, you can use TypeScript transports directly with native type stripping:
// my-transport.mts
import { createWriteStream } from 'node:fs'
export default (options: { destination: string }) => {
return createWriteStream(options.destination)
}// app.js
const pino = require('pino')
const transport = pino.transport({
target: './my-transport.mts',
options: { destination: '/path/to/file' }
})
pino(transport)- Node.js 22.6.0 - 22.17.x: Use the
--experimental-strip-typesflag. - Node.js 22.18.0+ and 24.0.0+: Type stripping is enabled by default.
- Use the
.mtsextension for TypeScript ESM modules to ensure proper module resolution.
Using TS-Node (Legacy)
For older Node.js versions, TS-Node can execute TypeScript without explicit transpilation, but with caveats:
- ES imports are not fully supported for "pure" TypeScript code.
- Only TS-Node is supported (not other loaders like TSM).
- May be problematic on Windows systems.
Transpiled TypeScript (Recommended for Production)
For maximum compatibility and production use, transpile TypeScript transports to JavaScript before deployment.
Notable Built-in Transports
pino/file
Routes logs to a file or file descriptor:
const pino = require('pino')
const transport = pino.transport({
target: 'pino/file',
options: { destination: '/path/to/file' }
})
pino(transport)Options:
destination: File path or file descriptor number. Defaults to1(STDOUT). Use2for STDERR.mkdir: Set totrueto create the directory if it does not exist.append: Set tofalseto truncate the file on open (default istrue, appending).
The difference between pino/file and pino.destination is that pino/file sets up pino.destination in a worker thread.
pino-pretty
Prettifies logs for development:
const pino = require('pino')
const transport = pino.transport({
target: 'pino-pretty',
options: { destination: 1 } // use 2 for stderr
})
pino(transport)Known Transports
Pino v7+ Compatible
| Transport | Description |
|---|---|
@axiomhq/pino | Official Axiom transport using axiom-js |
@logtail/pino | Forwards logs to Logtail by Better Stack |
@macfja/pino-fingers-crossed | Holds logs until a threshold level is reached |
@openobserve/pino-openobserve | Sends logs to OpenObserve |
datadog-logger-integrations | Forwards log events to Datadog |
pino-airbrake-transport | Forwards log events to Airbrake |
pino-axiom | Forwards logs to Axiom |
pino-discord-webhook | Forwards log events to Discord webhook |
pino-elasticsearch | Uploads log lines in bulk to Elasticsearch |
pino-hana | Saves logs to SAP HANA database |
pino-logflare | Sends logs to Logflare |
pino-logfmt | Formats logs into logfmt format |
pino-loki | Forwards logs to Grafana Loki |
pino-opentelemetry-transport | Forwards logs to OpenTelemetry collector |
pino-pretty | Prettifies log output for development |
pino-roll | Automatically rolls log files by size or time |
pino-seq-transport | Forwards log events to Seq |
pino-sentry-transport | Forwards log events to Sentry |
pino-slack-webhook | Forwards log events to Slack webhook |
pino-telegram-webhook | Sends messages to Telegram |
pino-yc-transport | Writes to Yandex Cloud Logging |
Sentry Native SDK Integration
As an alternative to pino-sentry-transport, Sentry's Node.js SDK (v10.18.0+) provides a native pinoIntegration:
const Sentry = require('@sentry/node')
const pino = require('pino')
Sentry.init({
dsn: 'https://******@sentry.io/12345',
enableLogs: true,
integrations: [Sentry.pinoIntegration()],
})
const logger = pino()
logger.info('This log will be captured by Sentry')Legacy Transports
Legacy transports (pre-v7) operate in a separate process and consume Pino logs from stdin:
| Transport | Description |
|---|---|
pino-applicationinsights | Forwards logs to Azure Application Insights |
pino-azuretable | Forwards logs to Azure Table Storage |
pino-cloudwatch | Buffers and forwards logs to Amazon CloudWatch |
pino-couch | Uploads each log line as a CouchDB document |
pino-datadog | Forwards logs to DataDog via API |
pino-gelf | Transforms logs to GELF format for Graylog |
pino-http-send | Batches logs and sends to a specified URL |
pino-kafka | Sends logs to Apache Kafka |
pino-logdna | Sends logs to LogDNA |
pino-mq | Sends logs over a message bus |
pino-mysql | Loads logs into MySQL/MariaDB |
pino-papertrail | Forwards logs to Papertrail via UDP |
pino-pg | Stores logs into PostgreSQL |
pino-redis | Loads logs into Redis |
pino-sentry | Loads logs into Sentry |
pino-seq | Forwards logs to Seq |
pino-socket | Forwards logs to IPv4 UDP or TCP socket |
pino-stackdriver | Forwards logs to Google Stackdriver |
pino-syslog | Converts logs to RFC3164 syslog format |
pino-websocket | Forwards log lines to a websocket server |
Legacy transports are used with shell piping:
node my-app.js | node my-transport-process.jsAsynchronous Startup
Transports boot asynchronously. Calling process.exit() before the transport starts will cause logs to not be delivered. Use the ready event:
const pino = require('pino')
const transport = pino.transport({
targets: [
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
{ target: 'some-file-transport', options: { destination: '/dev/null' } }
]
})
const logger = pino(transport)
logger.info('hello')
transport.on('ready', function () {
process.exit(0)
})Communication between Pino and Transports
Pino uses thread-stream to create a stream for transports. When a stream is created with thread-stream, it spawns a worker (an independent JavaScript execution thread).
Error Messages
When a transport worker emits an error event, the worker's error and unhandledRejection listeners send the error message to the main thread. Pino then re-emits the error, which can be caught with a listener:
const transport = pino.transport({
target: './transport.js'
})
transport.on('error', err => {
console.error('error caught', err)
})
const log = pino(transport)Notes
- v7+ transports run in a Worker Thread, while legacy transports run in separate processes.
- Transport options must be serializable via the Structured Clone Algorithm.
- Always include a
close()function in custom transports to prevent lost log lines. - Use
pino-abstract-transportwhen writing custom transports for reliable line parsing. - Pipeline transports require a terminating
Writablestream target.
Related
- Pretty printing
- Asynchronous logging
- API options
Avoid Message Conflict
When a log is written like log.info({ msg: 'a message' }, 'another message'), the final output JSON will have "msg":"another message" and the 'a message' string will be lost. The logMethod hook can be used to overcome this.
Using the logMethod Hook
Use the logMethod hook to detect and preserve conflicting msg properties:
'use strict'
const log = require('pino')({
level: 'debug',
hooks: {
logMethod (inputArgs, method) {
if (inputArgs.length === 2 && inputArgs[0].msg) {
inputArgs[0].originalMsg = inputArgs[0].msg
}
return method.apply(this, inputArgs)
}
}
})
log.info('no original message')
log.info({ msg: 'mapped to originalMsg' }, 'a message')
// {"level":30,"time":1596313323106,"pid":63739,"hostname":"foo","msg":"no original message"}
// {"level":30,"time":1596313323107,"pid":63739,"hostname":"foo","msg":"a message","originalMsg":"mapped to originalMsg"}Notes
- The conflict occurs because Pino uses
msgas the default message key - When both an object property
msgand a string message are provided, the string message wins - The
logMethodhook intercepts log calls before they are processed, allowing you to rename the conflicting key - In the example,
msgin the object is renamed tooriginalMsgso both values are preserved
Related
- duplicate-keys
- options
Best Performance for Logging to stdout
stdout への直接出力で最高パフォーマンスを得るための設定ガイド。
デフォルト設定が最速
const log = require('pino')()デフォルト設定(カスタムトランスポートやその他の設定なし)が、通常、stdout への最速ログ出力を実現する。
パフォーマンスに影響する設定
| 設定 | 影響 |
|---|---|
transport オプション | ワーカースレッドのオーバーヘッドが追加される |
formatters | 各ログ行で関数呼び出しが発生する |
serializers | オブジェクトのシリアライズ処理が追加される |
redact | パス探索とマスキング処理のコストがかかる |
pino.multistream() | 複数ストリームへの分岐オーバーヘッド |
推奨パターン
インプロセストランスポートではなく、外部パイプでログを処理する:
# 開発環境: 整形表示
node app.js | pino-pretty
# 本番環境: ファイル出力
node app.js > app.log
# 本番環境: 外部トランスポート
node app.js | pino-transportファイル出力が必要な場合
pino.destination() はバッファリングされた書き込みを提供する:
const pino = require('pino')
const logger = pino(pino.destination('./my-log'))非同期モードでさらに高速化:
const logger = pino(pino.destination({ dest: './my-log', sync: false }))Notes
- カスタム設定を追加するほどオーバーヘッドが増える
- より広範なロギング要件がある場合のみカスタム設定を使用すること
pino.destinationはデフォルトのprocess.stdoutより高速(SonicBoom ベース)
Related
- Transports
- Asynchronous Logging
- pino.destination()
- options
Duplicate Keys
子ロガーのバインディングとログ呼び出しのオブジェクトで同名キーが存在する場合の挙動。
発生条件
子ロガーに設定したバインディングと、ログメソッドに渡すオブジェクトに同じキーがあると、JSON 出力に重複キーが含まれる。
const pino = require('pino')
pino(pino.destination('./my-log'))
.child({ a: 'property' })
.child({ a: 'prop' })
.info('howdy')出力される JSON:
{"pid":95469,"hostname":"MacBook-Pro-3.home","level":30,"msg":"howdy","time":1459534114473,"a":"property","a":"prop"}2つの a キーが存在する。子のプロパティは親のプロパティの後に出力される。
解決の挙動
JSON.parse で解析すると、最後に出現した値が採用される:
$ cat my-log | node -e "process.stdin.once('data', (line) => console.log(JSON.stringify(JSON.parse(line))))"
{"pid":95469,"hostname":"MacBook-Pro-3.home","level":30,"msg":"howdy","time":"2016-04-01T18:08:34.473Z","a":"prop"}これは Bunyan の子ロガーと同じ挙動(最後の値が勝つ)。
Notes
- Pino はパフォーマンスのためにオブジェクトを構築・stringify せず文字列を直接組み立てるため、重複キーが出力される
- 別の JSON パーサーを使用する場合、重複キーの処理が異なる可能性がある
- 子ロガー作成時にキー名の衝突に注意すること
Related
- Child Loggers
- logger.child()
- Avoid Message Conflict
Mapping Pino Log Levels to Google Cloud Logging
Google Cloud Logging (formerly Stackdriver) uses severity levels instead of numeric log levels. Without configuration, all Pino logs may appear as INFO level in Google Cloud Logging. Google Cloud Logging also prefers log data in a message key instead of Pino's default msg key.
Manual Configuration
Use the formatters and messageKey options to map Pino levels to Google Cloud Logging severity levels:
const pino = require('pino')
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity
const PinoLevelToSeverityLookup = {
trace: 'DEBUG',
debug: 'DEBUG',
info: 'INFO',
warn: 'WARNING',
error: 'ERROR',
fatal: 'CRITICAL',
};
const defaultPinoConf = {
messageKey: 'message',
formatters: {
level(label, number) {
return {
severity: PinoLevelToSeverityLookup[label] || PinoLevelToSeverityLookup['info'],
level: number,
}
}
},
}
module.exports = function createLogger(options) {
return pino(Object.assign({}, options, defaultPinoConf))
}Using @google-cloud/pino-logging-gcp-config
A dedicated library is available for configuring Pino for Google Cloud Structured Logging: `@google-cloud/pino-logging-gcp-config`.
This library provides:
- Converts Pino log levels to Google Cloud Logging severity levels
- Uses
messageinstead ofmsgfor the message key - Adds a millisecond-granularity timestamp in the structure recognized by Google Cloud Logging (e.g.,
"timestamp":{"seconds":1445470140,"nanos":123000000}) - Adds a sequential
insertIdto ensure log messages with identical timestamps are ordered correctly - Logs including an
Errorobject have thestack_traceproperty set so that the error is forwarded to Google Cloud Error Reporting - Includes a
ServiceContextobject in the logs for Google Cloud Error Reporting, auto-detected from the environment if not specified - Maps the OpenTelemetry properties
span_id,trace_id, andtrace_flagsto the equivalent Google Cloud Logging fields
Notes
- Without configuration, Google Cloud Logging treats all Pino logs as INFO
- The
messageKeymust be set tomessagefor Google Cloud Logging compatibility - The
@google-cloud/pino-logging-gcp-configlibrary handles all mapping automatically and is the recommended approach - Both
traceanddebugPino levels map to Google Cloud'sDEBUGseverity
Related
- log-levels-as-labels
- ecosystem
- options
Log Filtering
The Pino philosophy advocates using common, preexisting system utilities for filtering logs.
Using grep
Filter logs by level value using grep:
$ # View all "INFO" level logs
$ node app.js | grep '"level":30'Using jq
Filter logs using the JSON processor jq:
$ # View all "ERROR" level logs
$ node app.js | jq 'select(.level == 50)'Notes
- Pino outputs structured JSON, making it easy to filter with standard tools
grepworks well for simple level-based filtering using the numeric level valuesjqprovides more powerful JSON-aware filtering with selectors- Default Pino log levels: trace=10, debug=20, info=30, warn=40, error=50, fatal=60
Related
- log-levels-as-labels
- options
Log Levels as Labels Instead of Numbers
Pino's default mode is to print the numeric level value instead of the string name, since log lines are meant to be parsable. However, you can configure Pino to output level labels using the formatters option.
Using the formatters Option
Use the formatters option with a level function to print the string name instead of the level value:
const pino = require('pino')
const log = pino({
formatters: {
level: (label) => {
return {
level: label
}
}
}
})
log.info('message')
// {"level":"info","time":1661632832200,"pid":18188,"hostname":"foo","msg":"message"}Alternative Approaches
Although the formatters approach works, these alternatives are recommended if possible:
1. Transport-based: Use a transport like `pino-text-level-transport` if the only change desired is the level name. 2. Prettifier: Use a prettifier like `pino-pretty` to make the logs human-friendly.
Notes
- By default, Pino uses numeric levels (trace=10, debug=20, info=30, warn=40, error=50, fatal=60)
- The
formatters.levelfunction receives the label string and can return any object shape - Transports or prettifiers are preferred over in-process formatting for better separation of concerns
Related
- log-filtering
- options
- ecosystem
Log Rotation
Pino recommends using a separate tool for log rotation, specifically logrotate.
Using logrotate
Given that logs are output to a file:
$ node server.js > /var/log/myapp.logConfigure logrotate by adding the following to /etc/logrotate.d/myapp:
/var/log/myapp.log {
su root
daily
rotate 7
delaycompress
compress
notifempty
missingok
copytruncate
}copytruncate Caveat
The copytruncate configuration has a very slight possibility of lost log lines due to a gap between copying and truncating -- the truncate may occur after additional lines have been written.
To perform log rotation without copytruncate, see the reopening log files approach.
Notes
- Use an external tool like
logrotaterather than handling rotation in-process copytruncateis the simplest approach but has a small risk of lost lines- For zero-loss rotation, use
pino.destinationwith signal-based reopening instead
Related
- reopening-log-files
- saving-to-multiple-files
- best-performance-stdout
Long Term Support
Pino provides Long Term Support (LTS) according to a defined schedule, with major releases supported for a minimum of six months and security updates continuing for an additional six months after the next major release.
LTS Policy
1. Major releases (the "X" in semantic versioning X.Y.Z) are supported for a minimum period of six months from their release date. Release dates can be found at the Pino releases page.
2. Security updates for major releases continue for an additional six months from the release of the next major release. After this period, security fixes will still be reviewed and released as long as they are provided by the community and do not violate other constraints (e.g., minimum supported Node.js version).
3. Node.js compatibility: Major releases are tested and verified against all Node.js release lines supported by the Node.js LTS policy within the LTS period of that given Pino release line. Only the latest Node.js release of a given line is supported.
A "month" is defined as 30 consecutive days.
Security Releases and Semver
As a consequence of providing long-term support for major releases, there are occasions where breaking changes are released as a minor version release. Such changes are always noted in the release notes.
To avoid automatically receiving breaking security updates, use the tilde (~) range qualifier. For example, to get patches for the 6.1 release and avoid automatically updating beyond it, specify the dependency as "pino": "~6.1.x". This will leave the application vulnerable, so use with caution.
Version Schedule
| Version | Release Date | End Of LTS Date | Node.js |
|---|---|---|---|
| 9.x | 2024-04-26 | TBD | 18, 20, 22 |
| 8.x | 2022-06-01 | 2024-10-26 | 14, 16, 18, 20 |
| 7.x | 2021-10-14 | 2023-06-01 | 12, 14, 16 |
| 6.x | 2020-03-07 | 2022-04-14 | 10, 12, 14, 16 |
CI Tested Operating Systems
Pino uses GitHub Actions for CI testing:
| OS | YAML Workflow Label | Node.js |
|---|---|---|
| Linux | ubuntu-latest | 18, 20, 22 |
| Windows | windows-latest | 18, 20, 22 |
| MacOS | macos-latest | 18, 20, 22 |
Notes
- Pino 9.x is the current actively supported version
- Only the latest Node.js release within a given major line is officially supported
- Breaking changes may appear in minor releases due to the LTS security update policy
- Use tilde (
~) version ranges inpackage.jsonif you need to avoid unexpected breaking security patches
Related
- options
Pino with debug
The popular debug module is used in many modules across the ecosystem. The pino-debug module can capture calls to debug loggers and run them through Pino instead, resulting in a 10x (20x in asynchronous mode) performance improvement.
Quick Setup
Install pino-debug and preload it with the -r flag, enabling any debug logs with the DEBUG environment variable:
$ npm i pino-debug
$ DEBUG=* node -r pino-debug app.jsFine-Grain Control
pino-debug offers fine-grain control to map specific debug namespaces to Pino log levels. See the pino-debug documentation for more details.
Notes
pino-debugwrapsdebugoutput in JSON via Pino, adding structure and improving performance- The
-r(require) flag preloadspino-debugbefore the application starts DEBUG=*enables all debug namespaces; narrow this to specific namespaces as needed- Performance gains come from Pino's optimized serialization even though additional data is logged
Related
- ecosystem
- log-filtering
- options
help
| Name | Description | Path |
|---|---|---|
| Avoid Message Conflict | When a log is written like `log.info({ msg: 'a message' }, 'another…' | avoid-message-conflict.md |
| Best Performance for Logging to stdout | stdout への直接出力で最高パフォーマンスを得るための設定ガイド。 | best-performance-stdout.md |
| Duplicate Keys | 子ロガーのバインディングとログ呼び出しのオブジェクトで同名キーが存在する場合の挙動。 | duplicate-keys.md |
| Mapping Pino Log Levels to Google Cloud Logging | Google Cloud Logging (formerly Stackdriver) uses severity levels instead of numeric log levels… | google-cloud-logging.md |
| Log Filtering | The Pino philosophy advocates using common, preexisting system utilities for filtering logs. | log-filtering.md |
| Log Levels as Labels Instead of Numbers | Pino's default mode is to print the numeric level value instead of the string name, since log… | log-levels-as-labels.md |
| Log Rotation | Pino recommends using a separate tool for log rotation, specifically logrotate. | log-rotation.md |
| Long Term Support | Pino provides Long Term Support (LTS) according to a defined schedule, with major releases… | lts.md |
| Pino with debug | The popular debug module is used in many modules across the ecosystem. | pino-with-debug.md |
| Reopening Log Files | In cases where a log rotation tool does not offer copy-truncate capabilities, or where using… | reopening-log-files.md |
| Saving to Multiple Files | Pino supports writing logs to multiple destinations using pino.multistream. | saving-to-multiple-files.md |
| Testing | Pino provides a dedicated utility for testing log output. | testing.md |
| Transports and systemd | systemd makes it complicated to use pipes in services. | transports-and-systemd.md |
| Unicode and Windows Terminal | Pino uses sonic-boom to speed up logging, which internally uses fs.write to write log… | unicode-and-windows.md |
Reopening Log Files
In cases where a log rotation tool does not offer copy-truncate capabilities, or where using them is deemed inappropriate, pino.destination can reopen file paths after a file has been moved away.
Signal-Based Reopening
Set up a SIGUSR2 or SIGHUP signal handler that reopens the log file destination. Write the process PID to a well-known location so the log rotation tool knows where to send the signal.
// write the process pid to a well known location for later
const fs = require('node:fs')
fs.writeFileSync('/var/run/myapp.pid', process.pid)
const dest = pino.destination('/log/file')
const logger = require('pino')(dest)
process.on('SIGHUP', () => dest.reopen())logrotate Configuration with postrotate
The log rotation tool can be configured to send the signal to the process after a log rotation event has occurred:
/var/log/myapp.log {
su root
daily
rotate 7
delaycompress
compress
notifempty
missingok
postrotate
kill -HUP `cat /var/run/myapp.pid`
endscript
}Notes
- This approach avoids the small risk of lost log lines inherent in
copytruncate pino.destinationsupportsreopen()for this purpose- Write the PID file so the rotation tool can signal the correct process
SIGHUPis the conventional signal for log reopening
Related
- log-rotation
- saving-to-multiple-files
- options
Saving to Multiple Files
Pino supports writing logs to multiple destinations using pino.multistream.
Usage
See the pino.multistream API for details on directing log output to multiple files or streams simultaneously.
For example, you can direct different log levels to different streams:
const pino = require('pino')
var streams = [
{level: 'debug', stream: process.stdout},
{level: 'error', stream: process.stderr},
{level: 'fatal', stream: process.stderr}
]
const logger = pino({
name: 'my-app',
level: 'debug', // must be the lowest level of all streams
}, pino.multistream(streams))Notes
- Use
pino.multistreamto send logs to multiple destination streams - The
leveloption on the logger must be set to the lowest level among all streams - Writing to multiple streams has a performance cost compared to single-destination logging
- Pino's default log destination is singular
stdoutfor best performance
Related
- log-rotation
- reopening-log-files
- best-performance-stdout
- options
Testing
Pino provides a dedicated utility for testing log output.
pino-test
Use `pino-test` for verifying logs generated by the Pino logger in your test suites.
Notes
pino-testis the officially recommended utility for testing Pino log output- It is part of the core Pino ecosystem and maintained by the Pino team
- Use it to assert on log levels, messages, and structured data in tests
Related
- ecosystem
- options
Transports and systemd
systemd makes it complicated to use pipes in services. A subshell can be used to overcome this challenge.
Configuration
Use a subshell in the ExecStart directive to pipe Pino output through a transport:
ExecStart=/bin/sh -c '/path/to/node app.js | pino-transport'Notes
systemddoes not natively support piped commands in service definitions- Wrapping the command in
/bin/sh -c '...'allows pipes to work correctly - Replace
pino-transportwith the actual transport command (e.g.,pino-pretty,pino-elasticsearch)
Related
- transports
- ecosystem
- best-performance-stdout
Unicode and Windows Terminal
Pino uses sonic-boom to speed up logging, which internally uses fs.write to write log lines directly to a file descriptor. On Windows, Unicode output is not handled properly in the terminal.
The Problem
Both cmd.exe and PowerShell on Windows do not properly handle Unicode output. As a result, log lines that include UTF-8 characters may be visualized incorrectly.
The Fix
Configure the terminal to display Unicode characters correctly using chcp:
chcp 65001Execute this command in the terminal before running your application.
Notes
- This is a known limitation of Node.js on Windows, not specific to Pino
chcp 65001sets the terminal code page to UTF-8- The issue only affects terminal display; the actual log data written to files is correct
- sonic-boom uses
fs.writefor performance, which bypasses some of Node.js's stream encoding handling
Related
- best-performance-stdout
- options
Pino Ecosystem
A catalog of ecosystem modules that integrate with Pino, split into core modules maintained by the Pino team and community-maintained modules.
Core
Frameworks
- `express-pino-logger`: use Pino to log requests within Express.
- `koa-pino-logger`: use Pino to log requests within Koa.
- `restify-pino-logger`: use Pino to log requests within Restify.
- `rill-pino-logger`: use Pino as the logger for the Rill framework.
Utilities
- `pino-arborsculpture`: change log levels at runtime.
- `pino-caller`: add callsite to the log line.
- `pino-clf`: reformat Pino logs into Common Log Format.
- `pino-console`: adapter for the WHATWG Console spec.
- `pino-debug`: use Pino to interpret
debuglogs. - `pino-elasticsearch`: send Pino logs to an Elasticsearch instance.
- `pino-eventhub`: send Pino logs to an Azure Event Hub.
- `pino-filter`: filter Pino logs in the same fashion as the
debugmodule. - `pino-gelf`: reformat Pino logs into GELF format for Graylog.
- `pino-hapi`: use Pino as the logger for Hapi.
- `pino-http`: easily use Pino to log requests with the core
httpmodule. - `pino-http-print`: reformat Pino logs into traditional HTTPD style request logs.
- `pino-mongodb`: store Pino logs in a MongoDB database.
- `pino-multi-stream`: send logs to multiple destination streams (slow!).
- `pino-noir`: redact sensitive information in logs.
- `pino-pretty`: basic prettifier to make log lines human-readable.
- `pino-socket`: send logs to TCP or UDP destinations.
- `pino-std-serializers`: the core object serializers used within Pino.
- `pino-syslog`: reformat Pino logs to standard syslog format.
- `pino-tee`: pipe Pino logs into files based upon log levels.
- `pino-test`: a set of utilities for verifying logs generated by the Pino logger.
- `pino-toke`: reformat Pino logs according to a given format string.
Community
- `@google-cloud/pino-logging-gcp-config`: config helper and formatter to output Google Cloud Platform Structured Logging.
- `@newrelic/pino-enricher`: a log customization to add New Relic context for Logs In Context.
- `@sentry/node`: Sentry SDK with native
pinoIntegrationfor direct Pino instrumentation and error tracking. - `cloud-pine`: transport providing abstraction and compatibility with
@google-cloud/logging. - `cls-proxify`: integration of Pino and CLS. Useful for creating dynamically configured child loggers (e.g. with trace IDs) for each request.
- `crawlee-pino`: use Pino to log within Crawlee.
- `eslint-plugin-pino`: linting rules for Pino usage, primarily for preventing missing context in logs due to incorrect argument order.
- `pino-colada`: cute ndjson formatter for Pino.
- `pino-dev`: simple prettifier for Pino with built-in support for common ecosystem packages.
- `pino-fluentd`: send Pino logs to Elasticsearch, MongoDB, and many others via Fluentd.
- `pino-lambda`: log transport for CloudWatch support inside AWS Lambda.
- `pino-pretty-min`: a minimal prettifier inspired by the logrus logger.
- `pino-rotating-file`: a hapi-pino log transport for splitting logs into separate, automatically rotating files.
- `pino-tiny`: a tiny (and extensible) little log formatter for Pino.
Notes
- Core modules are maintained by the Pino team; community modules are independently maintained
pino-multi-streamis noted as slow -- preferpino.multistreamor transports where possiblepino-prettyis the standard prettifier for human-readable output during developmentpino-testis the recommended utility for testing Pino log output
Related
- web-frameworks
- transports
- testing
Integrations
| Name | Description | Path |
|---|---|---|
| Pino Ecosystem | A catalog of ecosystem modules that integrate with Pino, split into core modules maintained by the Pino team and community-maintained modules. | ecosystem.md |
| Web Frameworks | Pino has first-class support for the Node.js web framework ecosystem, since HTTP logging is a primary use case. | web-frameworks.md |
Web Frameworks
Pino has first-class support for the Node.js web framework ecosystem, since HTTP logging is a primary use case.
Pino with Fastify
Fastify comes bundled with Pino by default. Set Fastify's logger option to true and use request.log or reply.log for per-request log messages:
const fastify = require('fastify')({
logger: true
})
fastify.get('/', async (request, reply) => {
request.log.info('something')
return { hello: 'world' }
})
fastify.listen({ port: 3000 }, (err) => {
if (err) {
fastify.log.error(err)
process.exit(1)
}
})The logger option can also be set to an object, which will be passed through directly as the pino options object.
See the Fastify logging documentation for more information.
Pino with Express
npm install pino-httpconst app = require('express')()
const pino = require('pino-http')()
app.use(pino)
app.get('/', function (req, res) {
req.log.info('something')
res.send('hello world')
})
app.listen(3000)See the pino-http README for more info.
Pino with Hapi
npm install hapi-pino'use strict'
const Hapi = require('@hapi/hapi')
const Pino = require('hapi-pino');
async function start () {
// Create a server with a host and port
const server = Hapi.server({
host: 'localhost',
port: 3000
})
// Add the route
server.route({
method: 'GET',
path: '/',
handler: async function (request, h) {
// request.log is HAPI's standard way of logging
request.log(['a', 'b'], 'Request into hello world')
// a pino instance can also be used, which will be faster
request.logger.info('In handler %s', request.path)
return 'hello world'
}
})
await server.register(Pino)
// also as a decorated API
server.logger.info('another way for accessing it')
// and through Hapi standard logging system
server.log(['subsystem'], 'third way for accessing it')
await server.start()
return server
}
start().catch((err) => {
console.log(err)
process.exit(1)
})See the hapi-pino README for more info.
Pino with Restify
npm install restify-pino-loggerconst server = require('restify').createServer({name: 'server'})
const pino = require('restify-pino-logger')()
server.use(pino)
server.get('/', function (req, res) {
req.log.info('something')
res.send('hello world')
})
server.listen(3000)See the restify-pino-logger README for more info.
Pino with Koa
npm install koa-pino-loggerconst Koa = require('koa')
const app = new Koa()
const pino = require('koa-pino-logger')()
app.use(pino)
app.use((ctx) => {
ctx.log.info('something else')
ctx.body = 'hello world'
})
app.listen(3000)See the koa-pino-logger README for more info.
Pino with Node core http
npm install pino-httpconst http = require('http')
const server = http.createServer(handle)
const logger = require('pino-http')()
function handle (req, res) {
logger(req, res)
req.log.info('something else')
res.end('hello world')
}
server.listen(3000)See the pino-http README for more info.
Pino with Nest
npm install nestjs-pinoimport { NestFactory } from '@nestjs/core'
import { Controller, Get, Module } from '@nestjs/common'
import { LoggerModule, Logger } from 'nestjs-pino'
@Controller()
export class AppController {
constructor(private readonly logger: Logger) {}
@Get()
getHello() {
this.logger.log('something')
return `Hello world`
}
}
@Module({
controllers: [AppController],
imports: [LoggerModule.forRoot()]
})
class MyModule {}
async function bootstrap() {
const app = await NestFactory.create(MyModule)
await app.listen(3000)
}
bootstrap()See the nestjs-pino README for more info.
Pino with H3
npm install pino-http h3Save as server.mjs:
import { createApp, createRouter, eventHandler, fromNodeMiddleware } from "h3";
import pino from 'pino-http'
export const app = createApp();
const router = createRouter();
app.use(router);
app.use(fromNodeMiddleware(pino()))
app.use(eventHandler((event) => {
event.node.req.log.info('something')
return 'hello world'
}))
router.get(
"/",
eventHandler((event) => {
return { path: event.path, message: "Hello World!" };
}),
);Execute npx --yes listhen -w --open ./server.mjs.
See the pino-http README for more info.
Pino with Hono
npm install pino pino-http honoimport { serve } from '@hono/node-server';
import { Hono } from 'hono';
import { requestId } from 'hono/request-id';
import { pinoHttp } from 'pino-http';
const app = new Hono();
app.use(requestId());
app.use(async (c, next) => {
// pass hono's request-id to pino-http
c.env.incoming.id = c.var.requestId;
// map express style middleware to hono
await new Promise((resolve) => pinoHttp()(c.env.incoming, c.env.outgoing, () => resolve()));
c.set('logger', c.env.incoming.log);
await next();
});
app.get('/', (c) => {
c.var.logger.info('something');
return c.text('Hello Node.js!');
});
serve(app);See the pino-http README for more info.
Notes
- Fastify includes Pino natively; no additional package is needed
- Express, Node core
http, H3, and Hono all use thepino-httpmiddleware - Hapi uses the dedicated
hapi-pinoplugin - Restify uses
restify-pino-logger - Koa uses
koa-pino-logger - Nest uses
nestjs-pino - All integrations expose a
req.log(or equivalent) logger bound to the current request
Related
- ecosystem
- transports
- options
Async Logging
Buffer log messages and write in large chunks for minimum I/O overhead.
const pino = require('pino')
// Asynchronous file destination — buffers 4 KB before writing
const logger = pino(
pino.destination({
dest: './app.log',
minLength: 4096, // flush threshold in bytes
sync: false // async mode
})
)
logger.info('buffered message')
// Flush remaining buffer explicitly (e.g. before graceful shutdown)
process.on('exit', () => logger.flush())
process.on('SIGINT', () => {
logger.flush()
process.exit(0)
})
// Stdout async (omit dest)
const stdoutLogger = pino(
pino.destination({ minLength: 4096, sync: false })
)Notes
sync: false(async mode) delivers the lowest possible logging overhead by writing in batches.- There is no 1:1 relationship between
logger.info()calls and disk writes — a crash before flush may lose the last buffer. - On AWS Lambda always use
sync: truebecause the runtime may freeze before an async flush completes. logger.flush()does not propagate through Worker Thread transports (e.g.pino-prettyviapino.transport()); usepino.destination()directly when flush control is required.
Basic Logging
Create a logger and emit structured JSON log messages at various levels.
const pino = require('pino')
const logger = pino({ name: 'my-app', level: 'info' })
logger.info('hello world')
// {"level":30,"time":1531257112193,"msg":"hello world","name":"my-app","pid":55956,"hostname":"x"}
// Log with a structured object merged into the JSON output
logger.info({ userId: 123 }, 'User logged in')
// printf-style placeholders
logger.info('Value: %d', 42)
logger.warn('something looks off')
logger.error(new Error('oops'), 'request failed')
logger.debug('not emitted at info level')Notes
- The first optional argument to a log method is a plain object (mergingObject) whose keys are merged into the JSON line.
leveldefaults to'info'; messages below the configured level are dropped with zero overhead.nameadds anamefield to every line.- Pass an
Erroras the first argument to serialize it into theerrfield automatically.
Child Loggers
Add persistent key-value bindings to every log line within a specific module or request scope.
const pino = require('pino')
const logger = pino({ name: 'my-app' })
// Create a child that always includes module: 'auth'
const authLog = logger.child({ module: 'auth' })
authLog.info('user logged in')
// {"level":30,...,"name":"my-app","module":"auth","msg":"user logged in"}
// Nest children for per-request context
function handleRequest(requestId) {
const reqLog = logger.child({ requestId })
reqLog.info('request received')
reqLog.info('processing done')
}
handleRequest('abc-123')Notes
.child(bindings)returns a new logger that inherits the parent's level, serializers, and transport configuration.- Bindings are prepended to every JSON line; no per-call overhead after creation.
- If parent and child share a key, both appear in raw output;
JSON.parseresolves to the child's value (last-wins). - Child creation is cheap — prefer one child per module or request rather than passing context manually to every call.
Log Levels
Configure the minimum log level at creation time and change it dynamically at runtime.
const pino = require('pino')
// Set level at creation
const logger = pino({ level: 'debug' })
logger.trace('trace') // emitted only when level <= trace
logger.debug('debug')
logger.info('info')
logger.warn('warn')
logger.error('error')
logger.fatal('fatal')
// Change level at runtime
logger.level = 'warn'
logger.debug('this is now suppressed')
// Guard expensive argument construction
if (logger.isLevelEnabled('debug')) {
logger.debug({ computed: expensiveCall() }, 'detailed data')
}
// Custom levels
const custom = pino({
customLevels: { audit: 35 },
level: 'audit'
})
custom.audit('audit event recorded')Notes
- Built-in levels in ascending order:
trace(10),debug(20),info(30),warn(40),error(50),fatal(60). - Messages with a numerical level below
logger.levelare filtered before any serialization — zero JSON cost. logger.levelcan be reassigned at any time to change the live filter threshold.- Use
isLevelEnabled()to skip expensive argument construction when the level would be filtered anyway. customLevelsadds new level methods; combine withuseOnlyCustomLevels: trueto drop built-in levels.
filtering
ログを特定のレベル・条件でフィルタリングして出力
grep でレベルフィルタリング(info = level 30)
node app.js | grep '"level":30'jq でレベルフィルタリング(error = level 50 以上)
node app.js | jq 'select(.level == 50)'jq で error レベル以上を抽出
node app.js | jq 'select(.level >= 50)'debug モジュールのログを pino で受け取る(pino-debug)
DEBUG=* node -r pino-debug app.js特定の名前空間のみ有効化する場合は DEBUG 環境変数で制御する。
DEBUG=myapp:* node -r pino-debug app.js事前に npm install pino-debug が必要。