Skip to content

Metrics

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.

use nest_rs_core::injectable;
use nest_rs_opentelemetry::OpenTelemetryMeter;
use opentelemetry::metrics::Counter;
use std::sync::Arc;
#[injectable]
pub struct UsersService {
#[inject]
meter: Arc<OpenTelemetryMeter>,
created: Counter<u64>,
}
impl UsersService {
pub async fn create(&self, input: CreateUser) -> anyhow::Result<User> {
let user = self.repo.insert(input).await?;
self.created.add(1, &[]);
Ok(user)
}
}

Counters, histograms, gauges — anything the opentelemetry meter exposes works. Arc<OpenTelemetryMeter> derefs to the inner Meter, so self.meter.u64_counter("users.created").build() is the canonical construction pattern when you want to build instruments lazily inside an impl block.

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

use opentelemetry::KeyValue;
self.created.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 but the readings stay local — useful for tests that want to assert on counter increments without spinning a collector.

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

  • service.name — argument to OpenTelemetry::init
  • service.versionNESTRS_OPENTELEMETRY__SERVICE_VERSION
  • deployment.environmentNESTRS_OPENTELEMETRY__SERVICE_ENVIRONMENT
  • service.instance_idNESTRS_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.