tokio-rs / tracing

Application level tracing for Rust.
https://tracing.rs
MIT License
5.33k stars 697 forks source link

`SetGlobalDefaultError` when running multiple serial tests #2844

Closed a-0-dev closed 8 months ago

a-0-dev commented 8 months ago

When executing one test at a time, everything works as expected. Running multiple tests at once (with one cargo test) yields a SetGlobalDefaultError for the second test:

thread 'state::api_state::tests::test_element_create' panicked at /home/a-0/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tracing-subscriber-0.3.17/src/fmt/mod.rs:517:14:
Unable to install global subscriber: SetGlobalDefaultError("a global default trace dispatcher has already been set")

A test looks like this:

#[tokio::test]
#[serial_test::serial]
async fn test_element_create() {
    tracing_subscriber::fmt().pretty().init();
    [...]
}

versions:

tokio = { version = "1.32.0", features = ["full"] }
tracing = "0.1.37"
tracing-subscriber = "0.3.17"

I couldn't find an alternative macro to the ones I used which would sufficiently isolate the test executions so the "old" global default isn't set anymore in the consecutive tests. Nor did I find a recommendation on how to use tracing when running multiple #[tokio::test]s. So I assume this is a bug - please let me know if this is not the case.

hawkw commented 8 months ago

The global default subscriber can only be set once per process. Once it has been set, it remains set until the process exits. Multiple tests in the same file are compiled into one binary and execute in the same process, regardless of whether or not they run in parallel.

You may want to consider using a scoped default subscriber instead. This way, the default subscriber you set in your tests will unset itself at the end of the test.

a-0-dev commented 8 months ago

Thanks for your answer, I thought setting the default subscriber (not global default) works as you suggested for the past few days - but it doesn't :/

In my tests, I create an axum server in whose request processing functions (what's called when someone makes an http request) I have quite some logging statements. Using the global default subscriber, these are logged. Using the scoped default, they are not. And since the scope of the scoped default is somehow connected to the lifetime or availability of the subscriber guard, I don't see any way of having these logging statements in the axum functions logged (they are only passed to the framework by function identifier reference, never explicitly called anywhere).

So I guess what I need is a global default which can be unset or set to something else - basically an ephemeral global default. I assume such thing does not exist (yet)? Is there another solution?