Tracing subscriber only works when setup inline, not in a function

I'm not sure if this is the right place, but I'm stumped.

I am using tokio's tracing with tracing_subscriber and tracing appender

[dependencies]
tracing-subscriber = { version = "0.3.17", features = ["env-filter", "json"] }
tracing-log = "0.1.3"
tracing-serde = "0.1"
tracing-appender = "0.2.2"
tracing = {version  = "0.1.37"}

I am using layers to setup a console subscriber and a log file with json logs.

When I setup the subscriber in the main() function, it all works as expected. But when I move the setup code exactly as is to a function, the console (stdout) subscriber works, but the file subscriber only creates the file and writes nothing to it.

Here is my code:

pub fn setup_subscribers() -> Box<impl Subscriber + Send + 'static> {
    let file = tracing_appender::rolling::hourly("./logs", "application.log");
    let (non_blocking, _guard) = tracing_appender::non_blocking(file);

    let console = Layer::new()
        .with_writer(std::io::stdout)
        .pretty();

    let inspector = Layer::new()
        .with_writer(non_blocking)
        .json();

    Box::new(tracing_subscriber::registry().with(console).with(inspector))
}

fn main() {
    // When I try to use the setup_subscribers() function, the console subscriber works, but the file subscriber doesn't
    // let subscriber = setup_subscribers();

    // However, when I move the body of the setup_subscribers() function inline, it all works.
    let file = tracing_appender::rolling::hourly("./logs", "application.log");
    let (non_blocking, _guard) = tracing_appender::non_blocking(file);

    let console = Layer::new()
        .with_writer(std::io::stdout)
        .pretty();

    let inspector = Layer::new()
        .with_writer(non_blocking)
        .json();

    let subscriber = Box::new(tracing_subscriber::registry().with(console).with(inspector));

    // From here, the implementations are the same. They both use the subscriber varaible
    tracing::subscriber::set_global_default(subscriber).expect("Unable to set a global collector");
    info!("hello world");
}

I can only imagine it has something to do with my return type (since its a Box and that I'm not being concrete enough or something.

But its weird to me that the console later works either way.

Any help would be appreciated. I guess I could always move it to a macro, but I'd rather understand what's happening here.

I'm not familiar with these libraries, but this is suspicious:

    let (non_blocking, _guard) = tracing_appender::non_blocking(file);

_guard suggests that this is something that's supposed to stay alive for some period. And the documentation concurs:

Note: _guard is a WorkerGuard which is returned by tracing_appender::non_blocking to ensure buffered logs are flushed to their output in the case of abrupt terminations of a process. See WorkerGuard module for more details.

So, you're not supposed to drop the guard until the program ends, but by putting it in a function, you are. The fix is just to return the guard from setup_subscribers() and bind it to a variable in main().

4 Likes

Wow, thank you. I'm embarrassed that I messed that and it requires me to re-architect a bit. But I really appreciate the response. Have a great day!