first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
# `batching`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Buffers outbound log records and flushes them in batches over a single
MessagePort frame. Reducing one-frame-per-record to one-frame-per-batch
materially lowers the wire cost for workers under load, where a sustained
plugin can emit several hundred records per second. The batcher is
sink-agnostic — it invokes an `onFlush` callback supplied at construction
with a frozen snapshot of buffered records, so it composes equally with
the worker-side client's `postMessage` call and with test sinks.
A second concern handled here is backpressure. Logging is best-effort: a
slow main-side sink must never break the application by surfacing as an
exception in `log.info(...)`. The batcher caps the number of
outstanding-but-not-yet-released batches at `maxOutstandingBatches`; once
the cap is reached new records are dropped silently and an `onDrop`
callback fires so the consumer can increment the
`LOGGER_PROXY_BACKPRESSURE_DROP` counter.
## Features
- Flush triggers: max batch size (default 100), idle interval (default
50 ms), `error`/`fatal` level (immediate, but records buffered before
the error still ship in the same batch — order is preserved).
- Bounded outstanding-batch budget (default 8). Dropped records increment
a counter; the batcher never throws on `enqueue`.
- Injectable `TimeoutScheduler` for deterministic timer tests.
- `dispose()` clears the timer and drops anything still buffered.
## Usage: Basic
```ts
import { LogBatcher } from "@suaveplan/api-logger-worker-proxy/batching";
const batcher = new LogBatcher((records) => {
port.postMessage({
v: "1.0.0",
kind: "log-batch",
plugin: { name: "demo", version: "0.1.0" },
records,
});
batcher.releaseBatch();
});
batcher.enqueue({
level: "info",
msg: "hello",
ts: new Date().toISOString(),
});
```
## Usage: Advanced
Wiring the drop counter to an OTel meter and using a deterministic
scheduler in tests:
```ts
import {
LogBatcher,
type TimeoutScheduler,
} from "@suaveplan/api-logger-worker-proxy/batching";
let dropped = 0;
const fakeScheduler: TimeoutScheduler = {
set: (h) => {
queueMicrotask(h);
return null;
},
clear: () => {},
};
const batcher = new LogBatcher((records) => sink.write(records), {
maxBatchSize: 10,
flushIntervalMs: 5,
maxOutstandingBatches: 4,
onDrop: () => {
dropped++;
},
scheduler: fakeScheduler,
});
```
## API Reference
- `new LogBatcher(onFlush, options?)` — constructor.
- `enqueue(record): boolean` — `true` accepted, `false` dropped.
- `flush()` — flush whatever is buffered; idempotent.
- `releaseBatch()` — return a batch slot to the budget.
- `dispose()` — stop accepting; drop everything in the buffer.
- `droppedCount` / `flushedBatchCount` / `bufferedCount` /
`outstandingBatches` — read-only counters for tests + telemetry.
- `DEFAULT_BATCHER_OPTIONS` — the defaults exposed for re-use.
## Implementation Notes
`error` and `fatal` flush immediately AFTER being appended to the buffer
so any records queued before them ship in the same batch and the
ordering is preserved. The `outstanding` counter is decremented by
`releaseBatch()`; the worker-side client invokes that synchronously
after `postMessage` returns since `MessagePort` has no ack frame for
log batches and there is no transport-level confirmation to wait for.
Best-effort posture: every code path that could otherwise throw on a
log call (full buffer, disposed batcher) routes through `recordDrop`
instead.