tokio-rs / tracing

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

Type erasure for registry/subscribers #2891

Open colinmarc opened 5 months ago

colinmarc commented 5 months ago

The complex types on tracing-subscriber Subscribers and Layers make them a little bit difficult to work with. Consider the following example, which switches on a command line flag to write a trace log to a directory:

let mut registry = tracing_subscriber::registry().with(printed_log);

if let Some(dir) = bug_report_dir {
    // Additionally write a trace log with everything to the bug report dir.
    let file = std::fs::File::create(dir.as_ref().join("server.log"))?;
    let trace_filter = tracing_subscriber::EnvFilter::new("server=trace");

    let trace_log = tracing_subscriber::fmt::layer()
        // This also hits #1817
        .with_ansi(false)
        .with_writer(Mutex::new(file))
        .with_filter(trace_filter);

    registry = registry.with(trace_log);
 }

This fails to compile with:

error[E0308]: mismatched types
   --> mm-server/src/main.rs:142:20
    |
129 |     let mut registry = tracing_subscriber::registry().with(printed_log);
    |                        ------------------------------------------------ expected due to this value
...
142 |         registry = registry.with(trace_log);
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^ expected `tracing_subscriber::Registry`, found `Layered<Filtered<..., ..., ...>, ...>`
    |
    = note: expected struct `Layered<Filtered<tracing_subscriber::fmt::Layer<tracing_subscriber::Registry, DefaultFields, _>, _, tracing_subscriber::Registry>, tracing_subscriber::Registry, tracing_subscriber::Registry>`
               found struct `Layered<Filtered<Layer<Layered<Filtered<Layer<Registry>, EnvFilter, Registry>, Registry>, LogFull, Format, Mutex<File>>, ..., ...>, ...>`
            the full type name has been written to ...

I'm relatively new to rust, so it could be I'm missing something obvious, but it seems like the type system prevents any conditional logic when setting up logging, which I would expect to be the norm for complicated apps. An imperative fn register(&mut self, impl Layer) would probably fix that case, but there's another problem, which is that the type system around Subscribers is fairly illegible at the moment.

Here's an actual screenshot of my terminal from last night when I was looking for workarounds to this problem:

image

I understand that there are complex requirements here, which in turn necessitate lots of API surface. And I don't have any concrete suggestions to make beyond what I mentioned earlier, so I hope this criticism isn't unwelcome. If someone is willing to point me in the right direction, I'm more than happy to contribute some work on this.

colinmarc commented 5 months ago

I just discovered that Option<Layer> implements Layer, which makes this problem a lot easier. I'm not sure how I missed that in the docs, but maybe one of the fmt examples should demonstrate that?

xuorig commented 4 months ago

You may also use boxed layers for type erasure: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/trait.FilterExt.html#method.boxed