Skip to content

Metrics

Inject Arc<OpenTelemetryMeter> and build counters, histograms or gauges; readings batch on a PeriodicReader and export through the same OTLP endpoint.

Metrics are the aggregate counterpart to traces — reach for them when you need a number over time (signups per minute, job duration percentiles, queue depth) rather than the story of one request.

OpenTelemetryModule provides Arc<OpenTelemetryMeter> — the OTel global meter, wrapped so it flows through the DI graph. Inject it like any other dependency.

Inject the meter, then build the instrument from it. Arc<OpenTelemetryMeter> derefs to the inner Meter; the meter caches instruments by name, so building the counter at the call site is cheap and needs no extra field.

crates/features/src/users/metrics.rs
use std::sync::Arc;
use nest_rs::core::injectable;
use nest_rs::opentelemetry::OpenTelemetryMeter;
#[injectable]
pub struct SignupMetrics {
#[inject]
meter: Arc<OpenTelemetryMeter>,
}
impl SignupMetrics {
/// Call this after a successful signup.
pub fn record_signup(&self) {
self.meter.u64_counter("users.created").build().add(1, &[]);
}
}

Counters, histograms, gauges — anything the opentelemetry meter exposes works. Inject Arc<OpenTelemetryMeter> into the service that owns the event and build the instrument lazily inside the method, as above.

The second argument to .add(...) / .record(...) is a slice of OTel attributes — the metric’s labels at the OTel/Prometheus boundary.

crates/features/src/users/metrics.rs
use opentelemetry::KeyValue;
self.meter.u64_counter("users.created").build().add(1, &[
KeyValue::new("org_id", org_id.to_string()),
KeyValue::new("source", "api"),
]);

Stick to a small, bounded cardinality on the label set — every distinct attribute combination becomes a separate time series.

Metrics batch on a PeriodicReader and export through the same OTLP endpoint as the traces (set NESTRS_OPENTELEMETRY__OTLP_ENDPOINT). Without an endpoint, instrument construction still works — counter() and friends return real handles, so wiring compiles and runs — but the meter comes from a NoopMeterProvider and every reading is dropped. The SDK says so once, at Meter was obtained from a NoopMeterProvider. No metrics will be recorded. A test that needs to assert on an increment has to hold its own counter; there is nothing to read back here.

The interval is 60 seconds by default, and that will look broken. Traces and logs reach a collector immediately; metrics wait for the reader’s next flush. The natural first check — wire a meter, hit the route, look at the collector — shows spans and log records with zero metrics, for a full minute. Nothing is wrong; shorten the interval for a local run:

Terminal window
NESTRS_OPENTELEMETRY__METRIC_INTERVAL_SECS=5 nestrs run dev api

or pin it in code, as with any other field:

apps/api/src/module.rs
OpenTelemetryConfig::from_env("api").with_metric_interval(Duration::from_secs(5))

A value of 0 is the sentinel for “keep the 60 s default” — a PeriodicReader on a zero period is a tight export loop, which is worse than a slow one. A non-numeric value is a typo, not a sentinel: the default is kept and the variable is named on stderr (nestrs: WARNING — unparseable NESTRS_OPENTELEMETRY__METRIC_INTERVAL_SECS=…). It is reported rather than boot-fatal because this config is what installs the subscriber, so it is built before there is anywhere to log or anything to propagate an error to.

Every exported metric carries the service-level resource attributes built from OpenTelemetryConfig:

  • service.name — argument to OpenTelemetry::init
  • service.version — NESTRS_OPENTELEMETRY__SERVICE_VERSION
  • deployment.environment — NESTRS_OPENTELEMETRY__SERVICE_ENVIRONMENT
  • service.instance_id — NESTRS_OPENTELEMETRY__SERVICE_INSTANCE_ID (a fresh UUIDv7 per process by default, so restarts get distinct identities in the backend)

These come from the opentelemetry-semantic-conventions schema, so any backend that understands OTel semconv (Grafana Cloud, Honeycomb, Datadog OTLP, …) keys on them out of the box.

  • Traces — the per-request story metrics aggregate.
  • Logs — structured events on the same OTLP endpoint.
  • OpenTelemetry — the meter provider and OTLP export.