Skip to content

Metrics with OpenTelemetry

Turn on OTLP metrics, what is stored, reading them by aggregation, filter and group, the series limits that keep cost predictable, privacy and metric alerts.

Markdown
On this page

Traces show one request and logs show what the code said. Metrics show a number over time: requests per second, memory in use, jobs waiting in a queue, free connections in a pool. Belvador takes them from the standard OpenTelemetry SDKs and the Collector over OTLP/HTTP, stores one point a minute on your own node, and lets you chart them and alert on them.

Metrics are off by default. They cost requests and have their own monthly budget, so you turn them on where you want them.

Turn them on#

  1. Ingest key. In Project settings → Ingest keys, tick Metrics on the key your services use (or create a new key with it). A key without it is answered 403.

  2. Exporter. In the standard variables, switch the metric exporter on:

Terminal
export OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.belvador.dev/otlp/<project-id>export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobufexport OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer%20<your-ingest-key>export OTEL_TRACES_EXPORTER=otlpexport OTEL_LOGS_EXPORTER=otlpexport OTEL_METRICS_EXPORTER=otlpexport OTEL_METRIC_EXPORT_INTERVAL=60000export OTEL_SERVICE_NAME=my-serviceexport OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=production

Export once a minute (60 s): Belvador keeps one point a minute, so exporting more often only adds requests. Both temporalities work — cumulative (the default of every SDK) and delta.

  1. Your node stores metrics from agent 0.1.37 on; nodes update themselves. Until then, metrics wait at the edge, encrypted to your node's key, and are delivered after the update.

Tested on 2026-10-27 with otelcol-contrib 0.163.1, telemetrygen v0.163.0: traces passed, logs passed, metrics passed.

The Collector run sends one metric of each type below and reads it back from the node. OpenTelemetry SDKs send the same OTLP format, but Belvador has not run their metric exporters yet, so they are not marked tested for metrics. Recipes for host, queue, business and Supabase metrics: Metrics recipes.

What is stored#

OTLP typeStored asTypical instruments
Gaugea levelObservableGauge, system.cpu.load_average.1m
Sum, monotonica counter (increments)Counter, system.network.io, app.signups
Sum, not monotonica level (cumulative) or increments (delta)UpDownCounter, system.memory.usage
Histograma distribution with its bucketsHistogram, http.server.request.duration
Exponential histograma distribution with its bucketsSDKs configured for base-2 buckets

Summary points are not stored; they are counted as Unsupported metric type (every current SDK exports histograms instead). Cumulative counters are turned into per-minute increments on your node; a counter that restarts (after a deploy) counts on from its new value instead of showing a negative drop.

Read them#

The Metrics page lists the metrics your project sent, with their type, unit and number of series. Pick one, then:

  • Aggregation — avg, sum, min, max, count, rate (per second, for counters and histograms), or p50/p90/p95/p99 (histograms). A counter opens as a rate, a histogram as p95, a level as the average.

  • Filters — up to 5 attribute conditions, the same as in Traces and Logs.

  • Group by — up to 2 attribute keys; each value set is one line on the chart (the first 50 groups are drawn).

  • Step — one point per minute up to one per day. One query covers at most 7 days and 10,000 points.

Histogram points carry exemplars when your SDK records them: the trace of one request in that bucket. Click one to open the trace.

Limits that keep cost predictable#

A series is one metric name with one set of attribute values. The number of series is what costs disk and memory, so your node caps it:

Default plan
Active series per node (seen in the last 24 h)10,000
Metric names per node1,000
Attributes per series32, values up to 256 characters
Kept for14 days, one point a minute
Monthly volume2 GB (compressed request bodies), separate from traces and logs

Points of a new series above the cap are not stored and counted as Over the series limit; series that already exist keep working, and the Metrics page names the metrics that hit the cap. The usual cause is an attribute with unbounded values — a user id, an order id, a full URL. Keep those on spans and logs, and give metrics bounded attributes (http.route, not the URL).

The Metrics page shows how many series and names your node holds against these caps; retention and the monthly volume are in Project settings → Traces, logs and metrics received.

Privacy#

Metric attributes go through the same privacy rules as span attributes before a series is formed: a removed attribute never becomes part of a series, and a hashed one groups by its hash. Values are stored only on your node. Belvador's database receives counts and sizes per day, never names, values or attribute values.

Alert on a metric#

Create a Metric monitor in Monitors → Create monitor, or press Create monitor on the Metrics page to carry the current query over. The monitor aggregates one metric over a window (for example p95 of http.server.request.duration over 5 minutes above 0.8 s) and alerts when it is above, below or outside a range. With group by, each value set is judged on its own, so one slow route alerts without the others. See Signal monitors.

From the API or an AI agent#

GET /v1/metrics/names, GET /v1/metrics/series and GET /v1/metrics/query read the same data with a token that has metrics:read; the MCP tool query_metrics lists metrics without a name and queries one with a name. See the API reference and MCP.

What it is not#

  • No browser metrics. Ingest keys are secret; web vitals come later through the browser SDK, not through OTLP.

  • No Prometheus endpoint. Prometheus and other sources are read by the Collector and forwarded as OTLP.

  • No long-term storage. Points are kept for the plan's retention at one-minute resolution; there is no downsampling to keep years of data.

Spotted something wrong or missing? Tell us: hello@belvador.dev