tracing
tracing provides structured, contextual logging for async Rust. Spans follow requests across tasks; fields are typed and filterable at runtime.
Recipe
[dependencies]
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }use tracing::{info, instrument};
#[instrument]
async fn fetch_user(user_id: u64) -> String {
info!(%user_id, "fetching user");
format!("user-{user_id}")
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_env_filter("info")
.init();
fetch_user(42).await;
}When to reach for this:
- Production services needing request IDs and latency fields
- Async code where
println!loses ordering - OpenTelemetry export via
tracing-opentelemetry
Working Example
use tracing::{info_span, Instrument};
async fn work() {
let span = info_span!("job", job_id = 7);
async {
tracing::info!("started");
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
tracing::info!("done");
}
.instrument(span)
.await;
}What this demonstrates:
- Spans nest and attach metadata
.instrument(span)propagates context across.await- Subscribers format output (text or JSON)
Deep Dive
Layers
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(tracing_subscriber::EnvFilter::from_default_env())
.init();Set RUST_LOG=info,my_crate=debug in production for targeted verbosity.
Cross-Link
See tracing in Tokio and Observability for metrics and OTLP.
Gotchas
- Initializing subscriber twice - panics in tests. Fix:
OnceLockortracing_subscriber::try_init(). - Huge span fields - logging full payloads is expensive. Fix: log ids and sizes.
- Missing
.instrumenton spawned tasks - broken parent links. Fix: instrument beforetokio::spawn. println!in libraries - bypasses filters. Fix:tracingmacros only.- Debug in hot loops - guard with
tracing::enabled!or level checks.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
log facade | Supporting older crates | Greenfield without bridge |
env_logger | Tiny CLIs | Multi-thread async services |
slog | Legacy code | New Tokio stacks |
FAQs
tracing vs log crate?
tracing is a superset with spans. Many crates use log; add tracing-log bridge.
JSON logs for Loki/Datadog?
Use tracing_subscriber::fmt::layer().json() or dedicated JSON layer.
How do I trace Axum requests?
tower-http TraceLayer plus tracing on handlers. See Axum service skill.
Performance cost?
Disabled spans are cheap. Enabled structured logging costs IO - sample in extreme paths.
Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.