Skip to content

Q: How can I have multiple fmt writers with different level? #597

Closed
@oblique

Description

@oblique

I'm trying to migrate from simplelog to tracing, however I have a very special case:
I have three writers, different level each (In the real case actually they are three different files).

In my first attempt, I tried to combine multiple fmt::Layer with Registry:

use tracing::{debug, info, info_span, trace, trace_span, warn};
use tracing_subscriber::fmt;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::Registry;

fn main() {
    let logger = Registry::default()
        .with(fmt::Layer::default())
        .with(fmt::Layer::default())
        .with(fmt::Layer::default());

    tracing::subscriber::set_global_default(logger).unwrap();

    let x = 1;
    let span = info_span!("info", x);
    let _guard = span.enter();

    let y = 2;
    let span = trace_span!("trace", y);
    let _guard = span.enter();

    info!("ABC");
    warn!("DEF");
    debug!("1234");
    trace!("5678");
}

And the output is:

Feb 22 23:29:02.783  INFO info{x=1x=1x=1}:trace{y=2y=2y=2}: tt: ABC
Feb 22 23:29:02.783  INFO info{x=1x=1x=1}:trace{y=2y=2y=2}: tt: ABC
Feb 22 23:29:02.783  INFO info{x=1x=1x=1}:trace{y=2y=2y=2}: tt: ABC
Feb 22 23:29:02.783  WARN info{x=1x=1x=1}:trace{y=2y=2y=2}: tt: DEF
Feb 22 23:29:02.783  WARN info{x=1x=1x=1}:trace{y=2y=2y=2}: tt: DEF
Feb 22 23:29:02.783  WARN info{x=1x=1x=1}:trace{y=2y=2y=2}: tt: DEF
Feb 22 23:29:02.783 DEBUG info{x=1x=1x=1}:trace{y=2y=2y=2}: tt: 1234
Feb 22 23:29:02.783 DEBUG info{x=1x=1x=1}:trace{y=2y=2y=2}: tt: 1234
Feb 22 23:29:02.783 DEBUG info{x=1x=1x=1}:trace{y=2y=2y=2}: tt: 1234
Feb 22 23:29:02.783 TRACE info{x=1x=1x=1}:trace{y=2y=2y=2}: tt: 5678
Feb 22 23:29:02.784 TRACE info{x=1x=1x=1}:trace{y=2y=2y=2}: tt: 5678
Feb 22 23:29:02.784 TRACE info{x=1x=1x=1}:trace{y=2y=2y=2}: tt: 5678

With this approach the I found two issues:

  1. I couldn't apply level filter in the layers
  2. Notice the duplication: x=1x=1x=1 and y=2y=2y=2.

My final approach is:

use tracing::{
    debug, info, info_span, metadata::Level, span, trace, trace_span, warn, Event, Subscriber,
};
use tracing_subscriber::fmt;
use tracing_subscriber::layer::{Context, Layer, SubscriberExt};
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::Registry;

fn main() {
    let logger = Registry::default()
        .with(fmt::Layer::default())
        .with(FmtWriterLayer::new(Level::DEBUG))
        .with(FmtWriterLayer::new(Level::INFO));

    tracing::subscriber::set_global_default(logger).unwrap();

    let x = 1;
    let span = info_span!("info", x);
    let _guard = span.enter();

    let y = 2;
    let span = trace_span!("trace", y);
    let _guard = span.enter();

    info!("ABC");
    warn!("DEF");
    debug!("1234");
    trace!("5678");
}

struct FmtWriterLayer<S>
where
    S: Subscriber + for<'a> LookupSpan<'a>,
{
    layer: fmt::Layer<S>,
    level: Level,
}

impl<S> FmtWriterLayer<S>
where
    S: Subscriber + for<'a> LookupSpan<'a>,
{
    fn new(level: Level) -> FmtWriterLayer<S> {
        let layer = fmt::Layer::default();

        FmtWriterLayer {
            layer,
            level,
        }
    }
}

impl<S> Layer<S> for FmtWriterLayer<S>
where
    S: Subscriber + for<'a> LookupSpan<'a>,
{
    fn new_span(&self, _attrs: &span::Attributes, _id: &span::Id, _ctx: Context<S>) {
        // intentionally not implemented
    }

    fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
        self.layer.on_record(id, values, ctx);
    }

    fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
        if *event.metadata().level() <= self.level {
            self.layer.on_event(event, ctx);
        }
    }
}

And the output is:

Feb 22 23:34:34.345  INFO info{x=1}:trace{y=2}: tracing_multifmt: ABC
Feb 22 23:34:34.345  INFO info{x=1}:trace{y=2}: tracing_multifmt: ABC
Feb 22 23:34:34.345  INFO info{x=1}:trace{y=2}: tracing_multifmt: ABC
Feb 22 23:34:34.345  WARN info{x=1}:trace{y=2}: tracing_multifmt: DEF
Feb 22 23:34:34.346  WARN info{x=1}:trace{y=2}: tracing_multifmt: DEF
Feb 22 23:34:34.346  WARN info{x=1}:trace{y=2}: tracing_multifmt: DEF
Feb 22 23:34:34.346 DEBUG info{x=1}:trace{y=2}: tracing_multifmt: 1234
Feb 22 23:34:34.346 DEBUG info{x=1}:trace{y=2}: tracing_multifmt: 1234
Feb 22 23:34:34.346 TRACE info{x=1}:trace{y=2}: tracing_multifmt: 5678

This is almost what I expect, however one small issue is that trace span is printed in all levels.

So, some questions:

  1. Is this a missing feature? Or is there another way?
  2. In case of missing feature, how would you like to be added? I was thinking that add_writer_with_level could be implemented in fmt::Subscriber. What are your thoughts? Do you have any suggestion?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions