# Rust Observability

> When to activate: Rust tracing, logging, metrics, spans, instruments, OpenTelemetry, tracing-subscriber, prometheus, structured logging

- Skill: `mattakushi432/rust-observability` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/rust-observability`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/rust-observability/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/rust-observability

---


# Rust Observability Patterns

## Tracing Setup

```toml
[dependencies]
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
```

```rust
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

pub fn init_tracing() {
    let env_filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("info"));

    tracing_subscriber::registry()
        .with(env_filter)
        .with(tracing_subscriber::fmt::layer().json())
        .init();
}
```

## Structured Logging

```rust
use tracing::{info, warn, error, debug, instrument};

fn process_request(user_id: u64, action: &str) {
    info!(user_id, action, "processing request");
    debug!(user_id, action, details = "extra context", "detailed log");
}

// Span for grouping related log entries
async fn handle_order(order_id: u64) -> anyhow::Result<()> {
    let span = tracing::info_span!("handle_order", order_id);
    let _enter = span.enter();

    info!("started processing");
    let items = fetch_items(order_id).await?;
    info!(item_count = items.len(), "fetched items");
    Ok(())
}

// #[instrument] automatically creates a span
#[instrument(skip(db, password), fields(user_id = ?user.id))]
async fn authenticate_user(db: &DbPool, user: &User, password: &str) -> anyhow::Result<Token> {
    debug!("verifying credentials");
    let token = verify_and_issue(db, user, password).await?;
    info!("authentication successful");
    Ok(token)
}
```

## Metrics with prometheus

```toml
[dependencies]
prometheus = "0.13"
lazy_static = "1"
```

```rust
use prometheus::{IntCounterVec, HistogramVec, register_int_counter_vec, register_histogram_vec};
use lazy_static::lazy_static;

lazy_static! {
    static ref HTTP_REQUESTS: IntCounterVec = register_int_counter_vec!(
        "http_requests_total", "Total HTTP requests",
        &["method", "path", "status"]
    ).unwrap();

    static ref REQUEST_DURATION: HistogramVec = register_histogram_vec!(
        "http_request_duration_seconds", "Request duration",
        &["method", "path"],
        vec![0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0]
    ).unwrap();
}

// Metrics middleware
async fn track_metrics(req: axum::extract::Request, next: axum::middleware::Next) -> axum::response::Response {
    let method = req.method().to_string();
    let path = req.uri().path().to_string();
    let timer = REQUEST_DURATION.with_label_values(&[&method, &path]).start_timer();

    let response = next.run(req).await;

    HTTP_REQUESTS.with_label_values(&[&method, &path, &response.status().as_u16().to_string()]).inc();
    timer.observe_duration();
    response
}

// Prometheus scrape endpoint
async fn metrics_handler() -> String {
    use prometheus::{Encoder, TextEncoder};
    let encoder = TextEncoder::new();
    let mut buffer = Vec::new();
    encoder.encode(&prometheus::gather(), &mut buffer).unwrap();
    String::from_utf8(buffer).unwrap()
}
```

## Health Check

```rust
use axum::response::Json;
use serde::Serialize;

#[derive(Serialize)]
struct Health {
    status: &'static str,
    version: &'static str,
}

async fn health_check(State(state): State<AppState>) -> (axum::http::StatusCode, Json<Health>) {
    let db_ok = state.db.ping().await.is_ok();
    let status = if db_ok { "healthy" } else { "degraded" };
    let code = if db_ok { axum::http::StatusCode::OK } else { axum::http::StatusCode::SERVICE_UNAVAILABLE };
    (code, Json(Health { status, version: env!("CARGO_PKG_VERSION") }))
}
```

## Log Levels and Filtering

```bash
# Set log level via env var
RUST_LOG=info cargo run
RUST_LOG=my_crate=debug,tower_http=warn cargo run

# JSON logs for production
LOG_JSON=1 cargo run
```

## Common Anti-Patterns

- **`println!` / `eprintln!` in production** — use `tracing::info!` / `tracing::error!`
- **Logging entire request bodies** — they may contain secrets; log IDs and summaries only
- **`unwrap()` on metric registration** — register metrics at startup; panics surface immediately
- **Missing span context in spawned tasks** — use `#[instrument]` or pass spans explicitly
- **No log level filtering** — always configure `EnvFilter`; logging everything creates noise and cost

