WarpTelemetry

class WarpTelemetry(replica: ReplicaId, store: DurableStore, maxSpans: Int = DEFAULT_MAX_SPANS, maxLogRecords: Int = DEFAULT_MAX_LOG_RECORDS, bufferPolicy: BufferPolicy = BufferPolicy.DROP_OLDEST, maxMetrics: Int = DEFAULT_MAX_METRICS, metricBufferPolicy: MetricBufferPolicy = MetricBufferPolicy.DROP_OLDEST)

A CRDT-backed, offline-first telemetry surface for Kotlin Multiplatform.

WarpTelemetry owns the three signal exporters: spans, metrics, and logs. Each exporter writes to a DurableStore and holds its data as a CRDT — so offline buffering, eventual delivery, and idempotent merge are structural properties, not retry logic layered on top.

Binding surface (option a — direct OTLP)

kuilt-otel targets the OTLP wire format directly rather than wrapping the JVM OTel SDK. This gives full Kotlin Multiplatform reach: the same exporter runs on JVM, Android, iOS, macOS, and wasmJs. The JVM OTel SDK path (option b) would strand Native and wasm — the platforms where a KMP exporter is most valuable.

Usage

val telemetry = WarpTelemetry(
replica = ReplicaId("device-uuid-here"),
store = InMemoryDurableStore(), // or a platform WAL in production
)
telemetry.recover() // load persisted state from the store
telemetry.spans.export(span) // export() returns on durable write
telemetry.logs.export(logRecord) // same guarantee for log records

A WarpOtlpBridge drains the converged CRDTs to a real OTLP endpoint whenever the network is available. Wire it with an OtlpEdge implementation and call WarpOtlpBridge.drain on each reconnect — it reconciles by digest and sends only the spans the edge does not yet have.

Honest limits

  • Platform WALs for iOS/macOS (#724) and wasmJs/IndexedDB (#725) are deferred; pass InMemoryDurableStore until those land.

Parameters

replica

Stable, unique identity for this device/process (use a UUID).

store

Durable persistence backend. InMemoryDurableStore in tests; a platform-specific WAL in production.

maxSpans

Maximum number of spans buffered in memory.

maxLogRecords

Maximum number of log records buffered in memory.

maxMetrics

Maximum number of distinct metric series buffered in memory.

bufferPolicy

Eviction strategy when maxSpans or maxLogRecords is exceeded.

metricBufferPolicy

Eviction strategy when maxMetrics is exceeded.

Samples

val telemetry = WarpTelemetry(
    replica = ReplicaId("device-uuid-abc123"),
    store = InMemoryDurableStore(),
)

// Load any spans and log records buffered during a previous session.
telemetry.recover()

// Span ids are raw bytes (OTLP wire format): 16 bytes for trace id, 8 for span id.
val span = SpanRecord(
    traceId = ByteString(ByteArray(16) { it.toByte() }),
    spanId = ByteString(ByteArray(8) { it.toByte() }),
    parentSpanId = null,
    name = "purchase",
    kind = SpanKind.SERVER,
    startEpochNanos = 1_000_000_000L,
    endEpochNanos = 1_500_000_000L,
    attributes = mapOf("item.id" to "widget-42"),
    status = SpanStatus.Ok,
)

// export() returns the moment the data is durably written locally —
// not when it reaches a backend. Delivery is the fabric's job.
val spanResult = telemetry.spans.export(span)
check(spanResult == ExportResult.Success) { "export failed: $spanResult" }

val logRecord = LogRecord(
    recordId = ByteString(ByteArray(8) { it.toByte() }),
    body = "purchase completed",
    severityNumber = 9, // INFO
    traceId = ByteString(ByteArray(16) { it.toByte() }),
    spanId = ByteString(ByteArray(8) { it.toByte() }),
)
val logResult = telemetry.logs.export(logRecord)
check(logResult == ExportResult.Success) { "log export failed: $logResult" }

Constructors

Link copied to clipboard
constructor(replica: ReplicaId, store: DurableStore, maxSpans: Int = DEFAULT_MAX_SPANS, maxLogRecords: Int = DEFAULT_MAX_LOG_RECORDS, bufferPolicy: BufferPolicy = BufferPolicy.DROP_OLDEST, maxMetrics: Int = DEFAULT_MAX_METRICS, metricBufferPolicy: MetricBufferPolicy = MetricBufferPolicy.DROP_OLDEST)

Properties

Link copied to clipboard

The log-record exporter (A4). Export log records here; they are CRDT-merged on reconnect.

Link copied to clipboard

The metric exporter (A3). Export cumulative sums, gauges, and cardinality estimates here; they are CRDT-merged on reconnect with no double-counting.

Link copied to clipboard

The span exporter (A2). Export spans here; they are CRDT-merged on reconnect and auto-stamped with causal context so cross-device happens-before links form automatically (#846).

Functions

Link copied to clipboard
suspend fun clear(): ExportResult

Empty every signal's buffer and its persisted state — the supported reset (#2208).

Link copied to clipboard
suspend fun recover()

Load persisted CRDT state from the DurableStore for all exporters.