Logging First Steps
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 attentionwarn: unexpected but handled, degraded behaviorinfo: 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.