LogCapture

class LogCapture(exporter: WarpLogRecordExporter, config: CaptureConfig, clock: Clock, random: Random, traceContextProvider: TraceContextProvider? = null)

The shared, platform-independent capture core.

Maps a NormalizedLogEvent to an OTLP-shaped LogRecord and exports it into the durable buffer. Every per-platform capture edge funnels through this one type, so the mapping — level, body, attributes, identity, timestamps — is identical on every target.

Self-capture exclusion (safety invariant)

Capture hooks the process-global logging config, so it sees every event in the process. The durable exporter itself logs on its hot path (a buffer-cap eviction warning, store-failure errors), so capturing those would feed a captured eviction-warn back into export → evict again → warn again — a self-sustaining loop that crowds out real application logs. To make that impossible, any event whose loggerName is under the exporter's own package (us.tractat.kuilt.otel) is dropped before a record is built. Both capture and captureAll reach that decision through one private mapper, so batching a run of events cannot weaken it into a per-run filter.

The exclusion is scoped to only the exporter's own us.tractat.kuilt.otel.* loggers — narrow enough to break the export feedback loop, but no broader. In particular it does not exclude kuilt's library loggers (us.tractat.kuilt.session.*, ...liveness.*, ...raft.*, ...nw.*, …): a consumer that uses kuilt as its networking library depends on those library diagnostics being captured just like its own application logs, and any consumer running under the us.tractat.kuilt package (but outside .otel) is captured normally. This is a non-negotiable invariant, not a configurable filter, and every capture edge inherits it through this one core.

Injected dependencies

Both time and randomness are dependencies, never reached for directly:

  • clock supplies the record timestamps. A test injects a virtual clock; a production install passes kotlin.time.Clock.System.

  • random supplies the fresh 8-byte recordId per record. A test injects a seeded Random; a production install passes Random.Default.

Edge resolution (emit-time semantics)

Anything that depends on when and where the line was logged is resolved at the synchronous capture edge, on the caller, and carried on the queued NormalizedLogEvent — never re-derived on the drain coroutine. That covers the ambient trace context (#1034), the CaptureConfig.attributeMapper (#1630) and the instant the line was logged (#1993). A queueing edge calls resolveAtEdge once; capture then reads the snapshot. Resolving any of them on the drain stamps records with whatever the ambient state has become by then, which the consumer cannot detect or repair.

Parameters

exporter

the durable log buffer this capture writes into.

config

which events to keep and how to shape their attributes.

clock

source of the record timestamps (required — never the wall clock). Read twice per record on a queueing edge: once by resolveAtEdge for the event time, once by capture for the observed time.

random

source of the per-record id bytes (required — never an unseeded default).

traceContextProvider

optional trace/sampling gate. When null (the M1 default) capture is always-on and records carry no trace ids. When set, the trace is resolved at the synchronous capture edge via resolveAtEdge and carried on NormalizedLogEvent.activeTrace; capture then gates on that snapshot (see resolveAtEdge and capture).

Constructors

Link copied to clipboard
constructor(exporter: WarpLogRecordExporter, config: CaptureConfig, clock: Clock, random: Random, traceContextProvider: TraceContextProvider? = null)

Functions

Link copied to clipboard

Map event to a LogRecord and export it.

Link copied to clipboard

Map a run of events to LogRecords and export them as one write turn.

Link copied to clipboard

Snapshot everything that must be sampled at the moment the line was logged onto event, for a queueing capture edge to hand to the drain.

Link copied to clipboard

Resolve the trace active on the current call.