fern icon indicating copy to clipboard operation
fern copied to clipboard

How to set different log levels for modules

Open andresv opened this issue 8 years ago • 11 comments

Currently I am using this snippet to setup fern:

fn setup_logger() {
    let logger_config = fern::DispatchConfig {
        format: Box::new(|msg: &str, level: &log::LogLevel, _location: &log::LogLocation| {
            let t = time::now();
            let ms = t.tm_nsec/1000_000;
            let path = _location.__module_path;
            let line = _location.__line;

            format!("{}.{:3} [{}] {} {}: {}", t.strftime("%Y-%m-%dT%H:%M:%S").unwrap(), ms, level, path, line, msg)
        }),
        output: vec![fern::OutputConfig::stderr()],
        level: log::LogLevelFilter::Trace,
    };

    if let Err(e) = fern::init_global_logger(logger_config, log::LogLevelFilter::Trace) {
        panic!("Failed to initialize global logger: {}", e);
    }
}

It sets up logging globally, however is it possible to setup different loglevels for different threads.

For example I would like that module mqtt::client would log only error messages. Is there possibility to do that?

andresv avatar Aug 28 '15 20:08 andresv

This fork allows to do it: https://github.com/marjakm/fern-rs/commit/29cead260804a4d192b2ade8ad374cf45f70c12c

andresv avatar Sep 02 '15 06:09 andresv

My apologies for not replying to this earlier! This is not possible with the current fern, as you have found, yes.

daboross avatar Jun 08 '16 05:06 daboross

Apologies for, well, not working on this until now! I'm finally getting around to some of the projects that I started, and fern does seem to need a refresh which would include this. I'm thinking a builder pattern like other crates have adopted would make everything flow much better, would you have any input on a new API like this?

let logger = fern::config()
    .format(|level, location, msg| {
        format!("[{}][{}] {}", level, location.module_path(), msg)
    })
    .chain(
        fern::config()
        // by default only accept warn messages
        .level(log::LogLevelFilter::Warn)
        // accept info messages from the current crate too
        .level_for("my_crate", log::LogLevelFilter::Info)
        .output(fern::stdout())
    )
    .chain(
        fern::config()
        // output all messages
        .level(log::LogLevelFilter::Trace)
        // except for hyper, in that case only show info messages
        .level_for("hyper", log::LogLevelFilter::Info)
        .output(fern::file("debug.log"))
        // not much purpose here, just demonstrating that output can be used multiple times.
        .output(fern::file("/tmp/debug.log"))
    )
    .chain(
        fern::config()
        .filter(|level, module_path| {
            // hmm, maybe just ignore some messages.
            level <= log::LogLevelFilter::Warn && rand::random()
        })
        .output(fern::stderr())
    )
    .build()

Different chains are there for demonstration purposes, all methods would be usable on all configurations.

I do see the fork that implements LogDirective, I'm not sure if that's the best solution though - as it would break current usage and then still require every user, whether using log directives or not, to have directives: vec![] in the construction.

daboross avatar Apr 23 '17 06:04 daboross

New update is not yet released, but almost completely built.

If you're still interested in this library at all, any feedback on this is greatly appreciated! I'm trying to make fern more ergonomic to use, and more runtime efficient.

Docs for unreleased changes at https://dabo.guru/rust/fern-dev/.

daboross avatar May 07 '17 06:05 daboross

I am OK with proposed changes.

andresv avatar May 07 '17 16:05 andresv

Awesome, thanks for the feedback!

There's one more thing I've just now added, which is documentation for exactly how level_for works internally. While env_logger's LogDirective just matches the start of any module name, fern 0.4 will test distinctly for each module in the parent path. level_for("hyper", ...); will match "hyper", "hyper::http" and "hyper::http::h1", but not "hyper_rustls" nor "hyper_rustls::ssl"

daboross avatar May 08 '17 03:05 daboross

fern 0.4 has been released, and includes this functionality!

Closing now that Dispatch::level_for exists.

daboross avatar May 09 '17 08:05 daboross

I am still very confused on how this is supposed to work. I have this code, but fern only logs for aurum, not aurum-cli. I've also tried this:

fern::Dispatch::new()
        .format(|out, message, record| {
            let prefix = match record.level() {
                Level::Error => "::".red().bold(),
                Level::Warn => "::".yellow().bold(),
                Level::Info => "::".blue().bold(),
                Level::Debug => "::".green().bold(),
                Level::Trace => "::".bold()
            };
            
            out.finish(format_args!(
                "{} {}",
                prefix,
                message
            ))
        })
        .chain(
            fern::Dispatch::new()
                .level(LevelFilter::Off)
                .level_for("aurum-cli", log_level)
                .chain(std::io::stdout())
        )
        .chain(
            fern::Dispatch::new()
                .level(LevelFilter::Off)
                .level_for("aurum", log_level)
                .chain(std::io::stdout())
        )
        
        .apply()
        .unwrap_or_exit(1);

But that gives me the same behavior. For the time being, I'm going to revert to println, but I'd like to resolve this in what I feel is the right way: making fern work. :P

MggMuggins avatar Sep 14 '18 00:09 MggMuggins

@MggMuggins Sorry about the confusion!

There should definitely be better documentation in this area than there is.

I think in this case the problem is that the compiler makes module names have _ instead of - and this is what log picks up. Using aurum_cli instead of aurum-cli should work. With that said, I think doing that translation automatically in fern would be a better idea than just accepting the invalid module name, which is what it currently does.

daboross avatar Sep 14 '18 04:09 daboross

Maybe have it spit out a warning message? Or is there a way to prevent names with - from compiling using a macro? If they aren't allowed at all, that might be a good way to do it.

I fixed it btw, that works exactly as you described. Some documentation of that would be really useful for the time being. Thanks for the help.

MggMuggins avatar Sep 14 '18 17:09 MggMuggins

Glad it's working! I'll at least update the documentation to handle this, and it might be good to do more.

One reason it's currently allowed is that - names can be log targets, they just have to be listed explicitly:

info!(target: "explicit-target", "this goes to explicit-target, not explicit_target");

Ideally I'd like to keep good support for explicit targets with - while still making the - -> _ conversion for rust crates apparent.

daboross avatar Sep 15 '18 03:09 daboross