Files
2026-05-27 23:29:53 +01:00

3.4 KiB

server (main-side)

(no submodules)

Purpose

The main-side server. Consumes inbound log-batch frames from a worker-isolated plugin, applies redaction at the boundary, narrows the wire-format levels (trace / fatal) down to the LoggerLike levels (debug / error), and forwards each record to the host's main-side @suaveplan/logger instance. Plugin pre-tagging is honoured by routing through logger.child(plugin) when available, so every record reaches the sink with structured plugin / plugin_version fields without the worker having to know about the host logger's child-binding semantics.

The server is best-effort, just like the worker-side client. Malformed frames increment a counter, fire an onInvalidFrame hook, and surface via onError — but they NEVER throw out of the listener. A single bad record cannot bring down the port.

Features

  • Wire-frame validation via Zod (parseLoggerProxyFrame).
  • Level narrowing (trace → debug, fatal → error) so downstream sinks need not understand the broader level set.
  • Optional redaction passthrough at the boundary.
  • Child-logger caching per PluginTag (Map keyed by name@version so caching survives structured-clone of the tag object across the port boundary).
  • Hard maxBatchSize cap (default 10_000 records/batch) that protects the main logger from a hostile or buggy worker dumping unbounded inbound traffic.
  • Observation hooks: onRecord (post-redaction), onError, and onInvalidFrame.
  • forwardedCount, invalidFrameCount, overflowDropCount counters.

Usage: Basic

import { createLoggerProxyServer } from "@suaveplan/api-logger-worker-proxy/main-side";
import { createLogger } from "@suaveplan/logger";

const mainLogger = createLogger({ level: "info" });
const server = createLoggerProxyServer(mainPort, mainLogger);
// ... worker emits log records over workerPort ...
server.close();

Usage: Advanced

Pipe through host redaction and observe every record:

import { createLoggerProxyServer } from "@suaveplan/api-logger-worker-proxy/main-side";
import { maskKeys } from "@suaveplan/api-logger-worker-proxy/redaction-passthrough";

const server = createLoggerProxyServer(mainPort, mainLogger, {
    redaction: {
        ctxRedactor: maskKeys(["password", "token", "secret"]),
    },
    onRecord: (rec, plug) => audit.write(rec, plug),
    onError: (err) => mainLogger.warn("logger-proxy error", { code: err.code }),
    onInvalidFrame: (raw, cause) =>
        mainLogger.warn("logger-proxy invalid frame", { raw, cause }),
    maxBatchSize: 5_000,
});

API Reference

  • createLoggerProxyServer(port, logger, options?): LoggerProxyServer — primary entry.
  • LoggerProxyServer.close() — detach. Idempotent.
  • OnRecord(record, plugin) — post-redaction, pre-dispatch hook.
  • OnInvalidFrame(input, error) — fires on Zod parse failure.

Implementation Notes

logger.child is optional on LoggerLike; when absent the server merges plugin / plugin_version into the call-site ctx instead. The child-logger cache is a Map keyed by ${name}@${version} so that workers reusing the same identity (the common case) hit the cache even after structured-clone has produced a fresh object on the receive side of the port. Sink failures (the host logger throws while writing) are caught per-record so a single bad record cannot stall the listener; the error surfaces via onError.