Pino
repository·main·Indexed 12 days ago
https://github.com/pinojs/pinoA low-overhead, high-performance JavaScript JSON logger for Node.js, Bare, and Pear. Version 10.3.1 features a powerful transport system for offloading log processing to separate threads, support for child loggers, custom log levels, and redaction of sensitive information. It integrates with popular web frameworks like Fastify, Express, and Nest, and provides pino-pretty for human-readable development logs.
What's inside Pino
Use tracing channel events for Pino internals
mainPino uses Node.js tracing channel events to provide insight into its internal workings. You can subscribe to these events to monitor the log serialization process.
Currently supported events:
tracing:pino_asJson:start: Emitted when the final serialization process of logs begins.- Payload:
{ instance, arguments }
- Payload:
tracing:pino_asJson:end: Emitted when the final serialization process completes.- Payload:
{ instance, arguments, result }whereresultis the finalized, newline-delimited log line string.
- Payload:
Understand Pino transports for log processing
mainPino transports are used for both transmitting and transforming log output. They are designed to minimize the impact of logging on your application's performance by offloading log processing.
There are two main ways Pino handles logs:
- Minimizing impact: The main application thread focuses on generating logs with minimal overhead.
- Flexibility: Transports allow you to process and store logs in various ways (e.g., sending to a database, transforming JSON, or writing to files) without blocking the application.
It is highly recommended to perform log transformation or transmission in a separate thread or a separate process to maintain application performance.
Use `mixin` to add dynamic metadata
mainThe
mixinoption allows you to inject dynamic data into every log line. Themixinfunction is called synchronously every time a log method is invoked. It receives themergeObject(or an empty object), the log level number, and the logger instance itself.Note: For performance, the object returned by
mixinis mutated by Pino. If you need to add static metadata, use a child logger instead.let n = 0 const logger = pino({ mixin () { return { line: ++n } } }) logger.info('hello') // {"level":30,"time":...,"line":1,"msg":"hello"}How transports and log processing work
mainTo avoid blocking Node.js's single-threaded event loop, all log processing (such as sending logs to external services, triggering alerts, or reformatting) should be handled in a separate process or thread.
In Pino, these processors are called transports. It is highly recommended to run transports in a worker thread using the
pino.transportAPI.Understand redaction performance overhead
mainPino uses
fast-redactfor its redaction logic. Users should be aware of the following performance characteristics:- Standard Redaction: Using explicit paths without wildcards adds approximately 2% overhead to
JSON.stringify. - Wildcard Redaction: Using the
*wildcard carries a non-trivial cost. For example, redacting four keys across two objects using wildcards can be ~50% slower than explicitly declaring those four keys.
To minimize overhead, prefer explicit paths over wildcards whenever possible.
- Standard Redaction: Using explicit paths without wildcards adds approximately 2% overhead to
Configure Browser logging options
mainWhen initializing Pino for the browser, you can pass a
browserobject in the options to customize behavior.Key options include:
asObject(Boolean): Creates a pino-like log object instead of passing arguments to console methods.asObjectBindingsOnly(Boolean): Keeps message and arguments unformatted to allow browsers to use their native rich formatting in devtools.formatters(Object): Customizes the shape of log lines (currently supportslevel).reportCaller(Boolean): Attempts to capture the originating callsite (file:line:column).write(Function | Object): Redirects logs to a custom function or level-specific methods instead of the console.serialize(Boolean | Array): Enables or selectively enables serializers.transmit(Object): Enables remote log recording via asendfunction.disabled(Boolean): Disables all browser logging.
Understand duplicate keys in child logger hierarchies
mainWhen nesting child loggers that use the same key names, Pino's performance-optimized string-building approach results in duplicate keys appearing in the raw JSON output.
For example, if a parent child has
{a: 'property'}and a sub-child has{a: 'prop'}, the raw log line will contain both:..."a":"property","a":"prop".How conflicts are resolved: Most log processors (including standard
JSON.parse) resolve these conflicts by taking the last value assigned to the key. In the example above, the parsed object will result in{"a":"prop"}. This behavior aligns with Bunyan's child logging implementation.Warning: Be mindful of this behavior if you use non-standard JSON parsers that handle duplicate keys differently. This reinforces the importance of not allowing untrusted data to define top-level keys.
const pino = require('pino') pino(pino.destination('./my-log')) .child({a: 'property'}) .child({a: 'prop'}) .info('howdy')How Pino communicates with transports
mainPino usesthread-streamto manage communication with transports. When you define a transport,thread-streamspawns an independent JavaScript execution thread (a worker) to handle the log processing. This offloads the work from the main application thread to ensure low overhead.Avoid duplicate key conflicts in logs
mainDuplicate keys can occur if a logged object contains a top-level key that collides with:
- A Pino internal field (e.g.,
level,time,msg). - A key in the child logger's bindings.
- A key in the parent logger's bindings.
Best Practice: For untrusted or externally supplied data, do not pass the object directly to logging methods. Instead, wrap it under an application-controlled key to prevent collisions:
logger.info({ untrusted: externalData })- A Pino internal field (e.g.,
Use v7+ transports with Worker Threads
mainSince Pino v7, transports can operate inside a Node.js Worker Thread. This is configured via the
transportoption in the object passed to thepino()constructor during initialization.Key behaviors for v7+ transports:
- Asynchronous by default: Transports operate asynchronously to avoid blocking the main thread.
- Flushing: Logs are flushed as quickly as possible.
- Synchronous override: You can force a transport to be synchronous by setting
options.sync: truewithin the specific transport's configuration options.
const pino = require('pino') const logger = pino({ transport: { target: 'pino/transport', options: { destination: 1 } // Example configuration } })How v7+ Transports work
mainA transport is a module that exports a default function (which can be async) that returns a writable stream. Transports run in a separate worker thread to minimize overhead in the main thread. The main thread writes logs to the worker, which then writes them to the stream returned by your transport module.
When creating a transport, you use
pino.transport()and pass it to thepino()constructor. You can specify atarget(a file path or an npm package name) and anoptionsobject. Note that theoptionsobject is serialized via the Structured Clone Algorithm, so it can only contain supported types.import { createWriteStream } from 'node:fs' // my-transport.mjs export default (options) => { return createWriteStream(options.destination) } // app.js const pino = require('pino') const transport = pino.transport({ target: '/absolute/path/to/my-transport.mjs' }) pino(transport)