installLogCapture

fun installLogCapture(exporter: WarpLogRecordExporter, config: CaptureConfig, clock: Clock, random: Random, scope: CoroutineScope, traceContextProvider: TraceContextProvider? = null): LogCaptureInstallation

Install log capture — the single uniform entry point on every platform.

Wires this platform's logging output into a shared LogCapture core that maps each event to a LogRecord and exports it into exporter. The capture edge is now identical on every target: kotlin-logging (oshai) exposes one settable appender on JVM, Android, iOS, macOS and wasmJs alike, so a single commonMain CapturingAppender hooks the output everywhere.

Installing routes kotlin-logging through its direct logger factory (required for the appender to take effect on JVM/Android/Darwin), then replaces the configured appender with a CapturingAppender that both feeds the capture core and forwards to a per-platform passthrough appender (captureDelegate) so the platform's existing log output is preserved.

Stop capture by closing the returned handle, which restores the previous appender/factory and stops the capturing appender. Cancelling scope alone is not enough — see LogCaptureInstallation.

Return

the LogCaptureInstallation handle — its LogCaptureInstallation.capture is the installed core, LogCaptureInstallation.health reports whether the bounded capture queue is dropping events (#2124), and LogCaptureInstallation.close uninstalls capture.

Parameters

exporter

the durable log buffer captured records are written into.

config

which events to keep and how to shape their attributes. Its CaptureConfig.attributeMapper is applied at the synchronous log() edge on the caller, so a mapper that folds ambient application state into attributes sees the state the line was emitted under (#1630).

clock

source of record timestamps (required — never the wall clock). Read at the synchronous log() edge for the record's event time (timestampEpochNanos) and again on the drain for its observed time (observedEpochNanos), so the two OTLP fields mean what OTLP says (#1993).

random

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

scope

the CoroutineScope the capture edge drains events on. Inject a test scope in tests; an application-owned scope in production.

traceContextProvider

optional trace/sampling gate — null (default) is always-on M1 capture; a provider gates and stamps per CaptureConfig.untracedPolicy and the trace's sampled flag. The provider is consulted at the synchronous log() edge on the caller (via LogCapture.resolveAtEdge), not on the drain coroutine — so an ambient provider that reads thread/coroutine-local context is honoured (#1034).

Samples

// The durable, offline-first buffer captured lines are written into. In
// production wire a platform WAL instead of the in-memory store.
val exporter = WarpLogRecordExporter(
    replica = ReplicaId("device-uuid-abc123"),
    store = InMemoryDurableStore(),
)

// One call, identical on JVM, Android, iOS, macOS and wasmJs. Time and
// randomness are injected — `Clock.System` and `Random.Default` in production,
// a virtual clock and a seeded RNG in a test.
val installation = installLogCapture(
    exporter = exporter,
    config = CaptureConfig(minLevel = LogLevel.INFO),
    clock = Clock.System,
    random = Random.Default,
    scope = scope,
)

// Your app keeps logging exactly the way it always has — no call-site change.
// Every line at or above INFO now also lands in the buffer.
val log = KotlinLogging.logger("com.example.Checkout")
log.info { "user checked out" }

// `close()` is how you stop capture: it restores the previous appender and
// stops buffering. Cancelling `scope` alone leaks the appender — see
// LogCaptureInstallation. Hold the handle for as long as capture should run.
return installation