ExporterHealth

data class ExporterHealth(val accepted: Long = 0, val failed: Long = 0, val consecutiveFailures: Int = 0, val lastFailure: Throwable? = null, val recoveryFailed: Boolean = false, val dropped: Long = 0, val refused: Long = 0)

An out-of-band health signal for a durable exporter.

Why this exists

An exporter reports a failed durable write by returning ExportResult.Failure — but on the logging path every caller discards it. LogCapture.capture() hands the result back to per-platform appenders whose framework signatures return void, so the failure has nowhere to go. A component whose whole purpose is post-hoc diagnosis was therefore unable to report its own death: a device silently stopped accepting telemetry and stayed that way for hours, with nothing written and nothing logged (#1860).

These counters answer the question that could not be answered at the time: "has this exporter accepted anything since process start?" They are cumulative and monotonic (except consecutiveFailures, which resets), so a monitor can read them at any moment, or collect the owning kotlinx.coroutines.flow.StateFlow and alarm on a stall.

No timestamp, deliberately

There is no lastSuccessAt/deadSince field. Time is an injected dependency in this repo and an exporter holds no Clock; adding one to carry a diagnostic field would put a wall-clock read on the export hot path. Counters are sufficient — accepted == 0 with failed > 0 is "dead since process start", and an observer that needs wall-clock timing can stamp its own observations of the flow.

Constructors

Link copied to clipboard
constructor(accepted: Long = 0, failed: Long = 0, consecutiveFailures: Int = 0, lastFailure: Throwable? = null, recoveryFailed: Boolean = false, dropped: Long = 0, refused: Long = 0)

Properties

Link copied to clipboard

Log records durably taken by WarpLogRecordExporter.export, cumulative. Counts records, not calls and not store writes: one batched export of 256 records moves this by 256, and one export of a record whose id was already taken moves it by zero — a dedup no-op returns ExportResult.Success without touching the store, so an all-dedup state cannot masquerade as a healthy climbing count. A record refused by the buffer cap under BufferPolicy.DROP_NEWEST is not counted either, for the same reason. A successful merge does not move this at all — it takes no records through admission — but does clear consecutiveFailures, because the store accepting it is evidence the store is up.

Link copied to clipboard

Failed writes since the last successful one. Resets to zero on any success — distinguishes "currently down" from "recovered after some trouble".

Link copied to clipboard

Records evicted by the buffer cap under BufferPolicy.DROP_OLDEST, cumulative.

Link copied to clipboard

Durable writes that threw, cumulative across the process.

Link copied to clipboard

Whether this exporter has never once written durably yet has failed at least once — the signature of the silent death in #1860.

Link copied to clipboard

The most recent failure cause, from either a write or a recovery. Retained after a subsequent success, as forensics.

Link copied to clipboard

Whether recover() could not read or decode the persisted state, so the exporter started empty and the previously buffered telemetry is unrecoverable. Sticky for the life of the exporter.

Link copied to clipboard

Records refused by the buffer cap under BufferPolicy.DROP_NEWEST, cumulative.