mrkeyoor.com_
Sat 08 Aug 22:00 UTC
npmInfraupdated 08 Aug 2026

bunyan

Bunyan is a CommonJS logging library for Node.js services. Every call writes one JSON record with standard fields such as name, hostname, process ID, level, message, and time, which makes logs easy for machines to ingest. It also includes a command-line formatter for humans, child loggers that bind context, serializers for errors and HTTP objects, and a stream system for sending different severity levels to different destinations.

Verdict

Keep Bunyan where its record format and tooling are already part of the system. For a new Node.js service, Pino offers a more current path with less maintenance uncertainty and better performance priorities.

API stability5/5The 1.x API has barely moved and the README promises semantic versioning: createLogger, level methods, child loggers, serializers, and stream descriptors have remained recognizable for many years. That stability is partly a consequence of low activity, but existing services are unlikely to face surprise API churn within the published 1.x line.
Docs4/5The README is unusually complete for an older logging package. It defines every core record field, documents all six levels, explains each stream type, gives CLI filters, covers serializer failure behavior, and calls out concrete production warnings for source locations and clustered rotation. Its examples and runtime links are dated, and there is no modern TypeScript or ESM guide.
Maintenance2/5The default npm release, 1.8.15, was published in January 2021 and the repository's latest push was in September 2023. The README still calls the master-line 2.x releases beta even though 2.0.5 was published alongside 1.8.15, and the repository reports 293 open issues and pull requests. The code is stable, but this is not an actively evolving project.
Ecosystem4/5Bunyan still records 3,891,254 weekly npm downloads and has 7,208 GitHub stars, evidence of a large installed base. Its newline-delimited JSON format, command-line viewer, Restify history, third-party stream list, and DefinitelyTyped declarations make maintenance practical. The center of new Node logging work has moved toward Pino and Winston, so ecosystem breadth no longer means momentum.

Use it if

  • You maintain an existing Node.js service whose log pipeline, dashboards, or conventions already expect Bunyan records
  • You want newline-delimited JSON on stdout plus a bundled CLI that can pretty-print and filter the same records
  • You need child loggers to bind request IDs or component names without passing those fields at every call
  • You need multiple Node writable streams with separate severity thresholds or a raw stream that receives record objects
Skip it if

Setup reality

Installing `bunyan` is simple, but its age shows around the edges. Version 1.8.15 has four optional dependencies: `dtrace-provider` enables DTrace, `mv` supports the rotating-file stream, `moment` supports local-time output in the CLI, and `safe-json-stringify` protects record serialization. npm may therefore attempt platform-sensitive optional installation work even if your app only logs JSON to stdout; omit optional dependencies if those features are irrelevant, then do not configure features that rely on them. TypeScript definitions are not bundled, so add `@types/bunyan` separately. A logger requires a `name`, defaults to stdout at info level, and writes raw JSON rather than pretty terminal text. Pipe development output through `bunyan`, but keep raw JSON in production for collectors. Error, request, and response objects only get useful shapes when you configure `bunyan.stdSerializers`; the request and response serializers intentionally omit bodies. Do not enable `src: true` in production because the README calls source-location discovery slow. File rotation also needs operational care: clustered processes cannot safely share one rotating path, size-based rotation is unavailable, and external rotation without copy-truncate requires your process to call `reopenFileStreams()` after the file descriptor changes. Stream errors are EventEmitter errors, not error-level log records, so attach a logger error handler when file writes can fail.

Patterns

Create a JSON loggercreate-logger

const bunyan = require('bunyan');

const log = bunyan.createLogger({ name: 'orders-api' });
log.info('service started');

The name is required. With no stream configuration, Bunyan writes JSON to stdout at info level.

Add searchable fields to a recordlog-structured-fields

log.info(
  { orderId: 'ord_123', customerId: 'cus_9', totalCents: 2499 },
  'order accepted'
);

Put application objects under named fields. Passing an arbitrary object directly can collide with Bunyan's core fields.

Preserve error message and stacklog-errors

const log = bunyan.createLogger({
  name: 'worker',
  serializers: bunyan.stdSerializers,
});

try {
  await runJob();
} catch (err) {
  log.error({ err, jobId: 'job_42' }, 'job failed');
}

Configure the err serializer when an Error shares a record with other fields; otherwise the useful non-enumerable Error properties can disappear.

Create a child logger for one requestbind-request-context

function handleRequest(req, res) {
  const reqLog = log.child({ reqId: req.headers['x-request-id'] });
  reqLog.info({ path: req.url }, 'request started');
  // Every later reqLog call includes reqId.
}

Child fields are copied onto every record. Avoid the optional simple-child shortcut unless profiling proves you need it, because it skips safety checks.

Serialize Node HTTP objectsserialize-http-request

const httpLog = bunyan.createLogger({
  name: 'http',
  serializers: bunyan.stdSerializers,
});

httpLog.info({ req }, 'request received');
httpLog.info({ res }, 'response sent');

The standard req and res serializers intentionally omit bodies, which avoids accidental huge payloads but does not by itself redact sensitive headers.

Send errors to a second destinationroute-by-level

const log = bunyan.createLogger({
  name: 'payments',
  streams: [
    { level: 'info', stream: process.stdout },
    { level: 'error', path: '/var/log/payments-error.log' },
  ],
});

An error record goes to both streams because each level is a minimum threshold. File-stream errors are re-emitted by the logger.

Handle file stream failureshandle-stream-errors

log.on('error', (err, stream) => {
  process.stderr.write(`logging failed for ${stream.path || 'stream'}: ${err.message}\n`);
});

A stream error event is separate from log.error(). Without an error listener, an EventEmitter error can terminate the process.

Raise or lower verbosity at runtimechange-log-level

log.level('debug');

if (log.debug()) {
  log.debug({ state: buildExpensiveDebugState() }, 'worker state');
}

Calling a level method with no arguments checks whether that level is enabled, so expensive debug data need not be built when filtered out.

Pretty-print and filter JSON logspretty-print-cli

node server.js | npx bunyan
node server.js | npx bunyan -l warn
npx bunyan app.log -c 'this.reqId == "req_123"'

Keep production output as JSON. The CLI is a viewing and filtering layer, and its condition expression processes log content as JavaScript.

Rotate a file on a time schedulerotate-log-files

const log = bunyan.createLogger({
  name: 'batch',
  streams: [{
    type: 'rotating-file',
    path: '/var/log/batch.log',
    period: '1d',
    count: 7,
  }],
});

Rotation is time-based only. Do not point multiple cluster workers at the same path; use separate paths or an external log service.

Reopen files after external rotationreopen-external-log

process.on('SIGUSR2', () => {
  log.reopenFileStreams();
});

Use this when logrotate renames the file without copytruncate; otherwise Bunyan keeps writing through the old file descriptor.

Keep recent trace records in memorybuffer-recent-records

const ring = new bunyan.RingBuffer({ limit: 100 });
const log = bunyan.createLogger({
  name: 'worker',
  streams: [
    { level: 'info', stream: process.stdout },
    { level: 'trace', type: 'raw', stream: ring },
  ],
});

console.log(ring.records);

A raw stream receives record objects rather than JSON strings. Bound the ring carefully because every retained record consumes process memory.

Alternatives

PackageRegistryPick it when
pinonpmNew Node services that want fast structured JSON logging, transports, and current maintenance
winstonnpmApplications that value a large transport ecosystem and configurable formats over a fixed JSON record shape
log4jsnpmTeams that want log4j-style categories, appenders, and configuration-driven routing in Node.js