🎓 A1Learner
🔍

Rust Log Filters in Practice

A log filter decides which log lines print. A filter can set one level for the whole program, or a louder level for one module only.

The shape of a filter

A filter is a list of rules. Each rule names a module path and a level:

RUST_LOG=warn,logfilter=info,logfilter::db=debug

One module, turned up

Run the closing program with only the db module turned up. Its two lines are the whole log output. Even the warn! line in main stays hidden:

$ RUST_LOG=logfilter::db=debug cargo run

A filter built into the code

default_filter_or names the filter to use when RUST_LOG is missing. A set RUST_LOG still wins:

let env = env_logger::Env::default()
    .default_filter_or("warn,logfilter=info,logfilter::db=debug");

The target is the module path

record.target() holds the module path of the line. This format prints it in square brackets:

writeln!(buf, "{} [{}] {}", record.level(), record.target(), record.args())

Restart to change your mind

This debug! line sits in main. The built-in filter holds main at info, so the line stays hidden. Restart with RUST_LOG=logfilter=debug and it appears. No rebuild is needed:

debug!("counting the till");

Example

// Cargo.toml: log = "0.4" (resolved 0.4.34), env_logger = "0.11" (resolved 0.11.11)
use log::{debug, info, warn};
use std::io::Write;

mod db {
    use log::{debug, info};

    pub fn save() {
        debug!("writing 3 rows to disk");
        info!("save finished");
    }
}

mod report {
    use log::{debug, info};

    pub fn build() {
        debug!("sorting 40 rows");
        info!("report ready");
    }
}

fn main() {
    let env = env_logger::Env::default()
        .default_filter_or("warn,logfilter=info,logfilter::db=debug");
    env_logger::Builder::from_env(env)
        .format(|buf, record| {
            writeln!(buf, "{} [{}] {}", record.level(), record.target(), record.args())
        })
        .init();

    info!("shop opened");
    db::save();
    report::build();
    warn!("till is low on coins");
    debug!("counting the till");
}

Key points

  • Filter: the setting that decides which log lines print.
  • It can set one level for the whole program.
  • Or one level per module path
  • Target: the name a filter rule matches.
  • For log lines it is the module path.
  • It starts with the crate name
  • The longest matching rule wins: logfilter::db=debug beats logfilter=info for lines written in the db module
  • RUST_LOG is read once when the logger starts: change it and restart the program to see more or less
  • Use default_filter_or to give the program a built-in filter: it applies only while RUST_LOG is not set

In this example, the crate is called logfilter, so its module paths start with that name. The built-in filter has three rules: warn for the world, info for the crate, debug for the db module. Read the output top to bottom. The shop opened line is info from the crate root, so it prints. The db module writes a debug line and an info line, and both print. The report module writes a debug line about sorting, and it stays hidden. report matches the crate rule, info, not the db rule. Its info line prints. The warn line prints under every rule. The last debug line, counting the till, sits in the crate root at info, so it stays hidden. Run the same binary with RUST_LOG=logfilter::db=debug and only the two db lines print. Run it with RUST_LOG=debug and all seven lines print. The code never changes.

Note

A rule for a library names the library's own crate.

Its lines obey your filter.

Their targets start with its crate name, not yours.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

You run a program with RUST_LOG=shop=info,shop::db=debug. A debug! line in the shop::report module stays hidden. Why?