mrkeyoor.com_
Sat 19 Sept 15:52 UTC
npmUtilsupdated 19 Sept 2026

pino review

Pino 10.3.1 turns Node log calls into newline-delimited JSON with numeric levels, timestamps, child bindings, serializers, and path-based redaction. The fast path writes records; pretty formatting and remote delivery normally run in a worker transport or downstream collector. This patch cleans invalid preload flags inherited by transport workers through `NODE_OPTIONS` and documents a level-filtering trap: a transport cannot receive a record that the logger already discarded. Version 10.3.0 also named worker threads and corrected the TypeScript return type for `multistream().clone()`. Pino fits services whose logs are data first and terminal output second.

45.0Mdownloads / wk
Verdict

Pino 10.3.1 installed in 1 second and used 3 MB across 14 packages, with no npm audit findings and a 3.6 KB gzipped browser bundle in our sandbox. It is a good default for Node services that ship JSON to a collector; choose another logger when in-process formatting and destination orchestration are the main job.

We installed it

Lab card: what happened when we installed pinoScreenshot of pino documentation
Install✓ · 1s14 packages on disk · 3 MB
ImportESM import works · require() works · CommonJS package
Browser3.6 KBgzipped (8.8 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does pino install cleanly?

Yes. In a fresh container with an empty cache, npm install pino finished in 1 seconds, leaving 14 packages and 3 MB on disk. npm audit reported no known vulnerabilities.

How much does pino add to a browser bundle?

3.6 KB gzipped (8.8 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does pino work with both ESM and CommonJS?

Yes. Both import 'pino' and require('pino') worked in Node 22 in our run. The package is published as CommonJS.

Does pino include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

pino or winston: which should you use?

Pick winston when several in-process destinations and independent formats define the logging design. Pino 10.3.1 installed in 1 second and used 3 MB across 14 packages, with no npm audit findings and a 3.6 KB gzipped browser bundle in our sandbox.

When should you not use pino?

One package must provide pretty files, rotation, and retention. pino-pretty and rotation remain separate pieces.

API stability4/5The main call shape, severity methods, object-first records, child bindings, serializers, redaction, and destinations remain recognizable in Pino 10. Patch 10.3.1 changes worker startup rather than application log calls, while 10.3.0 adjusts a `multistream().clone()` type. Custom transports and browser options have more moving parts than `logger.info()`, so pin major versions and exercise deployed worker targets before an upgrade.
Docs5/5Official documentation gives separate references for the logger API, transports, redaction, child bindings, asynchronous output, browser use, bundling, diagnostics, and framework integrations. It directly explains object-first logging and why transport work leaves the main thread. Retention, collector configuration, and graceful shutdown remain application concerns, so a complete production setup also needs documentation from the process manager and destination service.
Maintenance5/5Pino 10.3.1 shipped on 2026-02-09, and repository activity continued through 2026-08-25. The current patch repairs worker startup under invalid preload flags and clarifies transport-level filtering. GitHub counts 166 open issues and pull requests together. Nearby releases have addressed worker arguments, listener cleanup, target-loading safety, and TypeScript declarations, while the project publishes a support policy for maintained lines.
Ecosystem5/5npm recorded 45,211,268 downloads from 2026-08-19 through 2026-08-25, and GitHub shows 18,154 stars. Documented integrations cover Fastify, Express, Nest, Koa, Hapi, Hono, and Node's own HTTP server, with separate transports for delivery systems. Bundled declarations plus successful CommonJS and ESM loading in our test cover standard Node projects, while browser consumers get a distinct 3.6 KB gzipped path.

Use it if

  • A Node service writes JSON to stdout for a container runtime or log collector.
  • Request IDs and component fields should be bound once on child loggers.
  • Known object paths containing credentials must be censored before serialization.
  • Formatting or network delivery should leave the application event loop and run in a transport worker.
Skip it if

Setup reality

We installed Pino 10.3.1 in a fresh Node 22 Bookworm container. npm finished in 1 second, leaving 14 packages and 3 MB on disk, and reported zero vulnerabilities at every severity. Pino declares 11 direct dependencies and no peers; its package is 1,280 KB unpacked and includes TypeScript declarations. It is CommonJS without an exports map, though both require() and ESM import worked. Our full browser import bundled to 8.8 KB minified and 3.6 KB gzipped.

Plain stdout JSON needs no account, credential, or config file. Create one logger, pass fields in the first object argument, and use children for stable context such as requestId. Log an exception under err or assign an error serializer so its name and stack survive. Redaction accepts specific paths and wildcards before serialization. It cannot remove a credential that has already been interpolated into the message string.

Readable local output requires pino-pretty or another separate target. Network delivery, reformatting, and alerts fit transport workers because the main event loop keeps writing records. That worker must resolve its target from the deployed filesystem, which complicates one-file server bundles. Pino 10.3.1 filters invalid NODE_OPTIONS preload arguments before worker startup, fixing a failure seen with some monitoring preloads.

Buffered output can lose the last records when a process exits abruptly. During normal shutdown, stop accepting work, flush a supporting destination, and allow the event loop to finish instead of calling process.exit() after fatal(). Filtering occurs at the logger before the transport, so an info logger never sends debug records to a debug transport. The browser API uses transmit hooks and different serialization behavior; Node destinations and workers are not available there despite our 8.8 KB bundle result.

Patterns

Log fields beside a message write-structured-log

import pino from 'pino';

const logger = pino({ level: process.env.LOG_LEVEL ?? 'info' });
logger.info({ orderId: 'ord_42', amount: 19.5 }, 'order accepted');

Pino treats the leading object as structured fields. Interpolating the same values into the message leaves collectors with text to parse.

Bind request fields once create-child-logger

const requestLog = logger.child({
  requestId: request.id,
  route: request.route,
});
requestLog.info('request started');
requestLog.warn({ elapsedMs }, 'request slow');

Every child record inherits these bindings. Create the child at the request or component boundary instead of repeating identifiers at each call.

Preserve an error stack log-error-object

try {
  await saveOrder(order);
} catch (error) {
  logger.error({ err: error, orderId: order.id }, 'save failed');
  throw error;
}

The built-in `err` serializer keeps the error name, message, and stack. A template string normally records only the message text.

Remove known secret fields redact-secret-paths

const logger = pino({
  redact: {
    paths: ['req.headers.authorization', 'user.password', 'cards[*].number'],
    censor: '[REDACTED]',
  },
});

Redaction matches the configured object paths before output. Tokens embedded inside `msg` are outside those paths and remain visible.

Use readable local output pretty-print-development

const logger = pino({
  transport: process.env.NODE_ENV === 'development'
    ? { target: 'pino-pretty', options: { colorize: true } }
    : undefined,
});

`pino-pretty` is another package. Keep production output as JSON when a collector needs indexed fields.

Move processing to a transport worker send-worker-transport

const transport = pino.transport({
  target: './log-transport.js',
  options: { endpoint: process.env.LOG_ENDPOINT },
});
const logger = pino(transport);

The worker resolves this target at runtime. A bundler must copy or map the module rather than hiding it inside an unreachable bundle chunk.

Trim request and response objects set-custom-serializers

const logger = pino({
  serializers: {
    req: request => ({ method: request.method, url: request.url }),
    res: response => ({ statusCode: response.statusCode }),
  },
});

Whole server objects may expose credentials, sockets, or cycles. Returning a small known shape also keeps record volume predictable.

Flush during controlled shutdown flush-before-shutdown

process.once('SIGTERM', async () => {
  logger.info('shutdown requested');
  await stopServer();
  logger.flush();
  process.exitCode = 0;
});

An immediate `process.exit()` can cut off buffered records. Set the exit code after cleanup and let the process drain normally.

Alternatives

PackageRegistryPick it when
winstonnpmPick it when several in-process destinations and independent formats define the logging design.
bunyannpmKeep it in an established service that already depends on Bunyan serializers and tooling.
consolanpmPick it for developer-facing commands where readable terminal reporters matter more than collector-ready JSON.

More utils guides

lru-cache · type-fest · ajv · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.