Search code examples
rustrust-tracing

How to use `tracing-subscriber` Rust crate to build a mult-writer, global filter subscriber


I'm trying to build a logging setup using the tracing and tracing-subscriber. However I'm finding the tracing ecosystem incredibly abstract and hard to work with.

I'm trying to create Subscriber that has -

  • A top level filter based on the target. For e.g. a filter that logs error level for module_a and info level for module_b.^1
  • Two writers^2 each with their own layer level filter.

But I'm unable to create the final subscriber with the existing implementations Registry and FmtSubscriber.

The problem is that Registry does not allow adding an env_filter or a top level filter. While the FmtSubscriber does not allow adding the writer layers. And it's not clear to me why either of this is not possible when both of them implement the Subscriber trait.

use tracing::{instrument::WithSubscriber, metadata::LevelFilter, subscriber, Level, Subscriber};
use tracing_appender::{
    non_blocking::WorkerGuard,
    rolling::{RollingFileAppender, Rotation},
};
use tracing_subscriber::{
    filter::filter_fn, fmt, layer::Filter, prelude::*, registry::LookupSpan, Registry,
};

fn main() {
    // layer 1 is the file writer
    let rolling_log = RollingFileAppender::new(Rotation::NEVER, "hey", "cool.og");
    let (non_blocking, _) = tracing_appender::non_blocking(rolling_log);
    let layer1 = fmt::Layer::default()
        .with_writer(non_blocking)
        .with_filter(LevelFilter::from(Level::INFO));

    // layer 2 is the stdout writer
    let (non_blocking, _) = tracing_appender::non_blocking(std::io::stdout());
    let layer2 = fmt::Layer::default()
        .with_writer(non_blocking)
        .with_filter(LevelFilter::from(Level::ERROR));

    let top_level_filter: String = "module_a=info,module_b=error".to_string();
    // can't add env_filter/top level filter
    Registry::default().with(layer1).with(layer2).init();
    // can't add multiple writer layers
    fmt().with_env_filter(top_level_filter).init();
}

Overall I've found it hard to understand how the the various components in tracing fit together. Any blogs or tutorials explaining how it works will also help.


Solution

  • Answered by gusrut in this reddit comment.

    EnvFilter implements various levels of filtering and can be added to the subscriber using Subscriber::with to act as a global filter.

    use tracing_subscriber::filter::EnvFilter;
    
    let layer1 = ...;
    let layer2 = ...;
    
    let global_filter: EnvFilter = "module_a=info,module_b=error".parse()?;
    Registry::default().with(layer1).with(layer2).with(global_filter).init();