Skip to content

0016. Metrics are one registry with bounded labels

  • Status: accepted
  • Date: 2026-10-14
  • Supersedes: none
  • Change: openspec/changes/add-metrics

Context

Operators need live health numbers (rate, errors, failovers, latency, breaker state, backlog), and the roadmap adds an audience that will not run Prometheus and wants the same numbers in a page the binary serves. Metric systems fail in two known ways: label values taken from request data explode the number of series and leak content, and a second view built later re-derives numbers that then disagree with the first.

Decision

Operational metrics live in one in-process registry owned by src/metrics.rs. Every operator-facing view reads that registry: the Prometheus text endpoint now, a JSON view for a status page later. Label values come only from the loaded configuration or from fixed sets; a client-supplied string never becomes a label. Identity (configured key ids) is off by default and opt-in, only on a few counters and never on histograms, with a cap on distinct values. The endpoint has its own credential and is off by default. Counters are monotonic, money is integer micro-USD.

Alternatives considered: the prometheus or metrics crates (a dependency on the serving path and a second place that owns metric names, with no gain for a set this small); deriving metrics from the ledger at scrape time (reads on the hot store, and it cannot see refusals or breaker state); per-key labels on by default (a topology and identity leak to anyone who can scrape).

Consequences

Adding a metric means adding a row to the registry table, which gives every view the metric at once. We must keep label sets bounded and tested against hostile input, and the exposition must stay valid text format. Counters reset on restart, so the ledger remains the source of truth for cost. A status page later is a thin view, not a new data path.