Metrics
At a glance
- Builds a periodic meter provider that exports metrics to stdout.
- Uses
opentelemetry0.33.1,opentelemetry_sdk0.33.1, andopentelemetry-stdout0.33.1. - The book’s pinned versions are:
opentelemetry0.33.1,opentelemetry_sdk0.33.1,opentelemetry-otlp0.33.1,opentelemetry-stdout0.33.1,opentelemetry-appender-tracing0.33.1,tracing-opentelemetry0.34.0,tracing0.1.44,tracing-subscriber0.3.23,tokio1.54.1,rusqlite0.42.0, andlibsql0.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.