Configure Logging

Enable log levels per module

log env_logger cat-development-tools::debugging

Creates two modules foo and nested foo::bar with logging directives controlled separately with RUST_LOG⮳ environmental variable.

mod foo {
    mod bar {
        pub fn run() {
            log::warn!("[bar] warn");
            log::info!("[bar] info");
            log::debug!("[bar] debug");
        }
    }

    pub fn run() {
        log::warn!("[foo] warn");
        log::info!("[foo] info");
        log::debug!("[foo] debug");
        bar::run();
    }
}

fn main() {
    env_logger::init();
    log::warn!("[root] warn");
    log::info!("[root] info");
    log::debug!("[root] debug");
    foo::run();
}

The RUST_LOG environment variable controls env-logger⮳ output. Module declarations take comma separated entries formatted like path::to::module=log_level. Run the test application as follows:

RUST_LOG="warn,test::foo=info,test::foo::bar=debug" test

Sets the default log::Level⮳ to warn, module foo and module foo::bar to info and debug.

WARN:test: [root] warn
WARN:test::foo: [foo] warn
INFO:test::foo: [foo] info
WARN:test::foo::bar: [bar] warn
INFO:test::foo::bar: [bar] info
DEBUG:test::foo::bar: [bar] debug

Use a custom environment variable to set up logging

log env_logger cat-development-tools::debugging

env_logger::Builder⮳ configures logging.

env_logger::Builder::parse⮳ parses MY_APP_LOG environment variable contents in the form of RUST_LOG⮳ syntax. Then, env_logger::Builder::init⮳ initializes the logger. All these steps are normally done internally by env_logger::init⮳.


fn main() {
    init_logger();

    log::info!("informational message");
    log::warn!("warning message");
    log::error!("this is an error {}", "message");
    if log::log_enabled!(log::Level::Info) {
        let x = 3 * 4; // "Expensive" computation
        log::trace!("the answer was: {}", x);
    }
}

//#[cfg(not(test))]
fn init_logger() {
    // env_logger is a simple logger that can be configured via environment
    // variables. Example: RUST_LOG=info ./app
    // Typically you would use:
    // env_logger::init();

    // Initialise a logger with filter level Off,
    // then override the log filter from an environment variable called
    // MY_APP_LOG:
    env_logger::Builder::new()
        .filter_level(log::LevelFilter::Off)
        .parse_env("MY_APP_LOG")
        .init();

    // Alternatively, `Env` lets us tweak what the environment
    // variables to read are and what the default
    // value is if they're missing
    // let env = env_logger::Env::default()
    // // Specify an environment variable to read the filter from.
    // // If the variable is not set, the default value will be used.
    // .filter_or("MY_APP_LOG", "trace")
    // .write_style_or("MY_APP_LOG_STYLE", "always");
    // env_logger::init_from_env(env);
}

Include a timestamp in log messages

log env_logger chrono cat-development-tools::debugging

Creates a custom logger configuration with env_logger::Builder⮳ Each log entry calls chrono::offset::Local::now⮳ to get the current chrono::DateTime⮳ in local timezone and uses chrono::DateTime::format⮳ with chrono::format::strftime⮳ to format a timestamp used in the final log.

The example calls env_logger::Builder::format⮳ to set a closure which formats each message text with timestamp, log::Record::level⮳ and body (log::Record::args⮳).

use std::io::Write;

use chrono::Local;
use env_logger::Builder;
use log::LevelFilter;

fn main() {
    Builder::new()
        .format(|buf, record| {
            writeln!(
                buf,
                "{} [{}] - {}",
                Local::now().format("%Y-%m-%dT%H:%M:%S"),
                record.level(),
                record.args()
            )
        })
        .filter(None, LevelFilter::Info)
        .init();

    log::warn!("warn");
    log::info!("info");
    log::debug!("debug");
}

stderr output will contain

2017-05-22T21:57:06 [WARN] - warn
2017-05-22T21:57:06 [INFO] - info

Log messages to a custom location

log log4rs cat-development-tools::debugging

log4rs⮳ configures log output to a custom location. log4rs⮳ can use either an external YAML file or a builder configuration.

Create the log configuration with log4rs::append::file::FileAppender⮳ An appender defines the logging destination. The configuration continues with encoding using a custom pattern from log4rs::encode::pattern⮳ Assigns the configuration to log4rs::config::Config⮳ and sets the default log::LevelFilter

use anyhow::Result;
use log::LevelFilter;
use log4rs::append::file::FileAppender;
use log4rs::config::Appender;
use log4rs::config::Config;
use log4rs::config::Root;
use log4rs::encode::pattern::PatternEncoder;

fn main() -> Result<()> {
    let logfile = FileAppender::builder()
        .encoder(Box::new(PatternEncoder::new("{l} - {m}\n")))
        .build("temp/log/output.log")?;

    let config = Config::builder()
        .appender(Appender::builder().build("logfile", Box::new(logfile)))
        .build(Root::builder().appender("logfile").build(LevelFilter::Info))?;

    log4rs::init_config(config)?;

    log::info!("Hello, world!");

    Ok(())
}