@nxtedition/logger
v2.0.8
Published
A high-performance, structured JSON logger built on [pino](https://github.com/pinojs/pino) with a dedicated worker thread for I/O.
Downloads
935
Keywords
Readme
@nxtedition/logger
A high-performance, structured JSON logger built on pino with a dedicated worker thread for I/O.
Why a logger worker?
In Node.js, multiple threads writing to stdout concurrently can interleave partial writes, producing corrupted or merged log lines. This is especially problematic when several worker threads each emit structured JSON — a single torn line breaks every downstream log parser.
This package solves the problem by routing all log output through a single dedicated worker thread:
- No tearing / interleaving — Only one thread ever calls
write(2)on fd 1, so every JSON line is atomically written. - Better throughput — Writers serialize log messages into lock-free ring
buffers backed by
SharedArrayBuffer(SAB). The logger worker drains these buffers and writes to stdout using synchronous I/O, avoiding back-pressure from slow consumers stalling application threads. - Minimal latency on the hot path —
writeSyncinto a shared ring buffer is a memory copy + atomic store. No syscall, no serialization contention. The actualwrite(2)happens asynchronously on the worker thread.
Architecture
┌──────────────┐ ┌─────────────┐ ┌─────────────┐
│ Main thread │ │ Worker A │ │ Worker B │
│ (logger) │ │ (logger) │ │ (logger) │
└──────┬───────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
writeSync() writeSync() writeSync()
│ │ │
┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐
│ Ring buf │ │ Ring buf │ │ Ring buf │
│ (SAB) │ │ (SAB) │ │ (SAB) │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└──────────┬───────┘─────────────────┘
│
┌──────▼──────┐
│Logger worker│
│ (single) │
│ │
│ readSome() │
│ fs.writeSync│
│ fd 1 │
└─────────────┘SAB = SharedArrayBuffer
Each call to createLogger() allocates a 2 MiB SharedArrayBuffer ring
buffer. The writer (application side) serializes pino JSON into the buffer.
The logger worker polls all registered readers and flushes their contents to
stdout.
Registration and unregistration use BroadcastChannel with a
SharedArrayBuffer-based ack to ensure the worker has set up the reader
before the caller proceeds.
Graceful shutdown
On process exit (process.exit(), uncaught exceptions, SIGTERM, SIGINT):
- All writers call
flushSync()to publish pending data to their ring buffers. - The main thread signals the logger worker to drain via a shared
Int32Arrayflag. - The main thread blocks (up to 2 s) until the worker confirms drain is complete.
- The logger worker also registers a
process.on('exit')handler as a safety net — if the normal drain protocol fails, the handler performs a final synchronous drain of all readers.
Usage
import { createLogger } from '@nxtedition/logger'
const logger = createLogger({ level: 'info' })
logger.info({ key: 'value' }, 'hello world')createLogger accepts all pino options.
Custom serializers are merged with the built-in set (err, req, res, etc.).
Worker threads
Call createLogger() on the main thread first — this starts the logger
worker. Worker threads can then call createLogger() freely; each gets its
own ring buffer registered with the shared logger worker.
// main.ts
import { createLogger } from '@nxtedition/logger'
const logger = createLogger({ level: 'info' }) // starts worker
// worker.ts
import { createLogger } from '@nxtedition/logger'
const logger = createLogger({ level: 'debug' }) // registers with existing workerDevelopment
yarn build # compile TypeScript → lib/
yarn test # run tests
yarn test:coverage # run tests with c8 coverage reportLicense
See LICENSE.
