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 byname@versionso caching survives structured-clone of the tag object across the port boundary). - Hard
maxBatchSizecap (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, andonInvalidFrame. forwardedCount,invalidFrameCount,overflowDropCountcounters.
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.