pino-http

The fastest structured HTTP request logger for Node.js, built directly on pino and ready to drop into any http server or Express app.

Library
npm
v11.0.0
708 stars
MIT License

Repository Health

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

Technical Analysis

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

pino-http wraps a pino logger instance around Node’s raw http.IncomingMessage/ServerResponse cycle, attaching a child logger to every request (req.log) and response (res.log) and automatically emitting a structured completion log when the response finishes, errors, or the connection closes. It benchmarks well ahead of other Node HTTP loggers because it inherits pino’s low-overhead, asynchronous JSON serialization rather than doing synchronous string formatting on the request path.

Beyond the zero-config default, it exposes a deep hook system — customLogLevel, customProps, customSuccessMessage/Object, customErrorMessage/Object, customReceivedMessage/Object, customAttributeKeys, and wrapSerializers — so teams can reshape field names, add request-scoped metadata, or change log levels per status code without forking the library or writing their own middleware from scratch. It works unmodified as vanilla Node middleware or as Express middleware, and integrates with pino’s transport and pretty-printing ecosystem out of the box.

What You Get

  • Automatic request/response logging on every request, with no manual log calls required
  • A child logger available as req.log / res.log, pre-bound with request context for any additional log lines a handler wants to add
  • Configurable log levels per response (customLogLevel) so 4xx/5xx responses can be logged as warn/error automatically
  • Custom request ID generation (genReqId) with sane integer-based defaults and support for reusing an existing x-request-id header
  • Hooks to reshape the logged object and message text at each lifecycle stage (received, success, error) without losing pino’s serializer wrapping
  • Drop-in compatibility with plain Node http servers and Express, plus interop with pino transports and pino-pretty for local development

Common Use Cases

  • Adding production-grade structured request logging to an Express or vanilla Node HTTP API with a single middleware line
  • Centralizing request/response/error logging format across a service so log aggregators (ELK, Datadog, Loki) can parse a consistent schema
  • Elevating error-classed responses (4xx/5xx) to warn/error log levels automatically via customLogLevel for alerting pipelines
  • Attaching request-scoped correlation IDs (genReqId) that propagate through req.log for distributed tracing correlation
  • Suppressing verbose per-request bindings in high-throughput services via quietReqLogger/quietResLogger while keeping request IDs

Under The Hood

Architecture

pino-http is a single-module (logger.js) middleware factory: calling the exported function normalizes options, creates or reuses a pino instance via wrapChild, and returns a closure that wires per-request state onto the Node req/res objects using two module-level Symbols (startTime, reqObject) to avoid polluting the public object shape. The request lifecycle is handled by loggingMiddleware, which attaches child loggers to req.log/res.log and registers ‘finish’/‘close’/‘error’ listeners on the response; onResFinished computes response time, resolves the log level via customLogLevel, and dispatches to either the error or success object/message providers. The separation between option normalization, logger construction, event wiring, and completion handling is clean for a file of this size, and the closure-based design (rather than a class) keeps per-request state private via symbols instead of exposed instance fields.

Tech Stack

The library ships as plain CommonJS JavaScript (logger.js, deprecations.js) with hand-written TypeScript definitions (index.d.ts) validated separately via tsd rather than being compiled from TypeScript source. Runtime dependencies are minimal and pino-ecosystem-native: pino ^10 for the underlying logger, pino-std-serializers ^7 for req/res/err serialization (wrapped via wrapRequestSerializer/wrapResponseSerializer/wrapErrorSerializer), get-caller-file ^2 to resolve transport caller paths, and process-warning ^5 for deprecation warnings. Tests run on Node’s built-in test runner (node —test) with @matteo.collina/tspl for assertions, coverage via c8, linting via eslint with the neostandard shareable config, and CI wired through GitHub Actions.

Code Quality

test/test.js runs to roughly 1,600 lines and exercises the option surface exhaustively — custom log levels, autoLogging.ignore, customProps as both function and object, wrapSerializers toggling, quietReqLogger/quietResLogger, and custom attribute key remapping — plus a dedicated transport-integration.js test for pino transport interop. Error handling is explicit rather than swallowed: incompatible option combinations (useLevel + customLogLevel) throw immediately rather than silently picking one. There’s no TypeScript source, but the public type surface is independently validated against real usage via tsd and ts-node in the test-types script, which catches type-definition drift that plain unit tests would miss.

API Design The public surface is a single factory function that works with zero configuration (require(‘pino-http’)()) as either a raw http listener call or Express middleware, with no adapter code needed either way. Every stage of the request lifecycle (received, success, error) has both a message hook and an object hook, letting consumers restyle log text or the structured payload independently without losing the underlying serializer wrapping. Advanced consumers get an escape hatch via the exported startTime symbol for manually correcting response-time measurement when pino-http isn’t the first middleware in the chain, and stdSerializers/pinoHttp.logger expose the underlying pino primitives directly rather than hiding them behind a facade.

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