Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Metrics

At a glance

  • Builds a periodic meter provider that exports metrics to stdout.
  • Uses opentelemetry 0.33.1, opentelemetry_sdk 0.33.1, and opentelemetry-stdout 0.33.1.
  • The book’s pinned versions are: opentelemetry 0.33.1, opentelemetry_sdk 0.33.1, opentelemetry-otlp 0.33.1, opentelemetry-stdout 0.33.1, opentelemetry-appender-tracing 0.33.1, tracing-opentelemetry 0.34.0, tracing 0.1.44, tracing-subscriber 0.3.23, tokio 1.54.1, rusqlite 0.42.0, and libsql 0.10.0.
  • Run it with cargo run -p metrics.
  • The final line is ok: metrics exported.

Dependencies

[package]
name = "metrics"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
opentelemetry = "0.33.1"
opentelemetry_sdk = { version = "0.33.1", features = ["metrics"] }
opentelemetry-stdout = { version = "0.33.1", features = ["metrics"] }

Code

use std::io::{self, Write};
use std::time::Duration;

use opentelemetry::metrics::MeterProvider;
use opentelemetry::KeyValue;
use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider};
use opentelemetry_stdout::MetricExporter;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let exporter = MetricExporter::builder().build();
    let reader = PeriodicReader::builder(exporter)
        .with_interval(Duration::from_secs(5))
        .build();
    let provider = SdkMeterProvider::builder().with_reader(reader).build();
    let meter = provider.meter("metrics-example");

    let requests = meter.u64_counter("requests").build();
    requests.add(1, &[KeyValue::new("route", "/health")]);

    let duration = meter.f64_histogram("request.duration").build();
    duration.record(0.125, &[KeyValue::new("route", "/health")]);

    let active = meter.i64_up_down_counter("active.connections").build();
    active.add(1, &[KeyValue::new("service", "metrics")]);
    active.add(-1, &[KeyValue::new("service", "metrics")]);

    provider.shutdown()?;
    io::stdout().write_all(b"ok: metrics exported\n")?;

    Ok(())
}

Run it

Run:

cargo run -p metrics

The stdout exporter prints the collected metrics. The last line is:

ok: metrics exported

How it works

The program creates a stdout metric exporter.

A PeriodicReader collects metrics at a fixed interval. This example uses a five-second interval. The final provider shutdown also flushes the last collection, so the program does not need to wait for the interval.

The SdkMeterProvider owns the reader. A meter created from the provider creates instruments.

The u64 counter records values that only increase, such as completed requests.

The f64 histogram records measurements such as request duration. Its values can vary in either direction.

The up-down counter records values that can increase or decrease, such as the number of active connections.

Each recording includes attributes. Attributes add dimensions to a metric. Keep their values bounded. A user ID or request ID can create excessive cardinality and increase memory use and export size.

Temporality describes how a reader reports measurements. Cumulative temporality reports totals from the start of the instrument’s lifetime. Delta temporality reports the change since the previous collection. The exporter and reader determine the supported behavior.

Use a counter for a total, a histogram for a distribution, and an up-down counter for a current value that moves in both directions. Use a gauge when you need to observe a value directly rather than record changes, though this example does not build one.

The provider is shut down before the final status line. This gives the reader a chance to export the last collection.

Pitfalls

  • Forgetting provider shutdown can lose measurements that are still waiting for the next export interval.
  • Choosing a counter for a value that can decrease produces an incorrect metric.
  • Recording unbounded attributes creates high cardinality.
  • Setting the export interval too high delays output; setting it too low increases export work.
  • A histogram does not automatically provide useful bucket boundaries for every workload. Check the exported data and configure aggregation when the defaults do not fit.
  • The stdout exporter is useful for local inspection. It is not a remote collector endpoint.