Skip to content

Performance

Console is built for production. It adds minimal overhead to your application while offering structured logging, child loggers, configurable timestamps, and flexible transports — all in a ~10 KB gzipped bundle with zero dependencies.

Benchmarks

Measured on Apple M5 Max, Node.js v24.15, 100K iterations per benchmark.

What matters in production: structured JSON throughput

For most apps the only number that matters is "how fast can the logger actually emit a structured log line." This is the path your code runs in production — not silent / disabled.

Loggerops/secp50p95p99
Console (JSON → /dev/null)4.16M125 ns167 ns958 ns
Consola (JSON → /dev/null)795.9K1.13 µs1.37 µs2.17 µs
Bunyan (child → /dev/null)752.0K1.08 µs1.38 µs2.25 µs
Bunyan (JSON → /dev/null)741.8K1.25 µs1.46 µs2.33 µs
Winston (JSON → /dev/null)672.9K917 ns1.75 µs2.17 µs
Pino (JSON → /dev/null)560.5K1.63 µs2.67 µs3.38 µs

Console emits structured JSON ~5× faster than Consola, Bunyan, and Winston, and ~7× faster than Pino. p50 latency is roughly an order of magnitude lower than every competitor.

Pino, Winston, and Bunyan are Node.js only. Consola runs in the browser but without worker offloading. Console is the only structured logger that runs natively in both with worker offloading.

Microbenchmark: disabled / silent overhead

This measures how cheap a filtered-out log call is — i.e. what your code pays for logger.debug(…) in production when the level is set above debug. It is not a realistic throughput number because nobody runs a fully silenced logger; it's just an upper bound on per-call overhead.

LoggerModeops/sec
Pinochild, disabled34.02M
Consolechild, no buffer32.86M
Consolatagged child, silent22.60M
Consolasilent15.24M
Pinodisabled13.57M
Consolesilent, no buffer13.45M
Winstonsilent2.98M
Winstonchild, silent2.12M

Console, Pino, and Consola sit in the same fast-path tier (within run-to-run V8 noise). Winston is a tier below. Where Console pulls clearly ahead is the production scenario above — JSON output to a stream — not these microbenchmarks.

Browser / worker scenarios

ScenarioConsole
Silent + circular buffer (browser default)6.01M ops/sec
Child + circular buffer (browser default)3.88M ops/sec
With Worker transportnon-blocking on the main thread

Pino, Winston, Bunyan, and Consola don't have an equivalent — none offer worker-thread offloading, and Pino/Winston/Bunyan don't run in the browser at all.

Bundle & install size

ConsolePinoWinstonBunyanConsola
Bundle (gzip)~10 KB~32 KB~70 KB~45 KB~12 KB
Install size135 KB1.17 MB360 KB212 KB420 KB
Dependencies0111100

Reproducing benchmarks

Microbenchmark numbers at the nanosecond level vary between runs due to V8 JIT state, GC, and OS scheduling. Run npm run benchmark to see numbers on your hardware. Install competitors first with npm install --no-save pino winston bunyan consola.

Buffer Mode

In Node.js (default), log entries go directly to formatters and transports with no in-memory storage. This gives you maximum throughput and zero memory accumulation.

In browsers (default), entries are stored in a circular buffer so you can inspect them via getLogs(), viewLogs(), and the exposeToWindow() DevTools handle.

typescript
// Node.js: maximum throughput, no buffer (default)
const logger = new Konsole({ namespace: 'App' });

// Browser: stored for DevTools inspection (default)
const logger = new Konsole({ namespace: 'App' });

// Node.js: opt in to buffer when you need getLogs()
const logger = new Konsole({ namespace: 'App', buffer: true });

Circular Buffer

When buffer is enabled, Console stores up to 10,000 logs (configurable via maxLogs) in a circular buffer. When the limit is reached, oldest logs are automatically evicted.

typescript
const logger = new Konsole({
  namespace: 'App',
  buffer: true,
  maxLogs: 5000,
});
  • Constant memory — never grows beyond the limit
  • No manual cleanup — automatic eviction
  • O(1) operations — push and evict
typescript
const stats = logger.getStats();
console.log(stats.memoryUsage); // "1234/5000 (24.7%)"

Worker Transport

This is Console's standout feature. With useWorker: true, log storage and HTTP transport batching move to a background worker — Web Worker in browsers, worker_threads in Node.js. The main thread never blocks on logging, even at high volume.

No other structured logging library (Pino, Winston, Bunyan) works in the browser, let alone offers cross-platform worker offloading.

typescript
const logger = new Konsole({
  namespace: 'App',
  useWorker: true,
  transports: [{
    name: 'analytics',
    url: '/api/logs',
    batchSize: 50,
    flushInterval: 10000,
  }],
});

// Main thread stays free for rendering
logger.info('Frame rendered', { fps: 60, dt: 16.2 });
logger.info('User interaction', { event: 'scroll', y: 1200 });

// Retrieve logs from worker
const logs = await logger.getLogsAsync();

When to use

  • High-volume browser logging (100+ logs/sec)
  • Performance-critical SPAs and animations
  • Long-running applications where main-thread responsiveness matters
  • Shipping logs to a backend from the browser without blocking UI

How it works

When useWorker: true:

  • Logs are written to both the main-thread buffer (for synchronous getLogs()) and a background worker
  • HTTP transports run entirely in the worker — batching, flushing, and retries happen off the main thread
  • Use getLogsAsync() to retrieve the worker's copy of stored logs
  • In browsers, uses Web Worker via Blob + Object URL
  • In Node.js, uses worker_threads via dynamic import with a compatibility shim
  • Falls back gracefully to main-thread processing if no worker API is available

Production Tips

Set an appropriate level

typescript
const logger = new Konsole({
  namespace: 'App',
  level: process.env.NODE_ENV === 'production' ? 'warn' : 'debug',
});
// In production, trace/debug/info add zero overhead

Filter transports

typescript
{
  name: 'errors-only',
  url: '/api/logs',
  filter: (e) => e.levelValue >= 50,
  batchSize: 100,
  flushInterval: 60000,
}

Flush before exit

typescript
process.on('SIGTERM', async () => {
  await logger.flushTransports();
  process.exit(0);
});

Clean up in components

typescript
useEffect(() => {
  const logger = new Konsole({ namespace: 'Component' });
  return () => { logger.destroy(); };
}, []);

Running Benchmarks

bash
npm run build
npm run benchmark                          # Console only
npm install --no-save pino winston bunyan consola  # install competitors
npm run benchmark                          # full comparison
npm run benchmark:size                     # bundle size analysis
npm run benchmark:gc                       # with GC stats