Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unsatisfied trait bound when using test writer with reload handle that is satisfied when using default writer #3225

Open
taladar opened this issue Feb 28, 2025 · 0 comments

Comments

@taladar
Copy link

taladar commented Feb 28, 2025

Bug Report

Working code breaks with a complex unsatisfied trait bound by adding a call to .with_test_writer()

Version

├── tracing v0.1.41
│   ├── tracing-attributes v0.1.28 (proc-macro)
│   └── tracing-core v0.1.33
├── tracing-appender v0.2.3
│   └── tracing-subscriber v0.3.19
│       ├── tracing v0.1.41 (*)
│       ├── tracing-core v0.1.33 (*)
│       └── tracing-log v0.2.0
│           └── tracing-core v0.1.33 (*)
└── tracing-subscriber v0.3.19 (*)

Platform

Linux taladardesktop 6.13.4 #216 SMP PREEMPT_DYNAMIC Fri Feb 21 15:56:13 CET 2025 x86_64 AMD Ryzen 9 3900X 12-Core Processor AuthenticAMD GNU/Linux

Crates

I assume the problem is in tracing-subscriber

Description

I have an integration test binary in my project (among other tests).

This integration test uses a Mutex to serialize all tests since setting test threads can not easily be done per test binary and it uses some resources that do not allow concurrent access.

I want the test to log a high level overview (info level and above for my crate and some specific dependencies) to the test output and a more detailed log (trace level for my crate and other settings for other dependencies) into one log file per test.

For this I am using a reload handle to change the writer in the file layer at the start of each test.

This works fine as far as the file output is concerned but with the default writer the console output is printed even if a test does not fail.

So I wanted to replace the default writer with the TestWriter but the code does not compile with a complex trait bound error that I can't quite figure out but it suggests to me that some implementation for some trait is likely missing for TestWriter that exists for the default writer.

I made a minimal example crate to reproduce the issue with the problematic change to test writer commented out. Sadly, the minimal code is still quite long thanks to the huge reload handle type.

Pay particular attention to the first error in the output where the compiler notes that an implementation for fn() -> std::io::Stdout exists but not for TestWriter in the same position.

Minimal repro

Cargo.toml

[package]
name = "minimize_tracing_reload_problem"
version = "0.1.0"
edition = "2024"

[dependencies]
function_name = "0.3.0"
tokio = { version = "1.43.0", features = ["full"] }
tracing = "0.1.41"
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }

build.rs (necessary or OUT_DIR is not set at compile time)

fn main() { }

src/main.rs (default cargo new --bin version)

fn main() {
    println!("Hello, world!");
}

tests/test.rs

use tracing_subscriber::Layer as _;
use tracing_subscriber::layer::SubscriberExt as _;
use tracing_subscriber::util::SubscriberInitExt as _;

/// ensures tracing is only initialized once on the first test who gets the lock
pub static TRACING_INITIALIZED: std::sync::Once = std::sync::Once::new();

/// stores the reload handle to change the file layer
#[allow(clippy::type_complexity)]
pub static TRACING_RELOAD_HANDLE: std::sync::Mutex<
    Option<
        tracing_subscriber::reload::Handle<
            tracing_subscriber::fmt::Layer<
                tracing_subscriber::layer::Layered<
                    tracing_subscriber::filter::Filtered<
                        tracing_subscriber::fmt::Layer<tracing_subscriber::Registry>,
                        tracing_subscriber::EnvFilter,
                        tracing_subscriber::Registry,
                    >,
                    tracing_subscriber::Registry,
                >,
                tracing_subscriber::fmt::format::DefaultFields,
                tracing_subscriber::fmt::format::Format,
                tracing_appender::rolling::RollingFileAppender,
            >,
            tracing_subscriber::layer::Layered<
                tracing_subscriber::filter::Filtered<
                    tracing_subscriber::fmt::Layer<tracing_subscriber::Registry>,
                    tracing_subscriber::EnvFilter,
                    tracing_subscriber::Registry,
                >,
                tracing_subscriber::Registry,
            >,
        >,
    >,
> = std::sync::Mutex::new(None);

fn setup_tracing(test_name: &str) -> Result<(), Box<dyn std::error::Error>> {
    let log_dir = std::path::Path::new(env!("OUT_DIR")).join("test_logs");
    let log_file = format!("{}.log", test_name);
    TRACING_INITIALIZED.call_once(|| {
            #[allow(clippy::expect_used)]
            let minimal_env_filter = tracing_subscriber::EnvFilter::builder()
                .with_default_directive(tracing::level_filters::LevelFilter::INFO.into())
                .parse(std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string())).expect("level filter from RUST_LOG parse");
            #[allow(clippy::expect_used)]
            let env_filter = tracing_subscriber::EnvFilter::builder()
                .with_default_directive(tracing::level_filters::LevelFilter::TRACE.into())
                .parse(std::env::var("RUST_FILE_LOG").unwrap_or_else(|_| "trace".to_string())).expect("level filter from RUST_FILE_LOG parse");
            let registry = tracing_subscriber::Registry::default();
            // works
            let registry =
                registry.with(tracing_subscriber::fmt::Layer::default()
                    .with_filter(minimal_env_filter));
            // but this does not
            //let registry =
            //    registry.with(tracing_subscriber::fmt::Layer::default()
            //        .with_test_writer()
            //        .with_filter(minimal_env_filter));
            let file_appender = tracing_appender::rolling::never(&log_dir, &log_file);
            let file_layer =
                tracing_subscriber::fmt::Layer::default()
                .with_writer(file_appender);
            let (reloadable_file_layer, reload_handle) = tracing_subscriber::reload::Layer::new(file_layer);
            let reloadable_filtered_file_layer = reloadable_file_layer.with_filter(env_filter);
            {
                #[allow(clippy::expect_used)]
                let mut l = TRACING_RELOAD_HANDLE.lock().expect("reload handler mutex");
                *l = Some(reload_handle);
            }
            let registry =
                registry.with(reloadable_filtered_file_layer);
            registry.init();
        });
    let file_appender = tracing_appender::rolling::never(&log_dir, &log_file);
    let file_layer = tracing_subscriber::fmt::Layer::default().with_writer(file_appender);
    {
        #[allow(clippy::expect_used)]
        let l = TRACING_RELOAD_HANDLE.lock().expect("reload handler mutex");
        if let Some(ref reload_handle) = *l {
            reload_handle.reload(file_layer)?;
            tracing::info!(
                "Now logging details to\n{}/{}",
                log_dir.display(),
                log_file
            );
        }
    }
    Ok(())
}

static TEST_LOCK: std::sync::LazyLock<tokio::sync::Mutex<()>> =
    std::sync::LazyLock::new(|| tokio::sync::Mutex::new(()));

#[tokio::test]
#[function_name::named]
async fn test1() -> Result<(), Box<dyn std::error::Error>> {
    let _guard = TEST_LOCK.lock().await;

    setup_tracing(function_name!())?;

    tracing::info!("Logging in test {}", function_name!());

    Ok(())
}

#[tokio::test]
#[function_name::named]
async fn test2() -> Result<(), Box<dyn std::error::Error>> {
    let _guard = TEST_LOCK.lock().await;

    setup_tracing(function_name!())?;

    tracing::info!("Logging in test {}", function_name!());

    Ok(())
}

Compiler error when with_test_writer() version is commented in
   Compiling minimize_tracing_reload_problem v0.1.0 (/home/taladar/temp/20250228/minimize_tracing_reload_problem)
error[E0277]: the trait bound `Filtered<tracing_subscriber::reload::Layer<tracing_subscriber::fmt::Layer<Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>, DefaultFields, Format, RollingFileAppender>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, EnvFilter, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>: __tracing_subscriber_Layer<Layered<Filtered<tracing_subscriber::fmt::Layer<Registry, DefaultFields, Format, TestWriter>, EnvFilter, Registry>, Registry>>` is not satisfied
    --> tests/test.rs:72:31
     |
72   |                 registry.with(reloadable_filtered_file_layer);
     |                          ---- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound
     |                          |
     |                          required by a bound introduced by this call
     |
     = help: the trait `Layer<Layered<Filtered<tracing_subscriber::fmt::Layer<_, _, _, TestWriter>, _, _>, _>>` is not implemented for `Filtered<Layer<Layer<..., ..., ..., ...>, ...>, ..., ...>`
             but trait `Layer<Layered<Filtered<tracing_subscriber::fmt::Layer<_, _, _, fn() -> std::io::Stdout>, _, _>, _>>` is implemented for it
     = help: for that trait implementation, expected `fn() -> std::io::Stdout`, found `TestWriter`
note: required by a bound in `with`
    --> /home/taladar/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tracing-subscriber-0.3.19/src/layer/mod.rs:1504:12
     |
1502 |     fn with<L>(self, layer: L) -> Layered<L, Self>
     |        ---- required by a bound in this associated function
1503 |     where
1504 |         L: Layer<Self>,
     |            ^^^^^^^^^^^ required by this bound in `__tracing_subscriber_SubscriberExt::with`

error[E0599]: the method `init` exists for struct `Layered<Filtered<Layer<..., ...>, ..., ...>, ...>`, but its trait bounds were not satisfied
  --> tests/test.rs:73:22
   |
73 |             registry.init();
   |                      ^^^^ method cannot be called on `Layered<Filtered<Layer<..., ...>, ..., ...>, ...>` due to unsatisfied trait bounds
   |
  ::: /home/taladar/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tracing-subscriber-0.3.19/src/layer/layered.rs:22:1
   |
22 | pub struct Layered<L, I, S = I> {
   | ------------------------------- doesn't satisfy `_: Into<Dispatch>` or `_: SubscriberInitExt`
   |
note: there's an earlier shadowed binding `registry` of type `Registry` that has method `init` available
  --> tests/test.rs:50:17
   |
50 |             let registry = tracing_subscriber::Registry::default();
   |                 ^^^^^^^^ `registry` of type `Registry` that has method `init` defined earlier here
...
71 |             let registry =
   |                 -------- earlier `registry` shadowed here with type `Layered<Filtered<Layer<..., ...>, ..., ...>, ...>`
   = note: the full type name has been written to '/home/taladar/temp/20250228/minimize_tracing_reload_problem/target/debug/deps/test-581b513331dfbb49.long-type-9381801419203904221.txt'
   = note: consider using `--verbose` to print the full type name to the console
   = note: the following trait bounds were not satisfied:
           `Layered<Filtered<tracing_subscriber::reload::Layer<tracing_subscriber::fmt::Layer<Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>, DefaultFields, Format, RollingFileAppender>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, EnvFilter, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry, DefaultFields, Format, TestWriter>, EnvFilter, Registry>, Registry>>: Into<Dispatch>`
           which is required by `Layered<Filtered<tracing_subscriber::reload::Layer<tracing_subscriber::fmt::Layer<Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>, DefaultFields, Format, RollingFileAppender>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, EnvFilter, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry, DefaultFields, Format, TestWriter>, EnvFilter, Registry>, Registry>>: SubscriberInitExt`
           `&Layered<Filtered<tracing_subscriber::reload::Layer<tracing_subscriber::fmt::Layer<Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>, DefaultFields, Format, RollingFileAppender>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, EnvFilter, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry, DefaultFields, Format, TestWriter>, EnvFilter, Registry>, Registry>>: Into<Dispatch>`
           which is required by `&Layered<Filtered<tracing_subscriber::reload::Layer<tracing_subscriber::fmt::Layer<Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>, DefaultFields, Format, RollingFileAppender>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, EnvFilter, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry, DefaultFields, Format, TestWriter>, EnvFilter, Registry>, Registry>>: SubscriberInitExt`
           `&mut Layered<Filtered<tracing_subscriber::reload::Layer<tracing_subscriber::fmt::Layer<Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>, DefaultFields, Format, RollingFileAppender>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, EnvFilter, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry, DefaultFields, Format, TestWriter>, EnvFilter, Registry>, Registry>>: Into<Dispatch>`
           which is required by `&mut Layered<Filtered<tracing_subscriber::reload::Layer<tracing_subscriber::fmt::Layer<Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>, DefaultFields, Format, RollingFileAppender>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, EnvFilter, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry>, EnvFilter, Registry>, Registry>>, Layered<Filtered<tracing_subscriber::fmt::Layer<Registry, DefaultFields, Format, TestWriter>, EnvFilter, Registry>, Registry>>: SubscriberInitExt`

Some errors have detailed explanations: E0277, E0599.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `minimize_tracing_reload_problem` (test "test") due to 2 previous errors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant