Single-binary, zero-config observability backend for the cboxdk
Laravel ecosystem. Logs, traces and metrics in one directory, served back
through the Loki, Tempo and Prometheus APIs that laravel-telemetry-ui already speaks.
Your telemetry never leaves your infrastructure: one binary, one port, one data directory, no sidecars.
telemetryd is scoped deliberately: one team, a handful of apps, one VPS or a dev laptop. It targets that case completely rather than scaling to a large fleet.
Single-node is a design choice, not a limitation we plan to remove. It is what lets us delete sharding, consensus, object-store tiering and a query scheduler — and ship one 2.6 MB binary instead of a stack of services to operate. If you outgrow one node, you have outgrown telemetryd, and we would rather say so than pretend otherwise.
Status: feature complete. All three signals go in as OTLP/HTTP JSON (plus Prometheus
remote_write), land in Parquet segments, and come back out through the Loki, Tempo and Prometheus query APIs — with live tail, and retention enforcing both time and a disk budget. Nothing in the contract answers501.BUILD-STATUS.md is the honest, current list of what works, what the known gaps are, and what is deliberately absent.
telemetryd serveThat is the whole setup. It listens on 127.0.0.1:4319, stores data in
./telemetryd-data (or your platform data directory), keeps 7 days of logs and traces
and 30 days of metrics, and stays under a 10 GiB disk budget.
Or as a container, with the same amount of configuration:
docker run -d -p 4319:4319 -v telemetryd-data:/var/lib/telemetryd \
ghcr.io/cboxdk/telemetryd:latestA container binds 0.0.0.0, which telemetryd refuses to do unauthenticated, so the
image generates tokens on first start and prints them once — docker logs has them.
Every setting is an environment variable, so nothing needs a file. See
Docker.
telemetryd statusPoint cboxdk/laravel-telemetry at http://127.0.0.1:4319 and query it back:
# logs
curl -G http://127.0.0.1:4319/loki/api/v1/query_range \
--data-urlencode 'query={app="checkout", level="error"} |= "declined"'
# metrics
curl -G http://127.0.0.1:4319/api/v1/query \
--data-urlencode 'query=rate(http_requests_total[5m])'
# a trace
curl http://127.0.0.1:4319/api/traces/4bf92f3577b34da6a3ce929d0e0e4736Point all three of laravel-telemetry-ui's connectors — Loki, Tempo and
Prometheus — at that one base URL.
Full documentation is in docs/ — start with the quickstart.
| Getting started | Installing, sending data, testing |
| Core concepts | Architecture, signals, storage, performance |
| Cookbook | Running as a service, exposing it safely, sizing the budget |
| Configuration | Every option, with defaults |
| Extension points | What is adjustable, and what is not |
| Security | Threat model and honest scope |
The reasoning lives next to the code it explains: why a decision was made, and where the first attempt was wrong, are in the doc comments of the module that carries it. What is built, what is not, and what is deliberately absent is in BUILD-STATUS.md.
Pointing an AI agent at this? llms.txt is the short version, and the installation, sending-data and service pages each carry a copy-paste brief. The part worth reading first is which query constructs are refused — an agent that knows Loki and Prometheus will otherwise write valid expressions this does not run.
API contract: COMPATIBILITY.md — derived from laravel-telemetry-ui's
actual connector source, not from the upstream API references.
A self-hosted tool that can only be read through its own interface has quietly become a place data goes in. Every signal moves in every direction, as OTLP — the format every other backend already accepts.
telemetryd export --since 24h > dump.ndjson # to a file
telemetryd export --to http://new-host:4319 # straight to another instance
telemetryd import --from https://logs.internal # in from somewhere elseBetween two telemetryds, records are read straight from the store rather than
re-derived from a query language, so all three signals come across as stored. From a
foreign backend it goes through the read APIs each speaks — logs, traces, and metrics
through Prometheus remote read, which is the only one of the three that returns stored
samples rather than points on a step grid.
--progress writes to stderr while the data goes to stdout, so export | gzip keeps
its meter. See the transfer guide.
telemetryd refuses to start on a non-loopback address with no token configured —
including 0.0.0.0, which is the bind that actually exposes people. Telemetry
routinely contains emails, tokens and stack traces, so this fails closed. The error
tells you the three ways to fix it and generates a token to paste.
[auth]
ingest_token = "file:/run/secrets/ingest" # guards /v1/*, /api/v1/write
query_token = ["old-token", "new-token"] # guards the read APIs; a list rotates
admin_token = "env:TELEMETRYD_ADMIN" # guards /status and /metricsThree independent tokens, because app servers push, humans read, and dashboards
scrape — different credentials, different rotation. They are not a hierarchy: an admin
token does not grant reads. Comparison is constant-time over SHA-256, so token length
does not leak. Token values never reach a log line, /status, or telemetryd validate
output; that is enforced by the type, not by discipline.
Point it at Cbox ID and it will accept access tokens too, validated against the issuer's published keys rather than by calling the provider — so an identity provider that is down never stops you reading the logs that would explain why:
[auth.oidc]
issuer = "https://acme.cboxid.com"Scopes map to the same three roles. See the single sign-on guide.
Deliberately out of scope in v1, and stated rather than silently absent:
- Automatic certificate issuance. telemetryd can terminate TLS — set
server.tls.cert_fileandkey_file— but it does not obtain or renew certificates. Bring one from your CA, from certbot, or from whatever issues them where you run. A proxy in front remains a fine answer at a public edge, where one usually exists already; the built-in path is for internal deployments that have nowhere to put one. - Per-app tokens. The
applabel is a namespace, not a security boundary. - mTLS, user accounts. Out of frame for a single-team tool.
The write-ahead log defaults to wal_sync = "interval" at 100 ms. On hard power
loss you can lose up to 100 ms of telemetry. That is a deliberate default — per-write
fsync would cap ingest at the device's sync rate — but it is your call:
[storage]
wal_sync = "always" # fsync every batchA crash that tears the log tail is detected at startup, repaired, and reported three
ways: a WARN log, wal_truncations in /status, and
telemetryd_wal_truncations_total. Lost records are never silent.
The same principle covers limits generally. When a cap is hit — cardinality, body size, queue depth — telemetryd rejects loudly with a labelled counter and a structured error, rather than dropping data quietly.
| Endpoint | Auth | Purpose |
|---|---|---|
/healthz |
always open | Liveness. Touches nothing, so it cannot fail for the wrong reason. |
/status |
query token | Disk usage vs budget, WAL stats, recovery events, limits, retention. |
/metrics |
query token | Prometheus exposition of telemetryd's own metrics. |
cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace
cargo deny check # advisories, licenses, bans, sources
python3 scripts/generate-sbom.py --check # SBOM is not stale
python3 scripts/check-docs.py # docs structure, frontmatter, links
cargo bench -p telemetryd-store # measures the constants
cargo test -p telemetryd-store --test scale --release # asserts the asymptoticsPerformance is held in place from two directions: benchmarks measure the constants,
and tests/scale.rs asserts the asymptotics as segment-open counts rather than
wall-clock times — so they mean something on a shared CI runner. See
CI additionally cross-compiles all four release targets
({x86_64,aarch64}-unknown-linux-musl, {x86_64,aarch64}-apple-darwin) and asserts
the musl builds are statically linked — "no glibc surprises" is a constraint, not an
aspiration.
MIT