Expand description
Telemetry — and the question of what it is for, in an architecture that already has a log.
§Why this is not the usual answer
Distributed tracing exists because in a system of services nobody knows what happened. You reconstruct causality after the fact from correlated, sampled spans, and you accept that the reconstruction is partial.
Beck already has something strictly stronger for the part tracing usually covers: a durable
total order of every state transition. state = fold(f, init, log[..seq]) is not a sample and
not a reconstruction — it is the actual history, and crate::replay_to will rebuild any state
the system was ever in. Tracing the fold’s internal call tree as spans would re-record, lossily
and at cost, what the log records exactly and for free.
So the division of labour is specific:
| question | answered by |
|---|---|
| what happened, in what order, and what state did it produce | the log |
| what state was the system in at 14:02 | the log, by replay |
| why did this command produce that event | the log, by replay |
| how long did the fold take | here |
| how long did the append wait on Postgres | here |
| what was rejected, and never became an event | here |
| how many sessions are connected | here |
| what the maintained views cost, shared and per session | here |
| did the pod get killed mid-batch | here |
Everything in the right column is either wall-clock, resource use, or a non-event: something the log deliberately does not record, because §4.8 requires the fold to be replay-pure and a fold that recorded its own duration would not replay identically. Telemetry is not a weaker substitute for the log here; it is the complement of it, and the boundary between them is exactly the boundary of determinism.
§Correlation is seq, not a trace id
A random trace id identifies a request. seq identifies a state: given one, beck replay --to <seq> reproduces the system exactly as it was. So every record that has a sequence number
carries beck.seq, and a span in any OTel backend is one command away from a reproducible
debugging session. That is a property this architecture has and a microservice fleet does not.
§Is OpenTelemetry valid here?
Yes, for the right column, and this module speaks it: Telemetry::otlp_metrics and
Telemetry::otlp_logs produce OTLP/HTTP JSON, which is a first-class encoding in the OTLP
specification — same field names and semantics as the protobuf form, with no tonic, no
prost, and no code generation. Export is pull-only: the dashboard serves this data from
memory at /_beck/otlp/metrics and /_beck/otlp/logs, and nothing pushes to a collector
(a push exporter is scheduled, not built — docs/101 §101.8).
What Beck should not do is adopt OTel’s model as its own. Spans belong at the boundaries — ingress, validate, append, fold, view, patch — and not inside the fold, where the log is the better instrument.
§Cost
Counters and histogram buckets are AtomicU64: recording is one relaxed fetch-add and no
allocation, so instrumenting the fold does not perturb what it measures. Histograms are fixed
power-of-two buckets, so the bucket index is a leading_zeros. The log ring is bounded and
overwrites oldest-first, so a process that runs for a month does not accumulate.
Structs§
- Counter
- Gauge
- A gauge — a value that goes up and down, like the number of connected sessions.
- Histogram
- A histogram of durations in microseconds.
- Record
- One log record, kept in memory for the dashboard and exported as an OTLP log record.
- Telemetry
- The instruments.
Functions§
- now_
unix_ nanos - telemetry
- The process-wide instruments.
- timed
- Time a block and record it, returning what the block returned.