RUST Contents

Logging First Steps

Introduce production-friendly logging in Rust: structured fields, consistent levels, request correlation, and safe redaction so logs remain useful under real incident pressure.

On this page

Why Logging Is Not Just println!

In production, logs are not for developers reading a terminal. Logs are for incident response, debugging under time pressure, and automation (search, alerts, dashboards). That requires consistency: structured fields, stable log levels, and correlation identifiers.

Rust gives you great performance, but logging discipline determines whether you can operate the service when something breaks.

Pick a Logging Stack: log + env_logger (Minimal) vs tracing (Modern)

There are two common approaches:

  • log facade + a backend like env_logger: simple and widely supported
  • tracing: structured, span-based, better for request flows and modern observability

For production services, tracing is usually the better default. It scales from basic logs to distributed tracing without changing your instrumentation style.

Minimal Setup with tracing

Add dependencies:

# Cargo.toml
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json"] }

Initialize once at startup (main.rs/app.rs):

use tracing_subscriber::{fmt, EnvFilter};

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

    fmt()
        .with_env_filter(filter)
        .json()            // structured output for log pipelines
        .with_current_span(true)
        .with_target(false)
        .init();
}

Control verbosity at runtime:

RUST_LOG=info ./my_service
RUST_LOG=my_service=debug,hyper=warn ./my_service

Use Levels Correctly

Log levels are a contract. If everything is info, nothing is useful.

  • error: request failed, operation failed, needs attention
  • warn: unexpected but handled, degraded behavior
  • info: normal lifecycle events (startup, listening, migrations complete)
  • debug: diagnostic details (inputs sanitized, branch decisions)
  • trace: extremely noisy, rarely enabled in production
use tracing::{info, warn, error};

info!(port = 8080, "server starting");
warn!(component = "db", "slow query detected");
error!(err = %e, "request failed");

Structured Fields: Log for Machines, Not Humans

Production logging should include fields that are easy to query: request_id, user_id (if safe), route, status_code, latency_ms.

use tracing::info;

info!(
    method = "GET",
    path = "/health",
    status = 200,
    latency_ms = 2,
    "request completed"
);

This becomes powerful in log search systems because you can filter on fields instead of parsing strings.

Correlation: Request IDs

Without correlation, a single incident creates thousands of unrelated log lines. Add a request_id early and propagate it through logs.

At minimum, generate one per request and attach it as a field. In web frameworks (Axum, Actix), you typically do this in middleware, but the principle is the same: every log line in a request should carry the request_id.

use tracing::info;

pub fn log_with_request_id(request_id: &str) {
    info!(request_id = request_id, "handling request");
}

Redaction: Never Log Secrets

Logs often end up in third-party systems and long retention storage. Treat them as sensitive.

Never log:

  • Passwords, tokens, API keys
  • Full database URLs if they contain credentials
  • Authorization headers
  • Raw user input that may contain secrets

Bad:

tracing::info!(db_url = %config.database_url, "config loaded");

Better:

tracing::info!(db_host = "db.internal", "config loaded");

Startup Logs: The Minimum Useful Set

Production services should log a small set of high-signal events at startup:

  • Service name/version
  • Listening address/port
  • Runtime mode (dev/staging/prod)
  • Important toggles (without secrets)
use tracing::info;

info!(
    service = "my_service",
    version = env!("CARGO_PKG_VERSION"),
    port = 8080,
    "startup complete"
);

Logging Hygiene Under Load

Logging is I/O. Under heavy load, overly chatty logs can become a performance problem and increase cost. Production hygiene means:

  • Keep per-request info logs minimal (or sample if needed)
  • Use debug logs for detailed diagnostics
  • Prefer structured fields over long message strings

Production Checklist

  • Logging initialized once at startup
  • RUST_LOG controls verbosity
  • Structured JSON logs enabled for pipelines
  • Correct level usage (error/warn/info/debug)
  • Request correlation concept in place
  • Secrets and sensitive data never logged

Good logging is an operational feature. Once your service is in production, it becomes your fastest feedback loop during incidents. Start with structure and discipline from day one.