Pino

repository·main·Indexed 12 days ago

https://github.com/pinojs/pino

A 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.

Tokens
28.3K
Snippets
104
Records
127
Agent score
95%

What's inside Pino

  1. Supported runtimes for pino

    main

    Pino is designed for multiple environments:

    • Node.js: Built specifically for Node.js.
    • Bare: Works on Bare using the pino-bare compatibility module.
    • Pear: Works on Pear (built on Bare) using the pino-bare compatibility module.
  2. Use tracing channel events for Pino internals

    main

    Pino 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 }
    • tracing:pino_asJson:end: Emitted when the final serialization process completes.
      • Payload: { instance, arguments, result } where result is the finalized, newline-delimited log line string.
  3. Understand Pino transports for log processing

    main

    Pino 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:

    1. Minimizing impact: The main application thread focuses on generating logs with minimal overhead.
    2. 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.

  4. Use `mixin` to add dynamic metadata

    main

    The mixin option allows you to inject dynamic data into every log line. The mixin function is called synchronously every time a log method is invoked. It receives the mergeObject (or an empty object), the log level number, and the logger instance itself.

    Note: For performance, the object returned by mixin is 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"}
  5. How transports and log processing work

    main

    To 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.transport API.

  6. Understand redaction performance overhead

    main

    Pino uses fast-redact for 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.

  7. Configure Browser logging options

    main

    When initializing Pino for the browser, you can pass a browser object 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 supports level).
    • 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 a send function.
    • disabled (Boolean): Disables all browser logging.
  8. Understand duplicate keys in child logger hierarchies

    main

    When 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')
  9. How Pino communicates with transports

    main
    Pino uses thread-stream to manage communication with transports. When you define a transport, thread-stream spawns 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.
  10. Avoid duplicate key conflicts in logs

    main

    Duplicate keys can occur if a logged object contains a top-level key that collides with:

    1. A Pino internal field (e.g., level, time, msg).
    2. A key in the child logger's bindings.
    3. 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 })
  11. Use v7+ transports with Worker Threads

    main

    Since Pino v7, transports can operate inside a Node.js Worker Thread. This is configured via the transport option in the object passed to the pino() 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: true within the specific transport's configuration options.
    const pino = require('pino')
    
    const logger = pino({
      transport: {
        target: 'pino/transport',
        options: { destination: 1 } // Example configuration
      }
    })
  12. How v7+ Transports work

    main

    A 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 the pino() constructor. You can specify a target (a file path or an npm package name) and an options object. Note that the options object 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)