npm package: @nxtedition/logger
Description: A high-performance, structured JSON logger built on [pino](https://github.com/pinojs/pino) with a dedicated worker thread for I/O.
Latest version: 2.1.13

# @nxtedition/logger

A high-performance, structured JSON logger built on [pino](https://github.com/pinojs/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:

1. **No tearing / interleaving** — Only one thread ever calls `write(2)` on
   fd 1, so every JSON line is atomically written.
2. **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.
3. **Minimal latency on the hot path** — `writeSync` into a shared ring buffer
   is a memory copy + atomic store. No syscall, no serialization contention.
   The actual `write(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 4 MiB `SharedHandle` 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):

1. All writers call `[Symbol.dispose]()` to flush pending data to their ring
   buffers.
2. The main thread signals the logger worker to drain via a shared
   `Int32Array` flag.
3. The main thread blocks (up to 2 s) until the worker confirms drain is
   complete.
4. The logger worker also registers a `process.on('exit')` handler as a
   safety net — if the normal drain protocol fails, the handler drains all
   readers and calls `[Symbol.dispose]()` on each.

## Usage

```ts
import { createLogger } from '@nxtedition/logger'

const logger = createLogger({ level: 'info' })
logger.info({ key: 'value' }, 'hello world')
```

`createLogger` accepts all [pino options](https://getpino.io/#/docs/api?id=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.

```ts
// 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 worker
```

## Development

```sh
yarn build           # compile TypeScript → lib/
yarn test            # run tests
yarn test:coverage   # run tests with c8 coverage report
```

## License

See [LICENSE](./LICENSE).