Monitoring Rust Applications: the tracing Library and Metrics Collection
Monitoring is a key element of production systems. For Rust applications, known for their performance and reliability, it is important to have a lightweight and efficient tool for collecting telemetry. The tracing library has become the de facto standard for observability in the Rust ecosystem. It provides structured logging, spans for request tracing, and integration with metrics collection systems.
What is tracing?
tracing is a framework for instrumenting Rust programs. Unlike classic logging, it operates based on events and spans. Spans represent time intervals of code execution (e.g., processing an HTTP request), while events are points within spans (e.g., "database responded in 10 ms").
Key features:
- Structured logging — each event contains fields (key-value), not just a string.
- Span hierarchy — spans can be nested within each other, tracking the full execution path.
- Async support — works well with tokio and async/await.
- Subscriber — various backends can be attached: stdout, OpenTelemetry, Prometheus, files.
Installation and Basic Usage
Add to Cargo.toml:
[dependencies]tracing = "0.1"tracing-subscriber = "0.3"A simple example of structured logging:
use tracing::{info, warn, error};use tracing_subscriber;
fn main() { // Initialize subscriber for stdout output tracing_subscriber::fmt::init();
let user_id = 42; let request_duration_ms = 150;
info!(user_id, duration = request_duration_ms, "Request processed");
// Logging with warn level warn!(user_id, "Response time exceeds threshold");
// Logging with error error!("Failed to connect to database");}The output will contain fields in a JSON-like string format, simplifying parsing in monitoring systems.
Spans and Tracing
Spans allow measuring operation execution time and linking events into a chain:
use tracing::{span, info, Level};use tracing_subscriber;
fn process_request(user_id: u32) { // Create a span with context let span = span!(Level::INFO, "process_request", user_id); let _guard = span.enter(); // activate the span
info!("Processing started"); std::thread::sleep(std::time::Duration::from_millis(50)); info!("Processing completed");}
fn main() { tracing_subscriber::fmt::init(); process_request(100);}The output will show nested context, indicating that events belong to the process_request span.
Collecting Metrics with tracing and Prometheus
For production monitoring, exporting metrics to Prometheus is often required. Use the tracing-appender crate for file logging or tracing-opentelemetry for OpenTelemetry integration. However, the most direct approach is to use the metrics library together with tracing.
Example integration with metrics-exporter-prometheus:
[dependencies]metrics = "0.21"metrics-exporter-prometheus = "0.12"tracing = "0.1"tokio = { version = "1", features = ["full"] }Code for collecting metrics:
use metrics::{counter, gauge, histogram};use metrics_exporter_prometheus::PrometheusBuilder;use std::net::SocketAddr;
#[tokio::main]async fn main() { // Start HTTP server for Prometheus let addr: SocketAddr = "0.0.0.0:9090".parse().unwrap(); let builder = PrometheusBuilder::new(); builder .wit