3.4 KiB
json-sink
Purpose
JsonSink is the machine-readable structured output sink for @suaveplan/logger. It serialises each LogRecord to a single JSON line and passes it to a configurable writer callback — defaulting to process.stdout.write — producing newline-delimited JSON (NDJSON) that is directly ingestible by log aggregators such as Fluentd, Logstash, Loki, or Datadog.
The design is deliberately thin: one serialisation step, one write call, no buffering, no formatting decisions. Buffering is the responsibility of BufferedSink; formatting decisions belong to the caller. JsonSink is the composable primitive that sits at the bottom of a sink stack.
Features
- Serialises
LogRecordto a compact JSON line viaJSON.stringify - Pluggable
writercallback — defaults toprocess.stdout.writewith a trailing newline - Throws a structured
LoggerError(LOGGER.SERIALIZE)if serialisation fails (e.g., circular references inctx) - Synchronous
write— no buffering, no internal state; each call is independent - Trivially composable with
BufferedSinkfor async, bounded queuing - No external runtime dependencies beyond the
LogRecordtype andLoggerError
Usage: Basic Example
import { JsonSink } from "@suaveplan/logger/sink";
const sink = new JsonSink();
// Writes: {"ts":"2024-01-01T00:00:00.000Z","level":"info","msg":"hello","ctx":{}}\n
sink.write({
ts: new Date().toISOString(),
level: "info",
msg: "hello",
ctx: {},
});
Usage: Advanced Example
import { JsonSink } from "@suaveplan/logger/sink";
import { BufferedSink } from "@suaveplan/logger/sink";
import { createWriteStream } from "node:fs";
// Write NDJSON to a rotating log file
const stream = createWriteStream("/var/log/app/app.ndjson", { flags: "a" });
const json = new JsonSink({
writer: (line) => stream.write(line + "\n"),
});
// Wrap in a buffer to absorb bursts
const buffered = new BufferedSink({ sink: json, maxQueueSize: 2000 });
// Forward records from the logger to the buffered sink
logger.addSink(buffered);
// Drain and close on process exit
process.on("SIGTERM", async () => {
await buffered.close();
stream.end();
});
API Reference
class JsonSink implements Sink
Constructor: new JsonSink(options?: JsonSinkOptions)
| Option | Type | Default | Description |
|---|---|---|---|
writer |
(line: string) => void |
process.stdout.write(line + "\n") |
Receives each serialised JSON line |
Methods
write(record: LogRecord): void— Serialise the record and invoke the writer callback. ThrowsLoggerErrorwith codeLOGGER.SERIALIZEifJSON.stringifythrows (e.g., due to a circular reference insidectx).
Implementation Notes
The default writer uses process.stdout.write rather than console.log to avoid the extra newline that console.log appends to its own output and to prevent interference with any console mocking installed by test frameworks.
JsonSink does not implement flush or close — it has no internal buffer to drain. When wrapped in BufferedSink, those lifecycle methods are handled by the buffer layer. This clean separation of concerns keeps JsonSink trivially testable with a synchronous writer stub.
Because JSON.stringify can throw on circular structures, JsonSink wraps the call in a try/catch and re-throws as a typed LoggerError so callers can distinguish serialisation failures from other sink errors without inspecting error message strings.