What is tracing? (2019 rustc 1.65+)

Rust crate for structured, contextual logging and distributed tracing.
Unlike plain text log lines, it records events (log messages with typed fields) inside spans (named scopes that follow a request or task through async code).
It is widely used with async Rust (tokio, axum, tonic) because spans survive across .await points.
Crates: tracing (macros + span API), tracing-subscriber (formatters, filters, layers), tracing-appender (file rotation). Often paired with tracing-opentelemetry to export traces to Jaeger, Tempo, etc.

Simple example

use tracing::{info, instrument};
use tracing_subscriber;

#[instrument] // creates a span named "log" with args user_id
async fn log(user_id: u64) {
    info!(user_id, "processing request");
    // ... work ...
    info!("request finished");
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt()
        .with_env_filter("info")
        .init();

    log(42).await;
}

Sample output (JSON mode is also supported):

2026-07-18T10:00:00.123Z  INFO log{user_id=42}: processing request
2026-07-18T10:00:00.125Z  INFO log{user_id=42}: request finished

The span name and user_id field appear on every line inside log, so you can grep or query logs by request context without repeating fields in every message.

Spans vs events

Concept Analogy Macro examples
Event A single log line info!, warn!, error!
Span A scope / operation with start and end #[instrument], span!(...)

Spans can be nested (HTTP handler → DB query → cache lookup). Subscribers can export span trees for latency analysis, not just flat text logs.

How tracing suitable for Async runtimes(eg: tokio)

What async does?
A single OS thread may run task A -> task B (.await) -> task A — every time a task hits .await and is parked. .await loses track of which request or task produced a message.
How tracing helps here? A span is attached to the logical task, not just the thread.
When Tokio polls a future again after an .await, the span context is re-entered, so every log line inside that handler still carries the same request id, trace id, and parent span — even if the work ran on three different threads.

Why log and slog poor fit across async runtimes

We can call log::info! or slog::info! from an async fn. The problem is context does not follow .await automatically.

Which is best? Which is newest?

Newest (among these): tracing (2019). Active development continues in the Tokio project.
Best depends on your project:

Scenario Recommendation
Small CLI, quick debug output log + env_logger
Need Log4j-style config files on log log4rs
Structured JSON logs, sync or legacy codebase slog (still solid; check team familiarity)
Async web/service, request context, OpenTelemetry tracing + tracing-subscriber

There is no single “best” crate — log remains the common denominator (many libraries emit log events). Production async services in 2026 usually standardize on tracing and bridge older log output with tracing-log when needed.