Use of tracing and tracing_subscriber in Rust

I'm not going to bury the lead: I have never used a more frustrating library, but I have finally got this damn thing figured out. Step into my office and let's go over it.

Every tutorial for the tracing and tracing-subscriber libraries is going to tell you the same, God-awful lie: they are going to claim that your init code can look something like this.

let filter = Targets::new().with_target("footarget", Level::TRACE);
let subscriber = FmtSubscriber::builder().pretty().without_time().finish();
tracing::subscriber::set_global_default(subscriber.with(filter))
    .expect("logging failed");

You should be so lucky. This won't work. I mean, I have this working in one of my applications--I copied this code from a working program!--but it won't work in any other case I have looked at. The other thing you'll read in tutorials is that you can use EnvFilter, which will configure a subscriber based on an environment variable. You can pass that in at runtime by adding something like this to your program invocation.

$ my_crate::module=trace fooprogram ./foopath/foofile.txt

This will work somewhat reliably, but of course you have to remember to do that every time or muddy up your shell profiles, and the truth is that most programs either need to print logs or don't need to print logs. If that isn't the case, I'll probably add a --debug switch to the CLI. At no point do I really want to mess with the environment to make that determination. So, how can we fix this problem?

The solution

In order to actually print anything, both the target and the subscriber need to be configured correctly. That is, if you ever want to see DEBUG or TRACE logs, you've got to increase the verbosity on both. Here's an example from a piece of software I've been working on.

use tracing::Level;
use tracing_subscriber::{
    filter::Targets,
    layer::SubscriberExt,
    util::SubscriberInitExt
};

pub fn initialize() {
    let target = Targets::new()
        .with_target("yeti_search", Level::DEBUG)
        .with_target("search", Level::DEBUG)
        .with_target("rocket", Level::INFO);

    let subscriber = tracing_subscriber::fmt()
        .with_max_level(Level::DEBUG)
        .pretty()
        .finish();

    subscriber.with(target).init();
}

This works the same way every time without me needing to do anything odd re: environment variables. If I want to, I can pass in my CLI config and read a debug flag from there, but this program in particular doesn't need that: it's a server, and I pretty much always want it to print the same logging information. Simple.

I don't remember where I found this or how I pieced it together. There's not a good source online that I can locate now, but maybe there was a few months ago when I originally created this service. Dunno. I'm writing this mostly so that the next guy doesn't have to search as hard as I did in order to find it.

Good luck, next guy.