pino-pretty

A CLI and stream that prettifies Pino's ndjson log lines into readable, colorized terminal output for local development.

Library
npm
v13.2.0
1,706 stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum. How we score it →
76 /100 Good
Development Activity 68
Maintenance 68
Community 68
Maturity 60
Momentum 40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation. How we score it →
78 /100 Good
Architecture 85
Code Quality 90
Innovation 62
Learning Curve 75

pino-pretty is the companion formatter for the Pino logging library. Pino itself is deliberately minimal in production, emitting raw ndjson log lines for speed, but that format is hard to read at a glance during development. pino-pretty takes those lines (or any ndjson-looking input) and renders them as colorized, human-readable text, showing the timestamp, level, message, and any attached metadata or error stack traces in a compact single- or multi-line layout.

It can be used two ways: as a standalone CLI that you pipe Node’s stdout through (node app.js | pino-pretty), or as a programmatic stream that plugs directly into Pino’s transport system (pino({ transport: { target: 'pino-pretty' } })). Both paths share the same underlying formatter, so CLI flags and programmatic options mirror each other one-to-one (e.g. --colorize / colorize, --translateTime / translateTime).

Beyond basic prettifying, it supports custom level names/colors, a small message-templating language (messageFormat, including {if x}...{end} conditionals), per-key custom prettifier functions, key filtering via ignore/include, and config files (.pino-prettyrc) discovered automatically via joycon. It is explicitly recommended only for development use — Pino’s own docs steer production deployments away from it for performance reasons.

What You Get

  • A CLI binary (pino-pretty) that reads ndjson from stdin and writes formatted, colorized lines to stdout
  • A programmatic stream/transform (require('pino-pretty')) usable directly as a Pino transport target or a plain writable stream
  • Automatic runtime adaptation to a logger’s custom levels and message key via Pino’s transport PINO_CONFIG message channel
  • A messageFormat templating option (string or function) with conditional {if x}...{end} blocks for custom message layouts
  • Per-key customPrettifiers hooks to override how specific log properties (time, level, hostname, or arbitrary keys) are rendered
  • Config-file support (.pino-prettyrc, pino-pretty.config.js) auto-discovered via joycon so CLI flags don’t need repeating

Common Use Cases

  • Piping a Node.js app’s stdout through the CLI during local development for readable console logs
  • Setting transport: { target: 'pino-pretty' } in a Pino logger config so development logs are auto-formatted without touching app code
  • Filtering noisy fields with --ignore/--include to focus on the log keys that matter while debugging
  • Customizing error and object rendering with customPrettifiers to surface domain-specific log fields (e.g. a query or req.url property) more usefully
  • Using sync: true in test frameworks like Jest where async log flushing would otherwise race against test teardown

Under The Hood

Architecture The entry point (index.js) exports a build() function that wraps the core formatter in a Node Transform stream via pino-abstract-transport, piped through pump to a destination (stdout, a file descriptor, or a sonic-boom instance built by the local buildSafeSonicBoom wrapper). The CLI (bin.js) parses argv with minimist, optionally loads a .pino-prettyrc config via joycon + secure-json-parse + strip-json-comments, merges it with CLI flags, and feeds the same build() entry point by piping process.stdin through it — so the CLI and the programmatic transport share one code path. The actual line-formatting logic lives in lib/pretty.js, which orchestrates roughly two dozen single-purpose modules under lib/utils/ (prettify-time, prettify-level, prettify-message, prettify-metadata, prettify-object, prettify-error-log, filter-log, interpret-conditionals, etc.), each doing one narrow transformation step. Options are parsed once into a shared context object (parse-factory-options.js) and bound to the pretty function via .bind(), avoiding per-line option re-parsing. This is a clean layered pipeline: stream/CLI plumbing, then formatting orchestration, then single-purpose utilities — changing the output format means editing lib/pretty.js’s assembly order and the relevant prettify-* module, without touching stream or CLI wiring.

Tech Stack Plain CommonJS Node.js with no application framework. Dependencies are narrowly scoped: colorette for color-support detection and ANSI coloring, pump for safe stream piping/cleanup, pino-abstract-transport for the Pino v7+ transport contract, sonic-boom (wrapped locally) for fast async file/fd writes, dateformat for timestamp formatting, secure-json-parse/fast-safe-stringify/fast-copy for safe JSON handling, joycon + strip-json-comments for config-file discovery, minimist for CLI parsing, and help-me for rendering --help text from help/help.txt. Types are hand-authored in index.d.ts (JSDoc-driven, no source TypeScript). Linting uses neostandard (a StandardJS-based flat ESLint config); tests run on borp (Node’s native test runner) with c8 coverage gating in CI, plus tstyche for type-level tests and @arethetypeswrong/cli to verify the package’s dual CJS export shape resolves correctly for consumers. CI (.github/workflows/ci.yml) runs a Node 20/22/24 matrix against a pinned pino@^10 peer, plus a PR-only dependency-review gate. There is no database or deployment target — it’s a pure Node.js library and CLI.

Code Quality Test coverage is unusually thorough for a project this size: nearly every file under lib/utils/ has a co-located *.test.js, and top-level suites (basic, boolean-options, cli, cli-rc, crlf, error-objects) exercise CLI behavior end-to-end in addition to unit-level formatter behavior. c8 coverage is enforced in the ci npm script (--check-coverage), and tstyche plus attw add type-level and package-export correctness tests that are unusually rigorous for a CommonJS package. Error handling favors defensive parsing over throwing: the core formatter wraps JSON parsing in a local try/catch and falls back to pass-through output on malformed input, while the CLI validates config-file shape and throws descriptive errors for invalid or missing rc files. Naming is consistent (kebab-case files, one narrowly-scoped export per file), and linting via neostandard is enforced in CI. No CONTRIBUTING.md was found at the repo root.

API Design The public API is small and consistent: CLI flags and programmatic options share identical names (--colorize / colorize, --translateTime / translateTime, etc.), documented together in the README so switching between CLI and programmatic use requires no relearning. Getting started needs zero configuration — pino({ transport: { target: 'pino-pretty' } }) or a stdout pipe both work with sensible defaults (colorize based on TTY support, hostname ignored by default). Advanced customization (custom levels/colors, per-key customPrettifiers, a messageFormat mini-template language with {if x}...{end} conditionals) is opt-in and layered on top of the defaults rather than required up front. The transport integration goes further than a typical formatter: it listens for a PINO_CONFIG control message over the transport’s message channel to auto-adapt to a logger’s custom levels and message key at runtime, without any user-side wiring — a level of framework-specific integration generic ndjson-pretty-printers don’t attempt.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers