Skip to content
rusthacks.dev
Tooling

Logging in Rust With the log Crate and Env Logger Explained

The log crate is a facade and env_logger is one backend. Set it up, pick levels by RUST_LOG, log with lazy arguments and learn why libraries only depend on log.

2 min read
Summarize in AI
Flat illustration for the post “Logging in Rust With the log Crate and Env Logger Explained”, composed around cargo.

You added info!("started"), ran the program, and saw nothing. That is the most common first experience with Rust logging, and the cause is that log is a facade: the macros call into whatever backend you installed, and with none installed they are no-ops. This design is what lets libraries log without forcing a choice. This post sets up the common pair and shows the filter syntax. For richer needs, see structured logging with tracing.

Rust 1.90, log 0.4, env_logger 0.11.

Setup

Cargo.tomlTOML
[dependencies]
log = "0.4"
env_logger = "0.11"
src/main.rsrust
use log::{debug, error, info, warn};

fn main() {
    env_logger::init();
    info!("starting");
    debug!("config loaded from {}", "app.toml");
    warn!("cache is cold");
    error!("could not reach {}", "db.example.test");
}
Shell
RUST_LOG=debug cargo run
Text
[2026-11-03T09:12:01Z INFO  demo] starting
[2026-11-03T09:12:01Z DEBUG demo] config loaded from app.toml
[2026-11-03T09:12:01Z WARN  demo] cache is cold
[2026-11-03T09:12:01Z ERROR demo] could not reach db.example.test

The timestamp will differ. Without RUST_LOG, only the error! line appears.

The filter syntax

RUST_LOGMeaning
infoeverything at info and above
demo=debugthe demo target at debug, others at error
warn,demo::db=tracewarn globally, trace for one module
demo=debug,hyper=offsilence one noisy dependency

The target defaults to the module path, so demo::db matches log calls from that module.

Levels

error for things that need attention, warn for recoverable surprises, info for lifecycle events, debug for developer detail, trace for very fine detail. What to log is a design question treated in the logging guidance in the tracing post; a good test for info is whether an operator reading it learns something.

Cost

rust
debug!("payload: {:?}", expensive_to_format());

The macro checks the level first and formats only if enabled, but the argument expression runs only inside that check too, because the arguments live inside the macro's branch. For a computation you would otherwise do anyway, no overhead. To guard an explicit computation use if log_enabled!(log::Level::Debug) { ... }.

Libraries

A library depends on log only:

Cargo.tomlTOML
[dependencies]
log = "0.4"

It never calls env_logger::init(); that is the application's job. A library that initializes a backend panics when the application also does ("a logger has already been set").

Where to go next

log has no spans, no async awareness and limited structured fields. tracing supersedes it for services, and tracing-log bridges the two so dependencies that use log still show up. For command line tools, env_logger is plenty, and the --verbose flag convention from exit codes and CLI behavior maps naturally to levels. The crates are documented at docs.rs/log and docs.rs/env_logger.

Taggedlog
Share

Keep reading

Flat illustration for the post “Incident Response for Rust Services a Short Runbook”, composed around deploy.
DevOps

Incident Response for Rust Services a Short Runbook

A one-page runbook for when a Rust service misbehaves: stabilize first, check recent changes, read the four golden signals, find the failing dependency, and use the Rust-specific evidence: panics, memory, executor stalls.

4 min read
Flat illustration for the post “Debugging Async Backtraces and Task Dumps in Tokio”, composed around async.
Tooling

Debugging Async Backtraces and Task Dumps in Tokio

A hung async service has no useful thread backtrace. Tokio task dumps capture the await stack of every task. Enable tokio_unstable and taskdump, trigger a dump from a signal or endpoint, and read where each task is parked.

4 min read

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.