env_logger

repository·main·Indexed 21 days ago

https://github.com/rust-cli/env_logger

A Rust logger implementation for executable projects that allows configuring logging verbosity and targets via environment variables, primarily using the RUST_LOG variable. It includes the env_filter crate for dynamic runtime filtering based on module paths and log levels (error, warn, info, debug, trace, off). The library provides a Builder for programmatic configuration, support for custom output formatting, and the ability to redirect logs from stderr to stdout.

Tokens
7.7K
Snippets
28
Records
39
Agent score
76%

What's inside env_logger

  1. Note on output format stability

    main
    The default output format of env_logger is not guaranteed to be stable across major, minor, or patch version bumps during the 0.x release cycle. If you need to programmatically parse or interpret log output, you should implement a custom format.
  2. Initialize env_logger in an executable

    main

    In an executable, env_logger should be initialized as early as possible in the main function. Once initialized, you can use macros from the log crate (like info!, warn!, error!, etc.) to emit log messages.

    Control the visibility of these messages at runtime using the RUST_LOG environment variable.

    use log::info;
    
    fn main() {
        env_logger::init();
    
        info!("starting up");
    
        // ...
    }
  3. Use env_logger in tests

    main

    To see log messages during testing, add env_logger as a development dependency and initialize it within your tests using the builder pattern with .is_test(true).

    Note:

    1. env_logger::try_init() must be called in each test where logging is desired.
    2. Because tests run in parallel by default, output may be interleaved. To avoid this, run tests with RUST_TEST_THREADS=1 or run a specific test individually.

    To filter logs for a specific module during tests, use the format RUST_LOG=module_name=level cargo test.

    use log::info;
    
    fn add_one(num: i32) -> i32 {
        info!("add_one called with {}", num);
        num + 1
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        fn init() {
            let _ = env_logger::builder().is_test(true).try_init();
        }
    
        #[test]
        fn it_adds_one() {
            init();
    
            info!("can log from the test too");
            assert_eq!(3, add_one(2));
        }
    }
  4. Format of logging specification strings

    main

    The env_filter crate parses logging specification strings to determine which modules should log at which levels and applies optional regex filters.

    Syntax Structure

    A specification string follows this pattern: [directives]/[filter]

    1. Directives: A comma-separated list of module/crate specifications. Each directive can be:
      • name=level: e.g., crate1::mod3=error
      • name: e.g., crate1::mod3 (defaults to LevelFilter::max())
      • level: e.g., warn (applies the level globally, with no specific name)
    2. Filter: An optional regex filter following a single / character.

    Examples

    • crate1,crate2::mod3,crate3::x=error/foo
    • crate1::mod1=error,crate1::mod2,crate2=debug
    • warn,crate2=debug (sets global level to warn and crate2 to debug)
    • crate1/abc (sets crate1 to max level and applies regex filter abc)
    crate1,crate2::mod3,crate3::x=error/foo
  5. Set timestamp precision in logs

    main

    When using the default format, you can control the precision of the included timestamps using the TimestampPrecision enum via the timestamp method on ConfigurableFormat.

    Available precisions:

    • Seconds: Full second precision (0 decimal digits).
    • Millis: Millisecond precision (3 decimal digits).
    • Micros: Microsecond precision (6 decimal digits).
    • Nanos: Nanosecond precision (9 decimal digits).

    Note: Timestamp functionality requires the humantime feature.

    // Example of setting millisecond precision
    // (Assuming access to ConfigurableFormat via Builder)
    config.timestamp(Some(TimestampPrecision::Millis));
  6. How log filtering precedence works

    main

    When multiple directives are configured, env_filter uses a longest-prefix match strategy for module targets.

    1. Longest Match Wins: If you have a rule for crate and a rule for crate::module, a log record from crate::module will follow the more specific crate::module rule.
    2. Default Level: If no directives are provided to the Builder, it defaults to LevelFilter::Error.
    3. Global Filter: If a rule is provided without a module name (or via filter_level), it acts as a baseline for all modules not covered by more specific directives.
    4. Max Level: The filter() method on a Filter instance returns the highest LevelFilter currently configured across all directives.
    // Example of precedence:
    let filter = Builder::new()
        .filter(Some("crate2"), LevelFilter::Info)
        .filter(Some("crate2::mod"), LevelFilter::Debug)
        .build();
    
    // A log from "crate2::mod" will use LevelFilter::Debug
    // A log from "crate2::other" will use LevelFilter::Info
  7. Configure logging levels with RUST_LOG

    main

    The RUST_LOG environment variable determines which log messages are displayed. The level names are case-insensitive (e.g., info, INFO, and iNfO are all valid).

    Supported log levels from the log crate:

    • error
    • warn
    • info
    • debug
    • trace
    • off (a pseudo-level used to disable all logging for a module or the application)

    Example usage:

    $ RUST_LOG=info ./main
    $ RUST_LOG=INFO ./main
  8. Capture logs during cargo test

    main

    By default, cargo test captures and hides logs. To ensure logs are captured and visible in the test output, use the Builder::is_test(true) method in your test initialization code.

    #[cfg(test)]
    mod tests {
        use log::info;
    
        fn init() {
            // is_test(true) ensures logs are captured by the test harness
            let _ = env_logger::builder().is_test(true).try_init();
        }
    
        #[test]
        fn it_works() {
            init();
            info!("This record will be captured by `cargo test`");
            assert_eq!(2, 1 + 1);
        }
    }
  9. Configure the logger using `Builder`

    main

    Use env_logger::Builder for fine-grained control over the logger's behavior, such as custom formatting, specific module filters, or changing the output target. You can initialize a builder from the default environment, a custom environment, or a blank state.

    Common builder tasks include:

    • Custom Formatting: Use .format() to provide a closure for custom log output.
    • Filtering: Use .filter_level() for global levels or .filter_module() for specific modules.
    • Output Target: Use .target() to switch between stdout, stderr, or a custom pipe.
    • Styles: Use .write_style() to control ANSI color output.
    use env_logger::Builder;
    use log::{LevelFilter, error, info};
    
    let mut builder = Builder::from_default_env();
    
    builder
        .format(|buf, record| writeln!(buf, "{} - {}", record.level(), record.args()))
        .filter(None, LevelFilter::Info)
        .init();
    
    error!("error message");
    info!("info message");
  10. Construct a log filter using Builder

    main

    The Builder struct is used to configure and construct a Filter. You can define logging rules programmatically by specifying modules and levels, or by parsing a directive string (e.g., from an environment variable).

    To use the builder:

    1. Initialize it with Builder::new().
    2. Add rules using .filter(), .filter_module(), or .filter_level().
    3. Alternatively, parse a string using .parse() (which ignores errors and prints warnings to stderr) or .try_parse() (which returns a Result).
    4. Finalize the configuration by calling .build().
    # use log::LevelFilter;
    # use env_filter::Builder;
    
    let mut builder = Builder::new();
    
    // Parse a logging filter from an environment variable.
    if let Ok(rust_log) = std::env::var("RUST_LOG") {
        builder.parse(&rust_log);
    }
    
    let filter = builder.build();