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

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

Closed
oblique opened this issue Feb 22, 2020 · 18 comments
Closed

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

oblique opened this issue Feb 22, 2020 · 18 comments

Comments

@oblique
Copy link

oblique commented Feb 22, 2020

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?
@davidbarsky
Copy link
Member

Sorry for the delay in responding! Short answer: this is a missing feature and it's not supported in tracing yet. We've started work on supporting this in tracing in #508, but filtering has some subtle behavioral and performance properties.

As for something that works right now, I think your approach is reasonable. As for:

one small issue is that trace span is printed in all levels.

Levels implement Ord and PartialOrd (as they are a usize under the hood), so you'd need to filter for a specific level (while also filtering out other levels).

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?

I think #508 is the correct long-term solution for this problem. I'd rather not any additional methods/complex behavior to tracing_subscriber::fmt::Layer, but I'm not sure if @hawkw agrees with me.

@oblique
Copy link
Author

oblique commented Mar 1, 2020

Levels implement Ord and PartialOrd (as they are a usize under the hood), so you'd need to filter for a specific level (while also filtering out other levels).

Do you mean that I need to modify ctx of on_event? If yes, I couldn't find how I can remove spans.

@hawkw hawkw added this to To do in missing log features Mar 6, 2020
@jacekchmiel
Copy link

Hi, I have somewhat similar case. I'd like to use tracing-subscriber fmt feature with EnvFilter logic as it's very handy. On top of that, I'd like to measure some span durations for performance analysis. One approach would be to create spans in form of trace_span!("some_span", perf=true) and only measure spans with perf value attached and set to true.

Layered structure provided by tracing-subscriber crate only allows global filtering and no matter what, I'd end with events from my perf spans being logged as their spans must be enabled to allow time measurement.

To allow decoupling of logging and performance measurement I've introduced a simple Dispatch layer. It consists of Vec<Box<dyn Layer<S>>> and essentially all Layer methods are implemented as self.layers.iter().for_each(...). Filtering methods uses simple logic:

  • Layer::enabled() is implemented to return true if any of the inner Layers return true
  • Layer::register_callsite() returns always if any inner Layer returns always, otherwise it returns sometimes if any inner Layer returns sometimes and it returns never if all inner Layers return never.

This simple approach allows separate filtering for multiple independent use cases, it could also help with multiple log destinations/levels case OP was trying to solve.

I could share the code if anyone would be interested. Also if anyone reads this and sees some obvious flaws in my approach I'd appreciate the feedback.

@hawkw
Copy link
Member

hawkw commented Mar 8, 2020

@jacekchmiel I'd love to see your solution. I started investigating an implementation of per-layer filtering for tracing-subscriber that's somewhat different, but haven't had the time to finish it, so I'd love to compare notes. Also, if you've done any benchmarking of your solution, that would be very interesting --- ideally, we would want to minimize the overhead of per-layer filtering as much as possible.

@jacekchmiel
Copy link

I've created small repo with my solution. You can check it here: https://github.com/jacekchmiel/tracing-layer-dispatch. There is a criterion benchmark that does not show significant performance overhead. It may be possible that the benches are not constructed well. Feel free to open an issue/pull request there if there is something obvious to fix.

@hawkw
Copy link
Member

hawkw commented Mar 9, 2020

@jacekchmiel ah, as far as I can tell, it looks like your implementation doesn't have a mechanism for an individual layer to disable a span or event and not be notified further about that span or event, even if another layer does enable it? I think that's where most of the potential overhead we're trying to avoid would be.

@jacekchmiel
Copy link

@hawkw big thanks for the feedback! You are completely right, this is not only a potential performance problem but actually it makes my solution not work as I intended.

I'll try to think about it a little more. Hopefully I'll find some fairly simple solution with acceptable performance that will work as a workaround until you land proper layer filtering feature.

@hawkw
Copy link
Member

hawkw commented Mar 9, 2020

I can try to get the work I've been doing into a state where we can look at merging and iterating on it, since it seems like there's a lot of demand.

@jacekchmiel
Copy link

That would be great! I'd be happy to help if there is such need. For example, I could look at the feature to test it and check if it fits my use-case.

@kehlert
Copy link

kehlert commented Jan 23, 2021

If anyone else comes across this issue, here's an outline of code that works for the most recent version of the code on Github. I copied it out of a larger codebase, but I think the basic idea is clear (just don't expect it to work if you copy-paste). I'm far from a tracing expert, but it appears to get the job done (although I'm sure with some runtime cost). If somebody who knows more has any suggestions, they'd be appreciated.

fn setup() {
    let my_subscriber = Subscriber::new()
        .with_writer(MakeMyWriter {})
        .with_target(false)
        .with_ansi(false);

    let collector = tracing_subscriber::registry()
        .with(LevelFilter::from_level(Level::INFO))
        .with(
            Subscriber::new()
                .with_writer(std_out_writer)
                .with_target(false),
        )
        .with(
            Subscriber::new()
                .with_writer(file_writer)
                .with_target(false),
        )
        .with(MySubscriberWrapper {
            inner: my_subscriber,
        });

    tracing::collect::set_global_default(collector).expect("unable to set tracing collector");
}

struct MySubscriberWrapper<S> {
    inner: S,
}

impl<S: Subscribe<C>, C: tracing::Collect> Subscribe<C> for MySubscriberWrapper<S> {
    fn on_event(&self, event: &Event<'_>, context: Context<'_, C>) {
        if *event.metadata().level() == Level::ERROR {
            self.inner.on_event(event, context);
        }
    }
}

@a1ph
Copy link

a1ph commented Apr 6, 2021

It does not work for me (using tracing-subscriber = "0.2.17").

    tracing_subscriber::registry()
        .with(console_subscriber)
        .init();

fails with

296 |         .with(console_subscriber)
    |               ^^^^^^^^^^^^^^^^^^ the trait `__tracing_subscriber_Layer<Registry>` is not implemented for `FmtSubscriber`

@kehlert Any hints?

@kehlert
Copy link

kehlert commented Apr 6, 2021

It does not work for me (using tracing-subscriber = "0.2.17").

    tracing_subscriber::registry()
        .with(console_subscriber)
        .init();

fails with

296 |         .with(console_subscriber)
    |               ^^^^^^^^^^^^^^^^^^ the trait `__tracing_subscriber_Layer<Registry>` is not implemented for `FmtSubscriber`

@kehlert Any hints?

I'm using code directly from Github, and not via crates.io. That might be the difference.

Edit: Also noticed that the trait you're referring to is here: https://docs.rs/tracing-subscriber/0.2.17/tracing_subscriber/prelude/index.html

Looks like tracing_subscriber_layer::Layer<Registry> is not implemented for the console_subscriber.

@kehlert
Copy link

kehlert commented Apr 6, 2021

It does not work for me (using tracing-subscriber = "0.2.17").

    tracing_subscriber::registry()
        .with(console_subscriber)
        .init();

fails with

296 |         .with(console_subscriber)
    |               ^^^^^^^^^^^^^^^^^^ the trait `__tracing_subscriber_Layer<Registry>` is not implemented for `FmtSubscriber`

@kehlert Any hints?

@alamb, here's a fully working example. You should be able to copy-paste it into a fresh crate and it'll work.

Cargo.toml

[package]
name = "tracing_test"
version = "0.1.0"
authors = ["MyName"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tracing = {git="https://github.com/tokio-rs/tracing"}
tracing-appender = {git="https://github.com/tokio-rs/tracing"}
tracing-subscriber = {git="https://github.com/tokio-rs/tracing"} #need CollectExt

main.rs

use tracing::Level;
use tracing::{error, info, info_span, Event};
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::fmt::Subscriber;
use tracing_subscriber::subscribe::CollectExt;
use tracing_subscriber::subscribe::Context;
use tracing_subscriber::subscribe::Subscribe;

fn main() {
    let _guard = setup_logger();
    let span = info_span!("my span");
    let _enter = span.enter();
    info!("this is processed only by stdout");
    error!("this is processed by all subscribers");
}

fn setup_logger() -> tracing_appender::non_blocking::WorkerGuard {
    //these non-blocking writers can drop messages if queue > 128,000
    let (std_out_writer, guard) = tracing_appender::non_blocking(std::io::stdout());

    let collector = tracing_subscriber::registry()
        .with(LevelFilter::from_level(Level::INFO))
        .with(
            Subscriber::new()
                .with_writer(std_out_writer)
                .with_target(false),
        );

    let my_subscriber = Subscriber::new()
        .with_writer(MakeMyWriter {})
        .with_target(false);

    let collector = collector.with(MyFilter(my_subscriber));

    tracing::collect::set_global_default(collector).expect("unable to set tracing collector");
    guard
}

struct MyFilter<S>(S);

impl<S: Subscribe<C>, C: tracing::Collect> Subscribe<C> for MyFilter<S> {
    fn on_event(&self, event: &Event, context: Context<C>) {
        if *event.metadata().level() == Level::ERROR {
            self.0.on_event(event, context);
        }
    }
}

struct MakeMyWriter {}

struct MyWriter {}

impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for MakeMyWriter {
    type Writer = MyWriter;

    fn make_writer(&'a self) -> Self::Writer {
        MyWriter {}
    }
}

impl std::io::Write for MyWriter {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        println!("MyWriter is writing: {}", std::str::from_utf8(buf).unwrap());
        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

Here is the output:
image

@alamb
Copy link

alamb commented Apr 7, 2021

@tustvold and @jacobmarble -- FYI (check out the example from @kehlert in #597 (comment))

@ufoscout
Copy link

@kehlert
Do you know if there is a way to make your solution work with a EnvFilter?
e.g.:

let env_filter = EnvFilter::from_str("info,my_package=debug").unwrap();

struct MyFilter<S>{
  sub: S, 
  env_filter: EnvFilter
}

impl<S: Subscribe<C>, C: tracing::Collect> Subscribe<C> for MyFilter<S> {
    fn on_event(&self, event: &Event, context: Context<C>) {

        if self.env_filter.ACCEPT_EVENT(event) { // WHAT TO WRITE HERE?

            self.sub.on_event(event, context);
        }
    }
}

@kehlert
Copy link

kehlert commented Jul 27, 2021

@kehlert
Do you know if there is a way to make your solution work with a EnvFilter?
e.g.:

let env_filter = EnvFilter::from_str("info,my_package=debug").unwrap();

struct MyFilter<S>{
  sub: S, 
  env_filter: EnvFilter
}

impl<S: Subscribe<C>, C: tracing::Collect> Subscribe<C> for MyFilter<S> {
    fn on_event(&self, event: &Event, context: Context<C>) {

        if self.env_filter.ACCEPT_EVENT(event) { // WHAT TO WRITE HERE?

            self.sub.on_event(event, context);
        }
    }
}

Not off the top of my head. I haven't worked with an EnvFilter before.

@ufoscout
Copy link

@hawkw
Do you know if there is a way to make this example work?

pub struct MyFilter<S>{
    pub sub: S,
    pub env_filter: EnvFilter
}

impl<S: Subscriber, L: Layer<S>> Layer<S> for MyFilter<L> {
    fn on_event(&self, event: &Event, context: Context<S>) {

        if self.env_filter.enabled(event.metadata(), context.clone()) {
            self.sub.on_event(event, context);
        }

    }
}

It compiles fine but when the application starts, it throws this error at runtime (the stacktrace is from a test):

Unable to find FormattedFields in extensions; this is a bug
thread 'should_setup_logger_with_env_filter' panicked at 'Unable to find FormattedFields in extensions; this is a bug', /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-subscriber-0.2.19/src/fmt/format/mod.rs:940:18
stack backtrace:
   0: rust_begin_unwind
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:493:5
   1: core::panicking::panic_fmt
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/core/src/panicking.rs:92:14
   2: core::option::expect_failed
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/core/src/option.rs:1241:5
   3: core::option::Option<T>::expect
             at /home/ufo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:349:21
   4: <tracing_subscriber::fmt::format::FullCtx<S,N> as core::fmt::Display>::fmt
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-subscriber-0.2.19/src/fmt/format/mod.rs:938:27
   5: core::fmt::write
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/core/src/fmt/mod.rs:1094:17
   6: core::fmt::Write::write_fmt
             at /home/ufo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:184:9
   7: <&mut W as core::fmt::Write>::write_fmt
             at /home/ufo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:199:9
   8: <tracing_subscriber::fmt::format::Format<tracing_subscriber::fmt::format::Full,T> as tracing_subscriber::fmt::format::FormatEvent<S,N>>::format_event
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-subscriber-0.2.19/src/fmt/format/mod.rs:574:9
   9: <tracing_subscriber::fmt::fmt_layer::Layer<S,N,E,W> as tracing_subscriber::layer::Layer<S>>::on_event::{{closure}}
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-subscriber-0.2.19/src/fmt/fmt_layer.rs:742:16
  10: std::thread::local::LocalKey<T>::try_with
             at /home/ufo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:376:16
  11: std::thread::local::LocalKey<T>::with
             at /home/ufo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:352:9
  12: <tracing_subscriber::fmt::fmt_layer::Layer<S,N,E,W> as tracing_subscriber::layer::Layer<S>>::on_event
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-subscriber-0.2.19/src/fmt/fmt_layer.rs:726:9
  13: <my_common_logger::subscriber::MyFilter<L> as tracing_subscriber::layer::Layer<S>>::on_event
             at ./src/subscriber.rs:49:13
  14: <core::option::Option<L> as tracing_subscriber::layer::Layer<S>>::on_event
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-subscriber-0.2.19/src/layer.rs:875:13
  15: <tracing_subscriber::layer::Layered<L,S> as tracing_core::subscriber::Subscriber>::event
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-subscriber-0.2.19/src/layer.rs:659:9
  16: <tracing_subscriber::layer::Layered<L,S> as tracing_core::subscriber::Subscriber>::event
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-subscriber-0.2.19/src/layer.rs:658:9
  17: <alloc::sync::Arc<dyn tracing_core::subscriber::Subscriber+core::marker::Send+core::marker::Sync> as tracing_core::subscriber::Subscriber>::event
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-core-0.1.18/src/subscriber.rs:677:9
  18: tracing_core::dispatcher::Dispatch::event
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-core-0.1.18/src/dispatcher.rs:532:9
  19: tracing_log::dispatch_record::{{closure}}
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-log-0.1.2/src/lib.rs:204:9
  20: tracing_core::dispatcher::get_default::{{closure}}
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-core-0.1.18/src/dispatcher.rs:346:24
  21: std::thread::local::LocalKey<T>::try_with
             at /home/ufo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:376:16
  22: tracing_core::dispatcher::get_default
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-core-0.1.18/src/dispatcher.rs:343:5
  23: tracing_log::dispatch_record
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-log-0.1.2/src/lib.rs:188:5
  24: <tracing_log::log_tracer::LogTracer as log::Log>::log
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-log-0.1.2/src/log_tracer.rs:186:9
  25: log::__private_api_log
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.14/src/lib.rs:1460:5
  26: logger_env_filter_setup_it::inner1::log_smt
             at ./tests/logger_env_filter_setup_it.rs:13:9
  27: logger_env_filter_setup_it::should_setup_logger_with_env_filter::{{closure}}
             at ./tests/logger_env_filter_setup_it.rs:51:5
  28: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
             at /home/ufo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/future/mod.rs:80:19
  29: <core::pin::Pin<P> as core::future::future::Future>::poll
             at /home/ufo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/future/future.rs:120:9
  30: tokio::runtime::basic_scheduler::Inner<P>::block_on::{{closure}}::{{closure}}
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/runtime/basic_scheduler.rs:208:62
  31: tokio::coop::with_budget::{{closure}}
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/coop.rs:106:9
  32: std::thread::local::LocalKey<T>::try_with
             at /home/ufo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:376:16
  33: std::thread::local::LocalKey<T>::with
             at /home/ufo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:352:9
  34: tokio::coop::with_budget
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/coop.rs:99:5
  35: tokio::coop::budget
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/coop.rs:76:5
  36: tokio::runtime::basic_scheduler::Inner<P>::block_on::{{closure}}
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/runtime/basic_scheduler.rs:208:39
  37: tokio::runtime::basic_scheduler::enter::{{closure}}
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/runtime/basic_scheduler.rs:299:29
  38: tokio::macros::scoped_tls::ScopedKey<T>::set
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/macros/scoped_tls.rs:61:9
  39: tokio::runtime::basic_scheduler::enter
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/runtime/basic_scheduler.rs:299:5
  40: tokio::runtime::basic_scheduler::Inner<P>::block_on
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/runtime/basic_scheduler.rs:197:9
  41: tokio::runtime::basic_scheduler::InnerGuard<P>::block_on
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/runtime/basic_scheduler.rs:452:9
  42: tokio::runtime::basic_scheduler::BasicScheduler<P>::block_on
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/runtime/basic_scheduler.rs:157:24
  43: tokio::runtime::Runtime::block_on
             at /home/ufo/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/runtime/mod.rs:450:46
  44: logger_env_filter_setup_it::should_setup_logger_with_env_filter
             at ./tests/logger_env_filter_setup_it.rs:54:5
  45: logger_env_filter_setup_it::should_setup_logger_with_env_filter::{{closure}}
             at ./tests/logger_env_filter_setup_it.rs:39:7
  46: core::ops::function::FnOnce::call_once
             at /home/ufo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:227:5
  47: core::ops::function::FnOnce::call_once
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/core/src/ops/function.rs:227:5

hawkw added a commit that referenced this issue Sep 9, 2021
## Motivation

Currently, filtering with `Layer`s is always _global_. If a `Layer`
performs filtering, it will disable a span or event for _all_ layers
that compose the current subscriber. In some cases, however, it is often
desirable for individual layers to see different spans or events than
the rest of the `Layer` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Layer`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in `Layer::on_event`
and `Layer::new_span` based on some filter is relatively simple, this
wouldn't really be an ideal solution, for a number of reasons. A proper
per-layer filtering implementation would satisfy the following
_desiderata_:

* If a per-layer filter disables a span, it shouldn't be present _for
  the layer that filter is attached to_ when iterating over span
  contexts (such as `Context::event_scope`, `SpanRef::scope`, etc), or
  when looking up a span's parents.
* When _all_ per-layer filters disable a span or event, it should be
  completely disabled, rather than simply skipped by those particular
  layers. This means that per-layer filters should participate in
  `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-layer filters shouldn't interfere with non-filtered `Layer`s.
  If a subscriber contains layers without any filters, as well as
  layers with per-layer filters, the non-filtered `Layer`s should
  behave exactly as they would without any per-layer filters present.
* Similarly, per-layer filters shouldn't interfere with global
  filtering. If a `Layer` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-layer filters should also
  be effected by the global filter.
* Per-layer filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-layer-filtered layers_.
* Per-layer filters should be _tree-shaped_. If a `Subscriber` consists
  of multiple layers that have been `Layered` together to form new
  `Layer`s, and some of the `Layered` layers have per-layer filters,
  those per-layer filters should effect all layers in that subtree.
  Similarly, if `Layer`s in a per-layer filtered subtree have their
  _own_ per-layer filters, those layers should be effected by the union
  of their own filters and any per-layer filters that wrap them at
  higher levels in the tree.

Meeting all these requirements means that implementing per-layer
filtering correctly is somewhat more complex than simply skipping
events and spans in a `Layer`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-layer filtering
for `tracing-subscriber` v0.2. It should be possible to add this in a
point release without a breaking change --- in particular, the new
per-layer filtering feature _doesn't_ interfere with the global
filtering behavior that layers currently have when not using the new
per-layer filtering APIs, so existing configurations should all behave
exactly as they do now.

### Implementation

The new per-layer filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Layer`. When `enabled` is called on a `Filtered` layer,
it checks the metadata against the `Filter` and stores whether that
filter enabled the span/event in a thread-local cell. If every per-layer
filter disables a span/event, and there are no non-per-layer-filtered
layers in use, the `Registry`'s `enabled` will return `false`.
Otherwise, when the span/event is recorded, each `Filtered` layer's
`on_event` and `new_span` method checks with the `Registry` to see
whether _it_ enabled or disabled that span/event, and skips passing it
to the wrapped layer if it was disabled. When a span is recorded, the
`Registry` which filters disabled it, so that they can skip it when
looking up spans in other callbacks.

 A new `on_layer` method was added to the `Layer` trait, which allows
 running a callback when adding a `Layer` to a `Subscriber`. This allows
 mutating both the `Layer` and the `Subscriber`, since they are both
 passed by value when layering a `Layer` onto a `Subscriber`, and a new
 `register_filter` method was added to `LookupSpan`, which returns a
 `FilterId` type. When a `Filtered` layer is added to a subscriber that
 implements `LookupSpan`, it calls `register_filter` in its `on_layer`
 hook to generate a unique ID for its filter. `FilterId` can be used to
 look up whether a span or event was enabled by the `Filtered` layer.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-layer filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` layer in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 layers, let alone 64 separate per-layer
filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` layers. When `Filtered` layers are nested
using the `Layered`, the `Context` _combines_ the filter ID of the inner
and outer layers so that spans can be skipped if they were disabled by
either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `layer` module. Since we
  were adding more code to the `Layer` module (the `Filter` trait), the
  `layer.rs` file got significantly longer and was becoming somewhat
  hard to navigate. Therefore, I split out the `Context` and `Layered`
  types into their own files. Most of the code in those files is the
  same as it was before, although some of it was changed in order to
  support per-layer filtering. Apologies in advance to reviewers for
  this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-layer filtering, I added a new `Layer`
  implementation that does the same thing, but implements `Layer`
  rather than `Subscriber`. This allows testing per-layer filter
  behavior in the same way we test global filter behavior. Reviewers
  should feel free to just skim the test the test support changes, or
  skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow
up changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-layer filters. This is in
      order to avoid stabilizing a lot of the per-layer filtering
      design as public API without taking the time to ensure there are
      no serious issues. In the future, we will want to add new APIs to
      allow users to implement their own root `Subscriber`s that can
      host layers with per-layer filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-layer filter. We should add an
      implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably _also_
      be used as global filters. We could consider adding `Layer`
      implementations to allow them to be used for global filtering.
* [ ] We could consider adding new `Filter` implementations for performing
      common filtering predicates. For example, "only enable spans/events
      with a particular target or a set of targets", "only enable spans",
      "only enable events", etc. Then, we could add a combinator API to
      combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-layer filter evaluations, so
      that we don't need to always use `enabled` when there are several
      per-layer filters that disagree on whether or not they want a
      given span/event. There are a lot of unused bits in `Interest`
      that could potentially be used for this purpose. However, this
      would require new API changes in `tracing-core`, and is therefore
      out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
@hawkw
Copy link
Member

hawkw commented Sep 13, 2021

Fixed by #1523.

@hawkw hawkw closed this as completed Sep 13, 2021
missing log features automation moved this from To do to Done Sep 13, 2021
davidbarsky added a commit that referenced this issue Nov 29, 2021
## Motivation

Currently, filtering with `Layer`s is always _global_. If a `Layer`
performs filtering, it will disable a span or event for _all_ layers
that compose the current subscriber. In some cases, however, it is often
desirable for individual layers to see different spans or events than
the rest of the `Layer` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Layer`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in `Layer::on_event`
and `Layer::new_span` based on some filter is relatively simple, this
wouldn't really be an ideal solution, for a number of reasons. A proper
per-layer filtering implementation would satisfy the following
_desiderata_:

* If a per-layer filter disables a span, it shouldn't be present _for
  the layer that filter is attached to_ when iterating over span
  contexts (such as `Context::event_scope`, `SpanRef::scope`, etc), or
  when looking up a span's parents.
* When _all_ per-layer filters disable a span or event, it should be
  completely disabled, rather than simply skipped by those particular
  layers. This means that per-layer filters should participate in
  `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-layer filters shouldn't interfere with non-filtered `Layer`s.
  If a subscriber contains layers without any filters, as well as
  layers with per-layer filters, the non-filtered `Layer`s should
  behave exactly as they would without any per-layer filters present.
* Similarly, per-layer filters shouldn't interfere with global
  filtering. If a `Layer` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-layer filters should also
  be effected by the global filter.
* Per-layer filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-layer-filtered layers_.
* Per-layer filters should be _tree-shaped_. If a `Subscriber` consists
  of multiple layers that have been `Layered` together to form new
  `Layer`s, and some of the `Layered` layers have per-layer filters,
  those per-layer filters should effect all layers in that subtree.
  Similarly, if `Layer`s in a per-layer filtered subtree have their
  _own_ per-layer filters, those layers should be effected by the union
  of their own filters and any per-layer filters that wrap them at
  higher levels in the tree.

Meeting all these requirements means that implementing per-layer
filtering correctly is somewhat more complex than simply skipping
events and spans in a `Layer`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-layer filtering
for `tracing-subscriber` v0.2. It should be possible to add this in a
point release without a breaking change --- in particular, the new
per-layer filtering feature _doesn't_ interfere with the global
filtering behavior that layers currently have when not using the new
per-layer filtering APIs, so existing configurations should all behave
exactly as they do now.

### Implementation

The new per-layer filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Layer`. When `enabled` is called on a `Filtered` layer,
it checks the metadata against the `Filter` and stores whether that
filter enabled the span/event in a thread-local cell. If every per-layer
filter disables a span/event, and there are no non-per-layer-filtered
layers in use, the `Registry`'s `enabled` will return `false`.
Otherwise, when the span/event is recorded, each `Filtered` layer's
`on_event` and `new_span` method checks with the `Registry` to see
whether _it_ enabled or disabled that span/event, and skips passing it
to the wrapped layer if it was disabled. When a span is recorded, the
`Registry` which filters disabled it, so that they can skip it when
looking up spans in other callbacks.

 A new `on_layer` method was added to the `Layer` trait, which allows
 running a callback when adding a `Layer` to a `Subscriber`. This allows
 mutating both the `Layer` and the `Subscriber`, since they are both
 passed by value when layering a `Layer` onto a `Subscriber`, and a new
 `register_filter` method was added to `LookupSpan`, which returns a
 `FilterId` type. When a `Filtered` layer is added to a subscriber that
 implements `LookupSpan`, it calls `register_filter` in its `on_layer`
 hook to generate a unique ID for its filter. `FilterId` can be used to
 look up whether a span or event was enabled by the `Filtered` layer.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-layer filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` layer in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 layers, let alone 64 separate per-layer
filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` layers. When `Filtered` layers are nested
using the `Layered`, the `Context` _combines_ the filter ID of the inner
and outer layers so that spans can be skipped if they were disabled by
either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `layer` module. Since we
  were adding more code to the `Layer` module (the `Filter` trait), the
  `layer.rs` file got significantly longer and was becoming somewhat
  hard to navigate. Therefore, I split out the `Context` and `Layered`
  types into their own files. Most of the code in those files is the
  same as it was before, although some of it was changed in order to
  support per-layer filtering. Apologies in advance to reviewers for
  this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-layer filtering, I added a new `Layer`
  implementation that does the same thing, but implements `Layer`
  rather than `Subscriber`. This allows testing per-layer filter
  behavior in the same way we test global filter behavior. Reviewers
  should feel free to just skim the test the test support changes, or
  skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow
up changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-layer filters. This is in
      order to avoid stabilizing a lot of the per-layer filtering
      design as public API without taking the time to ensure there are
      no serious issues. In the future, we will want to add new APIs to
      allow users to implement their own root `Subscriber`s that can
      host layers with per-layer filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-layer filter. We should add an
      implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably _also_
      be used as global filters. We could consider adding `Layer`
      implementations to allow them to be used for global filtering.
* [ ] We could consider adding new `Filter` implementations for performing
      common filtering predicates. For example, "only enable spans/events
      with a particular target or a set of targets", "only enable spans",
      "only enable events", etc. Then, we could add a combinator API to
      combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-layer filter evaluations, so
      that we don't need to always use `enabled` when there are several
      per-layer filters that disagree on whether or not they want a
      given span/event. There are a lot of unused bits in `Interest`
      that could potentially be used for this purpose. However, this
      would require new API changes in `tracing-core`, and is therefore
      out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Mar 23, 2022
; This is the 1st commit message:

subscriber: implement per-subscriber filtering (#1523)

## Motivation

Currently, filtering with `Subscribe`s is always _global_. If a
`Subscribe` performs filtering, it will disable a span or event for
_all_ subscribers that compose the current subscriber. In some cases,
however, it is often desirable for individual subscribers to see
different spans or events than the rest of the `Subscribe` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Subscribe`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in
`Subscribe::on_event` and `Subscribe::new_span` based on some filter is
relatively simple, this wouldn't really be an ideal solution, for a
number of reasons. A proper per-subscriber filtering implementation
would satisfy the following _desiderata_:

* If a per-subscriber filter disables a span, it shouldn't be present
  _for the subscriber that filter is attached to_ when iterating over
  span contexts (such as `Context::event_scope`, `SpanRef::scope`, etc),
  or when looking up a span's parents.
* When _all_ per-subscriber filters disable a span or event, it should
  be completely disabled, rather than simply skipped by those particular
  subscribers. This means that per-subscriber filters should participate
  in `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-subscriber filters shouldn't interfere with non-filtered
  `Subscribe`s. If a subscriber contains subscribers without any
  filters, as well as subscribers with per-subscriber filters, the
  non-filtered `Subscribe`s should behave exactly as they would without
  any per-subscriber filters present.
* Similarly, per-subscriber filters shouldn't interfere with global
  filtering. If a `Subscribe` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-subscriber filters should
  also be effected by the global filter.
* Per-subscriber filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-subscriber-filtered subscribers_.
* Per-subscriber filters should be _tree-shaped_. If a `Subscriber`
  consists of multiple subscribers that have been `Subscribeed` together
  to form new `Subscribe`s, and some of the `Subscribeed` subscribers
  have per-subscriber filters, those per-subscriber filters should
  effect all subscribers in that subtree. Similarly, if `Subscribe`s in
  a per-subscriber filtered subtree have their _own_ per-subscriber
  filters, those subscribers should be effected by the union of their
  own filters and any per-subscriber filters that wrap them at higher
  levels in the tree.

Meeting all these requirements means that implementing per-subscriber
filtering correctly is somewhat more complex than simply skipping events
and spans in a `Subscribe`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-subscriber
filtering for `tracing-subscriber` v0.2. It should be possible to add
this in a point release without a breaking change --- in particular, the
new per-subscriber filtering feature _doesn't_ interfere with the global
filtering behavior that subscribers currently have when not using the
new per-subscriber filtering APIs, so existing configurations should all
behave exactly as they do now.

### Implementation

The new per-subscriber filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Subscribe`. When `enabled` is called on a `Filtered`
subscriber, it checks the metadata against the `Filter` and stores
whether that filter enabled the span/event in a thread-local cell. If
every per-subscriber filter disables a span/event, and there are no
non-per-subscriber-filtered subscribers in use, the `Registry`'s
`enabled` will return `false`. Otherwise, when the span/event is
recorded, each `Filtered` subscriber's `on_event` and `new_span` method
checks with the `Registry` to see whether _it_ enabled or disabled that
span/event, and skips passing it to the wrapped subscriber if it was
disabled. When a span is recorded, the `Registry` which filters disabled
it, so that they can skip it when looking up spans in other callbacks.

 A new `on_subscriber` method was added to the `Subscribe` trait, which
 allows running a callback when adding a `Subscribe` to a `Subscriber`.
 This allows mutating both the `Subscribe` and the `Subscriber`, since
 they are both passed by value when subscribering a `Subscribe` onto a
 `Subscriber`, and a new `register_filter` method was added to
 `LookupSpan`, which returns a `FilterId` type. When a `Filtered`
 subscriber is added to a subscriber that implements `LookupSpan`, it
 calls `register_filter` in its `on_subscriber` hook to generate a
 unique ID for its filter. `FilterId` can be used to look up whether a
 span or event was enabled by the `Filtered` subscriber.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-subscriber filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` subscriber in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 subscribers, let alone 64 separate
per-subscriber filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` subscribers. When `Filtered` subscribers are
nested using the `Subscribeed`, the `Context` _combines_ the filter ID
of the inner and outer subscribers so that spans can be skipped if they
were disabled by either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `subscriber` module. Since
  we were adding more code to the `Subscribe` module (the `Filter`
  trait), the `subscriber.rs` file got significantly longer and was
  becoming somewhat hard to navigate. Therefore, I split out the
  `Context` and `Subscribeed` types into their own files. Most of the
  code in those files is the same as it was before, although some of it
  was changed in order to support per-subscriber filtering. Apologies in
  advance to reviewers for this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-subscriber filtering, I added a new
  `Subscribe` implementation that does the same thing, but implements
  `Subscribe` rather than `Subscriber`. This allows testing
  per-subscriber filter behavior in the same way we test global filter
  behavior. Reviewers should feel free to just skim the test the test
  support changes, or skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow up
changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-subscriber filters. This is
      in order to avoid stabilizing a lot of the per-subscriber
      filtering design as public API without taking the time to ensure
      there are no serious issues. In the future, we will want to add
      new APIs to allow users to implement their own root `Subscriber`s
      that can host subscribers with per-subscriber filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-subscriber filter. We should add
      an implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably
      _also_ be used as global filters. We could consider adding
      `Subscribe` implementations to allow them to be used for global
      filtering.
* [ ] We could consider adding new `Filter` implementations for
      performing common filtering predicates. For example, "only enable
      spans/events with a particular target or a set of targets", "only
      enable spans", "only enable events", etc. Then, we could add a
      combinator API to combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-subscriber filter
      evaluations, so that we don't need to always use `enabled` when
      there are several per-subscriber filters that disagree on whether
      or not they want a given span/event. There are a lot of unused
      bits in `Interest` that could potentially be used for this
      purpose. However, this would require new API changes in
      `tracing-core`, and is therefore out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>

; The commit message #2 will be skipped:

; fixup: start making changes for filter

; The commit message #3 will be skipped:

; fixup: move stuff to `subscribe` folder from `layer

; The commit message #4 will be skipped:

; fixup: additional compilation errors
hawkw added a commit that referenced this issue Mar 23, 2022
## Motivation

Currently, filtering with `Subscribe`s is always _global_. If a
`Subscribe` performs filtering, it will disable a span or event for
_all_ subscribers that compose the current subscriber. In some cases,
however, it is often desirable for individual subscribers to see
different spans or events than the rest of the `Subscribe` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Subscribe`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in
`Subscribe::on_event` and `Subscribe::new_span` based on some filter is
relatively simple, this wouldn't really be an ideal solution, for a
number of reasons. A proper per-subscriber filtering implementation
would satisfy the following _desiderata_:

* If a per-subscriber filter disables a span, it shouldn't be present
  _for the subscriber that filter is attached to_ when iterating over
  span contexts (such as `Context::event_scope`, `SpanRef::scope`, etc),
  or when looking up a span's parents.
* When _all_ per-subscriber filters disable a span or event, it should
  be completely disabled, rather than simply skipped by those particular
  subscribers. This means that per-subscriber filters should participate
  in `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-subscriber filters shouldn't interfere with non-filtered
  `Subscribe`s. If a subscriber contains subscribers without any
  filters, as well as subscribers with per-subscriber filters, the
  non-filtered `Subscribe`s should behave exactly as they would without
  any per-subscriber filters present.
* Similarly, per-subscriber filters shouldn't interfere with global
  filtering. If a `Subscribe` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-subscriber filters should
  also be effected by the global filter.
* Per-subscriber filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-subscriber-filtered subscribers_.
* Per-subscriber filters should be _tree-shaped_. If a `Subscriber`
  consists of multiple subscribers that have been `Subscribeed` together
  to form new `Subscribe`s, and some of the `Subscribeed` subscribers
  have per-subscriber filters, those per-subscriber filters should
  effect all subscribers in that subtree. Similarly, if `Subscribe`s in
  a per-subscriber filtered subtree have their _own_ per-subscriber
  filters, those subscribers should be effected by the union of their
  own filters and any per-subscriber filters that wrap them at higher
  levels in the tree.

Meeting all these requirements means that implementing per-subscriber
filtering correctly is somewhat more complex than simply skipping events
and spans in a `Subscribe`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-subscriber
filtering for `tracing-subscriber` v0.2. It should be possible to add
this in a point release without a breaking change --- in particular, the
new per-subscriber filtering feature _doesn't_ interfere with the global
filtering behavior that subscribers currently have when not using the
new per-subscriber filtering APIs, so existing configurations should all
behave exactly as they do now.

### Implementation

The new per-subscriber filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Subscribe`. When `enabled` is called on a `Filtered`
subscriber, it checks the metadata against the `Filter` and stores
whether that filter enabled the span/event in a thread-local cell. If
every per-subscriber filter disables a span/event, and there are no
non-per-subscriber-filtered subscribers in use, the `Registry`'s
`enabled` will return `false`. Otherwise, when the span/event is
recorded, each `Filtered` subscriber's `on_event` and `new_span` method
checks with the `Registry` to see whether _it_ enabled or disabled that
span/event, and skips passing it to the wrapped subscriber if it was
disabled. When a span is recorded, the `Registry` which filters disabled
it, so that they can skip it when looking up spans in other callbacks.

 A new `on_subscriber` method was added to the `Subscribe` trait, which
 allows running a callback when adding a `Subscribe` to a `Subscriber`.
 This allows mutating both the `Subscribe` and the `Subscriber`, since
 they are both passed by value when subscribering a `Subscribe` onto a
 `Subscriber`, and a new `register_filter` method was added to
 `LookupSpan`, which returns a `FilterId` type. When a `Filtered`
 subscriber is added to a subscriber that implements `LookupSpan`, it
 calls `register_filter` in its `on_subscriber` hook to generate a
 unique ID for its filter. `FilterId` can be used to look up whether a
 span or event was enabled by the `Filtered` subscriber.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-subscriber filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` subscriber in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 subscribers, let alone 64 separate
per-subscriber filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` subscribers. When `Filtered` subscribers are
nested using the `Subscribeed`, the `Context` _combines_ the filter ID
of the inner and outer subscribers so that spans can be skipped if they
were disabled by either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `subscriber` module. Since
  we were adding more code to the `Subscribe` module (the `Filter`
  trait), the `subscriber.rs` file got significantly longer and was
  becoming somewhat hard to navigate. Therefore, I split out the
  `Context` and `Subscribeed` types into their own files. Most of the
  code in those files is the same as it was before, although some of it
  was changed in order to support per-subscriber filtering. Apologies in
  advance to reviewers for this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-subscriber filtering, I added a new
  `Subscribe` implementation that does the same thing, but implements
  `Subscribe` rather than `Subscriber`. This allows testing
  per-subscriber filter behavior in the same way we test global filter
  behavior. Reviewers should feel free to just skim the test the test
  support changes, or skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow up
changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-subscriber filters. This is
      in order to avoid stabilizing a lot of the per-subscriber
      filtering design as public API without taking the time to ensure
      there are no serious issues. In the future, we will want to add
      new APIs to allow users to implement their own root `Subscriber`s
      that can host subscribers with per-subscriber filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-subscriber filter. We should add
      an implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably
      _also_ be used as global filters. We could consider adding
      `Subscribe` implementations to allow them to be used for global
      filtering.
* [ ] We could consider adding new `Filter` implementations for
      performing common filtering predicates. For example, "only enable
      spans/events with a particular target or a set of targets", "only
      enable spans", "only enable events", etc. Then, we could add a
      combinator API to combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-subscriber filter
      evaluations, so that we don't need to always use `enabled` when
      there are several per-subscriber filters that disagree on whether
      or not they want a given span/event. There are a lot of unused
      bits in `Interest` that could potentially be used for this
      purpose. However, this would require new API changes in
      `tracing-core`, and is therefore out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Mar 23, 2022
## Motivation

Currently, filtering with `Subscribe`s is always _global_. If a
`Subscribe` performs filtering, it will disable a span or event for
_all_ subscribers that compose the current subscriber. In some cases,
however, it is often desirable for individual subscribers to see
different spans or events than the rest of the `Subscribe` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Subscribe`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in
`Subscribe::on_event` and `Subscribe::new_span` based on some filter is
relatively simple, this wouldn't really be an ideal solution, for a
number of reasons. A proper per-subscriber filtering implementation
would satisfy the following _desiderata_:

* If a per-subscriber filter disables a span, it shouldn't be present
  _for the subscriber that filter is attached to_ when iterating over
  span contexts (such as `Context::event_scope`, `SpanRef::scope`, etc),
  or when looking up a span's parents.
* When _all_ per-subscriber filters disable a span or event, it should
  be completely disabled, rather than simply skipped by those particular
  subscribers. This means that per-subscriber filters should participate
  in `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-subscriber filters shouldn't interfere with non-filtered
  `Subscribe`s. If a subscriber contains subscribers without any
  filters, as well as subscribers with per-subscriber filters, the
  non-filtered `Subscribe`s should behave exactly as they would without
  any per-subscriber filters present.
* Similarly, per-subscriber filters shouldn't interfere with global
  filtering. If a `Subscribe` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-subscriber filters should
  also be effected by the global filter.
* Per-subscriber filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-subscriber-filtered subscribers_.
* Per-subscriber filters should be _tree-shaped_. If a `Subscriber`
  consists of multiple subscribers that have been `Subscribeed` together
  to form new `Subscribe`s, and some of the `Subscribeed` subscribers
  have per-subscriber filters, those per-subscriber filters should
  effect all subscribers in that subtree. Similarly, if `Subscribe`s in
  a per-subscriber filtered subtree have their _own_ per-subscriber
  filters, those subscribers should be effected by the union of their
  own filters and any per-subscriber filters that wrap them at higher
  levels in the tree.

Meeting all these requirements means that implementing per-subscriber
filtering correctly is somewhat more complex than simply skipping events
and spans in a `Subscribe`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-subscriber
filtering for `tracing-subscriber` v0.2. It should be possible to add
this in a point release without a breaking change --- in particular, the
new per-subscriber filtering feature _doesn't_ interfere with the global
filtering behavior that subscribers currently have when not using the
new per-subscriber filtering APIs, so existing configurations should all
behave exactly as they do now.

### Implementation

The new per-subscriber filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Subscribe`. When `enabled` is called on a `Filtered`
subscriber, it checks the metadata against the `Filter` and stores
whether that filter enabled the span/event in a thread-local cell. If
every per-subscriber filter disables a span/event, and there are no
non-per-subscriber-filtered subscribers in use, the `Registry`'s
`enabled` will return `false`. Otherwise, when the span/event is
recorded, each `Filtered` subscriber's `on_event` and `new_span` method
checks with the `Registry` to see whether _it_ enabled or disabled that
span/event, and skips passing it to the wrapped subscriber if it was
disabled. When a span is recorded, the `Registry` which filters disabled
it, so that they can skip it when looking up spans in other callbacks.

 A new `on_subscriber` method was added to the `Subscribe` trait, which
 allows running a callback when adding a `Subscribe` to a `Subscriber`.
 This allows mutating both the `Subscribe` and the `Subscriber`, since
 they are both passed by value when subscribering a `Subscribe` onto a
 `Subscriber`, and a new `register_filter` method was added to
 `LookupSpan`, which returns a `FilterId` type. When a `Filtered`
 subscriber is added to a subscriber that implements `LookupSpan`, it
 calls `register_filter` in its `on_subscriber` hook to generate a
 unique ID for its filter. `FilterId` can be used to look up whether a
 span or event was enabled by the `Filtered` subscriber.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-subscriber filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` subscriber in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 subscribers, let alone 64 separate
per-subscriber filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` subscribers. When `Filtered` subscribers are
nested using the `Subscribeed`, the `Context` _combines_ the filter ID
of the inner and outer subscribers so that spans can be skipped if they
were disabled by either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `subscriber` module. Since
  we were adding more code to the `Subscribe` module (the `Filter`
  trait), the `subscriber.rs` file got significantly longer and was
  becoming somewhat hard to navigate. Therefore, I split out the
  `Context` and `Subscribeed` types into their own files. Most of the
  code in those files is the same as it was before, although some of it
  was changed in order to support per-subscriber filtering. Apologies in
  advance to reviewers for this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-subscriber filtering, I added a new
  `Subscribe` implementation that does the same thing, but implements
  `Subscribe` rather than `Subscriber`. This allows testing
  per-subscriber filter behavior in the same way we test global filter
  behavior. Reviewers should feel free to just skim the test the test
  support changes, or skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow up
changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-subscriber filters. This is
      in order to avoid stabilizing a lot of the per-subscriber
      filtering design as public API without taking the time to ensure
      there are no serious issues. In the future, we will want to add
      new APIs to allow users to implement their own root `Subscriber`s
      that can host subscribers with per-subscriber filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-subscriber filter. We should add
      an implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably
      _also_ be used as global filters. We could consider adding
      `Subscribe` implementations to allow them to be used for global
      filtering.
* [ ] We could consider adding new `Filter` implementations for
      performing common filtering predicates. For example, "only enable
      spans/events with a particular target or a set of targets", "only
      enable spans", "only enable events", etc. Then, we could add a
      combinator API to combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-subscriber filter
      evaluations, so that we don't need to always use `enabled` when
      there are several per-subscriber filters that disagree on whether
      or not they want a given span/event. There are a lot of unused
      bits in `Interest` that could potentially be used for this
      purpose. However, this would require new API changes in
      `tracing-core`, and is therefore out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Mar 23, 2022
## Motivation

Currently, filtering with `Subscribe`s is always _global_. If a
`Subscribe` performs filtering, it will disable a span or event for
_all_ subscribers that compose the current subscriber. In some cases,
however, it is often desirable for individual subscribers to see
different spans or events than the rest of the `Subscribe` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Subscribe`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in
`Subscribe::on_event` and `Subscribe::new_span` based on some filter is
relatively simple, this wouldn't really be an ideal solution, for a
number of reasons. A proper per-subscriber filtering implementation
would satisfy the following _desiderata_:

* If a per-subscriber filter disables a span, it shouldn't be present
  _for the subscriber that filter is attached to_ when iterating over
  span contexts (such as `Context::event_scope`, `SpanRef::scope`, etc),
  or when looking up a span's parents.
* When _all_ per-subscriber filters disable a span or event, it should
  be completely disabled, rather than simply skipped by those particular
  subscribers. This means that per-subscriber filters should participate
  in `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-subscriber filters shouldn't interfere with non-filtered
  `Subscribe`s. If a subscriber contains subscribers without any
  filters, as well as subscribers with per-subscriber filters, the
  non-filtered `Subscribe`s should behave exactly as they would without
  any per-subscriber filters present.
* Similarly, per-subscriber filters shouldn't interfere with global
  filtering. If a `Subscribe` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-subscriber filters should
  also be effected by the global filter.
* Per-subscriber filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-subscriber-filtered subscribers_.
* Per-subscriber filters should be _tree-shaped_. If a `Subscriber`
  consists of multiple subscribers that have been `Subscribeed` together
  to form new `Subscribe`s, and some of the `Subscribeed` subscribers
  have per-subscriber filters, those per-subscriber filters should
  effect all subscribers in that subtree. Similarly, if `Subscribe`s in
  a per-subscriber filtered subtree have their _own_ per-subscriber
  filters, those subscribers should be effected by the union of their
  own filters and any per-subscriber filters that wrap them at higher
  levels in the tree.

Meeting all these requirements means that implementing per-subscriber
filtering correctly is somewhat more complex than simply skipping events
and spans in a `Subscribe`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-subscriber
filtering for `tracing-subscriber` v0.2. It should be possible to add
this in a point release without a breaking change --- in particular, the
new per-subscriber filtering feature _doesn't_ interfere with the global
filtering behavior that subscribers currently have when not using the
new per-subscriber filtering APIs, so existing configurations should all
behave exactly as they do now.

### Implementation

The new per-subscriber filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Subscribe`. When `enabled` is called on a `Filtered`
subscriber, it checks the metadata against the `Filter` and stores
whether that filter enabled the span/event in a thread-local cell. If
every per-subscriber filter disables a span/event, and there are no
non-per-subscriber-filtered subscribers in use, the `Registry`'s
`enabled` will return `false`. Otherwise, when the span/event is
recorded, each `Filtered` subscriber's `on_event` and `new_span` method
checks with the `Registry` to see whether _it_ enabled or disabled that
span/event, and skips passing it to the wrapped subscriber if it was
disabled. When a span is recorded, the `Registry` which filters disabled
it, so that they can skip it when looking up spans in other callbacks.

 A new `on_subscriber` method was added to the `Subscribe` trait, which
 allows running a callback when adding a `Subscribe` to a `Subscriber`.
 This allows mutating both the `Subscribe` and the `Subscriber`, since
 they are both passed by value when subscribering a `Subscribe` onto a
 `Subscriber`, and a new `register_filter` method was added to
 `LookupSpan`, which returns a `FilterId` type. When a `Filtered`
 subscriber is added to a subscriber that implements `LookupSpan`, it
 calls `register_filter` in its `on_subscriber` hook to generate a
 unique ID for its filter. `FilterId` can be used to look up whether a
 span or event was enabled by the `Filtered` subscriber.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-subscriber filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` subscriber in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 subscribers, let alone 64 separate
per-subscriber filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` subscribers. When `Filtered` subscribers are
nested using the `Subscribeed`, the `Context` _combines_ the filter ID
of the inner and outer subscribers so that spans can be skipped if they
were disabled by either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `subscriber` module. Since
  we were adding more code to the `Subscribe` module (the `Filter`
  trait), the `subscriber.rs` file got significantly longer and was
  becoming somewhat hard to navigate. Therefore, I split out the
  `Context` and `Subscribeed` types into their own files. Most of the
  code in those files is the same as it was before, although some of it
  was changed in order to support per-subscriber filtering. Apologies in
  advance to reviewers for this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-subscriber filtering, I added a new
  `Subscribe` implementation that does the same thing, but implements
  `Subscribe` rather than `Subscriber`. This allows testing
  per-subscriber filter behavior in the same way we test global filter
  behavior. Reviewers should feel free to just skim the test the test
  support changes, or skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow up
changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-subscriber filters. This is
      in order to avoid stabilizing a lot of the per-subscriber
      filtering design as public API without taking the time to ensure
      there are no serious issues. In the future, we will want to add
      new APIs to allow users to implement their own root `Subscriber`s
      that can host subscribers with per-subscriber filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-subscriber filter. We should add
      an implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably
      _also_ be used as global filters. We could consider adding
      `Subscribe` implementations to allow them to be used for global
      filtering.
* [ ] We could consider adding new `Filter` implementations for
      performing common filtering predicates. For example, "only enable
      spans/events with a particular target or a set of targets", "only
      enable spans", "only enable events", etc. Then, we could add a
      combinator API to combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-subscriber filter
      evaluations, so that we don't need to always use `enabled` when
      there are several per-subscriber filters that disagree on whether
      or not they want a given span/event. There are a lot of unused
      bits in `Interest` that could potentially be used for this
      purpose. However, this would require new API changes in
      `tracing-core`, and is therefore out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Mar 24, 2022
## Motivation

Currently, filtering with `Subscribe`s is always _global_. If a
`Subscribe` performs filtering, it will disable a span or event for
_all_ subscribers that compose the current subscriber. In some cases,
however, it is often desirable for individual subscribers to see
different spans or events than the rest of the `Subscribe` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Subscribe`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in
`Subscribe::on_event` and `Subscribe::new_span` based on some filter is
relatively simple, this wouldn't really be an ideal solution, for a
number of reasons. A proper per-subscriber filtering implementation
would satisfy the following _desiderata_:

* If a per-subscriber filter disables a span, it shouldn't be present
  _for the subscriber that filter is attached to_ when iterating over
  span contexts (such as `Context::event_scope`, `SpanRef::scope`, etc),
  or when looking up a span's parents.
* When _all_ per-subscriber filters disable a span or event, it should
  be completely disabled, rather than simply skipped by those particular
  subscribers. This means that per-subscriber filters should participate
  in `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-subscriber filters shouldn't interfere with non-filtered
  `Subscribe`s. If a subscriber contains subscribers without any
  filters, as well as subscribers with per-subscriber filters, the
  non-filtered `Subscribe`s should behave exactly as they would without
  any per-subscriber filters present.
* Similarly, per-subscriber filters shouldn't interfere with global
  filtering. If a `Subscribe` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-subscriber filters should
  also be effected by the global filter.
* Per-subscriber filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-subscriber-filtered subscribers_.
* Per-subscriber filters should be _tree-shaped_. If a `Subscriber`
  consists of multiple subscribers that have been `Subscribeed` together
  to form new `Subscribe`s, and some of the `Subscribeed` subscribers
  have per-subscriber filters, those per-subscriber filters should
  effect all subscribers in that subtree. Similarly, if `Subscribe`s in
  a per-subscriber filtered subtree have their _own_ per-subscriber
  filters, those subscribers should be effected by the union of their
  own filters and any per-subscriber filters that wrap them at higher
  levels in the tree.

Meeting all these requirements means that implementing per-subscriber
filtering correctly is somewhat more complex than simply skipping events
and spans in a `Subscribe`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-subscriber
filtering for `tracing-subscriber` v0.2. It should be possible to add
this in a point release without a breaking change --- in particular, the
new per-subscriber filtering feature _doesn't_ interfere with the global
filtering behavior that subscribers currently have when not using the
new per-subscriber filtering APIs, so existing configurations should all
behave exactly as they do now.

### Implementation

The new per-subscriber filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Subscribe`. When `enabled` is called on a `Filtered`
subscriber, it checks the metadata against the `Filter` and stores
whether that filter enabled the span/event in a thread-local cell. If
every per-subscriber filter disables a span/event, and there are no
non-per-subscriber-filtered subscribers in use, the `Registry`'s
`enabled` will return `false`. Otherwise, when the span/event is
recorded, each `Filtered` subscriber's `on_event` and `new_span` method
checks with the `Registry` to see whether _it_ enabled or disabled that
span/event, and skips passing it to the wrapped subscriber if it was
disabled. When a span is recorded, the `Registry` which filters disabled
it, so that they can skip it when looking up spans in other callbacks.

 A new `on_subscriber` method was added to the `Subscribe` trait, which
 allows running a callback when adding a `Subscribe` to a `Subscriber`.
 This allows mutating both the `Subscribe` and the `Subscriber`, since
 they are both passed by value when subscribering a `Subscribe` onto a
 `Subscriber`, and a new `register_filter` method was added to
 `LookupSpan`, which returns a `FilterId` type. When a `Filtered`
 subscriber is added to a subscriber that implements `LookupSpan`, it
 calls `register_filter` in its `on_subscriber` hook to generate a
 unique ID for its filter. `FilterId` can be used to look up whether a
 span or event was enabled by the `Filtered` subscriber.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-subscriber filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` subscriber in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 subscribers, let alone 64 separate
per-subscriber filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` subscribers. When `Filtered` subscribers are
nested using the `Subscribeed`, the `Context` _combines_ the filter ID
of the inner and outer subscribers so that spans can be skipped if they
were disabled by either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `subscriber` module. Since
  we were adding more code to the `Subscribe` module (the `Filter`
  trait), the `subscriber.rs` file got significantly longer and was
  becoming somewhat hard to navigate. Therefore, I split out the
  `Context` and `Subscribeed` types into their own files. Most of the
  code in those files is the same as it was before, although some of it
  was changed in order to support per-subscriber filtering. Apologies in
  advance to reviewers for this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-subscriber filtering, I added a new
  `Subscribe` implementation that does the same thing, but implements
  `Subscribe` rather than `Subscriber`. This allows testing
  per-subscriber filter behavior in the same way we test global filter
  behavior. Reviewers should feel free to just skim the test the test
  support changes, or skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow up
changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-subscriber filters. This is
      in order to avoid stabilizing a lot of the per-subscriber
      filtering design as public API without taking the time to ensure
      there are no serious issues. In the future, we will want to add
      new APIs to allow users to implement their own root `Subscriber`s
      that can host subscribers with per-subscriber filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-subscriber filter. We should add
      an implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably
      _also_ be used as global filters. We could consider adding
      `Subscribe` implementations to allow them to be used for global
      filtering.
* [ ] We could consider adding new `Filter` implementations for
      performing common filtering predicates. For example, "only enable
      spans/events with a particular target or a set of targets", "only
      enable spans", "only enable events", etc. Then, we could add a
      combinator API to combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-subscriber filter
      evaluations, so that we don't need to always use `enabled` when
      there are several per-subscriber filters that disagree on whether
      or not they want a given span/event. There are a lot of unused
      bits in `Interest` that could potentially be used for this
      purpose. However, this would require new API changes in
      `tracing-core`, and is therefore out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Mar 24, 2022
## Motivation

Currently, filtering with `Subscribe`s is always _global_. If a
`Subscribe` performs filtering, it will disable a span or event for
_all_ subscribers that compose the current subscriber. In some cases,
however, it is often desirable for individual subscribers to see
different spans or events than the rest of the `Subscribe` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Subscribe`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in
`Subscribe::on_event` and `Subscribe::new_span` based on some filter is
relatively simple, this wouldn't really be an ideal solution, for a
number of reasons. A proper per-subscriber filtering implementation
would satisfy the following _desiderata_:

* If a per-subscriber filter disables a span, it shouldn't be present
  _for the subscriber that filter is attached to_ when iterating over
  span contexts (such as `Context::event_scope`, `SpanRef::scope`, etc),
  or when looking up a span's parents.
* When _all_ per-subscriber filters disable a span or event, it should
  be completely disabled, rather than simply skipped by those particular
  subscribers. This means that per-subscriber filters should participate
  in `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-subscriber filters shouldn't interfere with non-filtered
  `Subscribe`s. If a subscriber contains subscribers without any
  filters, as well as subscribers with per-subscriber filters, the
  non-filtered `Subscribe`s should behave exactly as they would without
  any per-subscriber filters present.
* Similarly, per-subscriber filters shouldn't interfere with global
  filtering. If a `Subscribe` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-subscriber filters should
  also be effected by the global filter.
* Per-subscriber filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-subscriber-filtered subscribers_.
* Per-subscriber filters should be _tree-shaped_. If a `Subscriber`
  consists of multiple subscribers that have been `Subscribeed` together
  to form new `Subscribe`s, and some of the `Subscribeed` subscribers
  have per-subscriber filters, those per-subscriber filters should
  effect all subscribers in that subtree. Similarly, if `Subscribe`s in
  a per-subscriber filtered subtree have their _own_ per-subscriber
  filters, those subscribers should be effected by the union of their
  own filters and any per-subscriber filters that wrap them at higher
  levels in the tree.

Meeting all these requirements means that implementing per-subscriber
filtering correctly is somewhat more complex than simply skipping events
and spans in a `Subscribe`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-subscriber
filtering for `tracing-subscriber` v0.2. It should be possible to add
this in a point release without a breaking change --- in particular, the
new per-subscriber filtering feature _doesn't_ interfere with the global
filtering behavior that subscribers currently have when not using the
new per-subscriber filtering APIs, so existing configurations should all
behave exactly as they do now.

### Implementation

The new per-subscriber filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Subscribe`. When `enabled` is called on a `Filtered`
subscriber, it checks the metadata against the `Filter` and stores
whether that filter enabled the span/event in a thread-local cell. If
every per-subscriber filter disables a span/event, and there are no
non-per-subscriber-filtered subscribers in use, the `Registry`'s
`enabled` will return `false`. Otherwise, when the span/event is
recorded, each `Filtered` subscriber's `on_event` and `new_span` method
checks with the `Registry` to see whether _it_ enabled or disabled that
span/event, and skips passing it to the wrapped subscriber if it was
disabled. When a span is recorded, the `Registry` which filters disabled
it, so that they can skip it when looking up spans in other callbacks.

 A new `on_subscriber` method was added to the `Subscribe` trait, which
 allows running a callback when adding a `Subscribe` to a `Subscriber`.
 This allows mutating both the `Subscribe` and the `Subscriber`, since
 they are both passed by value when subscribering a `Subscribe` onto a
 `Subscriber`, and a new `register_filter` method was added to
 `LookupSpan`, which returns a `FilterId` type. When a `Filtered`
 subscriber is added to a subscriber that implements `LookupSpan`, it
 calls `register_filter` in its `on_subscriber` hook to generate a
 unique ID for its filter. `FilterId` can be used to look up whether a
 span or event was enabled by the `Filtered` subscriber.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-subscriber filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` subscriber in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 subscribers, let alone 64 separate
per-subscriber filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` subscribers. When `Filtered` subscribers are
nested using the `Subscribeed`, the `Context` _combines_ the filter ID
of the inner and outer subscribers so that spans can be skipped if they
were disabled by either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `subscriber` module. Since
  we were adding more code to the `Subscribe` module (the `Filter`
  trait), the `subscriber.rs` file got significantly longer and was
  becoming somewhat hard to navigate. Therefore, I split out the
  `Context` and `Subscribeed` types into their own files. Most of the
  code in those files is the same as it was before, although some of it
  was changed in order to support per-subscriber filtering. Apologies in
  advance to reviewers for this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-subscriber filtering, I added a new
  `Subscribe` implementation that does the same thing, but implements
  `Subscribe` rather than `Subscriber`. This allows testing
  per-subscriber filter behavior in the same way we test global filter
  behavior. Reviewers should feel free to just skim the test the test
  support changes, or skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow up
changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-subscriber filters. This is
      in order to avoid stabilizing a lot of the per-subscriber
      filtering design as public API without taking the time to ensure
      there are no serious issues. In the future, we will want to add
      new APIs to allow users to implement their own root `Subscriber`s
      that can host subscribers with per-subscriber filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-subscriber filter. We should add
      an implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably
      _also_ be used as global filters. We could consider adding
      `Subscribe` implementations to allow them to be used for global
      filtering.
* [ ] We could consider adding new `Filter` implementations for
      performing common filtering predicates. For example, "only enable
      spans/events with a particular target or a set of targets", "only
      enable spans", "only enable events", etc. Then, we could add a
      combinator API to combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-subscriber filter
      evaluations, so that we don't need to always use `enabled` when
      there are several per-subscriber filters that disagree on whether
      or not they want a given span/event. There are a lot of unused
      bits in `Interest` that could potentially be used for this
      purpose. However, this would require new API changes in
      `tracing-core`, and is therefore out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Mar 24, 2022
## Motivation

Currently, filtering with `Subscribe`s is always _global_. If a
`Subscribe` performs filtering, it will disable a span or event for
_all_ subscribers that compose the current subscriber. In some cases,
however, it is often desirable for individual subscribers to see
different spans or events than the rest of the `Subscribe` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Subscribe`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in
`Subscribe::on_event` and `Subscribe::new_span` based on some filter is
relatively simple, this wouldn't really be an ideal solution, for a
number of reasons. A proper per-subscriber filtering implementation
would satisfy the following _desiderata_:

* If a per-subscriber filter disables a span, it shouldn't be present
  _for the subscriber that filter is attached to_ when iterating over
  span contexts (such as `Context::event_scope`, `SpanRef::scope`, etc),
  or when looking up a span's parents.
* When _all_ per-subscriber filters disable a span or event, it should
  be completely disabled, rather than simply skipped by those particular
  subscribers. This means that per-subscriber filters should participate
  in `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-subscriber filters shouldn't interfere with non-filtered
  `Subscribe`s. If a subscriber contains subscribers without any
  filters, as well as subscribers with per-subscriber filters, the
  non-filtered `Subscribe`s should behave exactly as they would without
  any per-subscriber filters present.
* Similarly, per-subscriber filters shouldn't interfere with global
  filtering. If a `Subscribe` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-subscriber filters should
  also be effected by the global filter.
* Per-subscriber filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-subscriber-filtered subscribers_.
* Per-subscriber filters should be _tree-shaped_. If a `Subscriber`
  consists of multiple subscribers that have been `Subscribeed` together
  to form new `Subscribe`s, and some of the `Subscribeed` subscribers
  have per-subscriber filters, those per-subscriber filters should
  effect all subscribers in that subtree. Similarly, if `Subscribe`s in
  a per-subscriber filtered subtree have their _own_ per-subscriber
  filters, those subscribers should be effected by the union of their
  own filters and any per-subscriber filters that wrap them at higher
  levels in the tree.

Meeting all these requirements means that implementing per-subscriber
filtering correctly is somewhat more complex than simply skipping events
and spans in a `Subscribe`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-subscriber
filtering for `tracing-subscriber` v0.2. It should be possible to add
this in a point release without a breaking change --- in particular, the
new per-subscriber filtering feature _doesn't_ interfere with the global
filtering behavior that subscribers currently have when not using the
new per-subscriber filtering APIs, so existing configurations should all
behave exactly as they do now.

### Implementation

The new per-subscriber filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Subscribe`. When `enabled` is called on a `Filtered`
subscriber, it checks the metadata against the `Filter` and stores
whether that filter enabled the span/event in a thread-local cell. If
every per-subscriber filter disables a span/event, and there are no
non-per-subscriber-filtered subscribers in use, the `Registry`'s
`enabled` will return `false`. Otherwise, when the span/event is
recorded, each `Filtered` subscriber's `on_event` and `new_span` method
checks with the `Registry` to see whether _it_ enabled or disabled that
span/event, and skips passing it to the wrapped subscriber if it was
disabled. When a span is recorded, the `Registry` which filters disabled
it, so that they can skip it when looking up spans in other callbacks.

 A new `on_subscriber` method was added to the `Subscribe` trait, which
 allows running a callback when adding a `Subscribe` to a `Subscriber`.
 This allows mutating both the `Subscribe` and the `Subscriber`, since
 they are both passed by value when subscribering a `Subscribe` onto a
 `Subscriber`, and a new `register_filter` method was added to
 `LookupSpan`, which returns a `FilterId` type. When a `Filtered`
 subscriber is added to a subscriber that implements `LookupSpan`, it
 calls `register_filter` in its `on_subscriber` hook to generate a
 unique ID for its filter. `FilterId` can be used to look up whether a
 span or event was enabled by the `Filtered` subscriber.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-subscriber filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` subscriber in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 subscribers, let alone 64 separate
per-subscriber filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` subscribers. When `Filtered` subscribers are
nested using the `Subscribeed`, the `Context` _combines_ the filter ID
of the inner and outer subscribers so that spans can be skipped if they
were disabled by either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `subscriber` module. Since
  we were adding more code to the `Subscribe` module (the `Filter`
  trait), the `subscriber.rs` file got significantly longer and was
  becoming somewhat hard to navigate. Therefore, I split out the
  `Context` and `Subscribeed` types into their own files. Most of the
  code in those files is the same as it was before, although some of it
  was changed in order to support per-subscriber filtering. Apologies in
  advance to reviewers for this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-subscriber filtering, I added a new
  `Subscribe` implementation that does the same thing, but implements
  `Subscribe` rather than `Subscriber`. This allows testing
  per-subscriber filter behavior in the same way we test global filter
  behavior. Reviewers should feel free to just skim the test the test
  support changes, or skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow up
changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-subscriber filters. This is
      in order to avoid stabilizing a lot of the per-subscriber
      filtering design as public API without taking the time to ensure
      there are no serious issues. In the future, we will want to add
      new APIs to allow users to implement their own root `Subscriber`s
      that can host subscribers with per-subscriber filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-subscriber filter. We should add
      an implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably
      _also_ be used as global filters. We could consider adding
      `Subscribe` implementations to allow them to be used for global
      filtering.
* [ ] We could consider adding new `Filter` implementations for
      performing common filtering predicates. For example, "only enable
      spans/events with a particular target or a set of targets", "only
      enable spans", "only enable events", etc. Then, we could add a
      combinator API to combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-subscriber filter
      evaluations, so that we don't need to always use `enabled` when
      there are several per-subscriber filters that disagree on whether
      or not they want a given span/event. There are a lot of unused
      bits in `Interest` that could potentially be used for this
      purpose. However, this would require new API changes in
      `tracing-core`, and is therefore out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Mar 24, 2022
## Motivation

Currently, filtering with `Subscribe`s is always _global_. If a
`Subscribe` performs filtering, it will disable a span or event for
_all_ subscribers that compose the current subscriber. In some cases,
however, it is often desirable for individual subscribers to see
different spans or events than the rest of the `Subscribe` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Subscribe`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in
`Subscribe::on_event` and `Subscribe::new_span` based on some filter is
relatively simple, this wouldn't really be an ideal solution, for a
number of reasons. A proper per-subscriber filtering implementation
would satisfy the following _desiderata_:

* If a per-subscriber filter disables a span, it shouldn't be present
  _for the subscriber that filter is attached to_ when iterating over
  span contexts (such as `Context::event_scope`, `SpanRef::scope`, etc),
  or when looking up a span's parents.
* When _all_ per-subscriber filters disable a span or event, it should
  be completely disabled, rather than simply skipped by those particular
  subscribers. This means that per-subscriber filters should participate
  in `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-subscriber filters shouldn't interfere with non-filtered
  `Subscribe`s. If a subscriber contains subscribers without any
  filters, as well as subscribers with per-subscriber filters, the
  non-filtered `Subscribe`s should behave exactly as they would without
  any per-subscriber filters present.
* Similarly, per-subscriber filters shouldn't interfere with global
  filtering. If a `Subscribe` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-subscriber filters should
  also be effected by the global filter.
* Per-subscriber filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-subscriber-filtered subscribers_.
* Per-subscriber filters should be _tree-shaped_. If a `Subscriber`
  consists of multiple subscribers that have been `Subscribeed` together
  to form new `Subscribe`s, and some of the `Subscribeed` subscribers
  have per-subscriber filters, those per-subscriber filters should
  effect all subscribers in that subtree. Similarly, if `Subscribe`s in
  a per-subscriber filtered subtree have their _own_ per-subscriber
  filters, those subscribers should be effected by the union of their
  own filters and any per-subscriber filters that wrap them at higher
  levels in the tree.

Meeting all these requirements means that implementing per-subscriber
filtering correctly is somewhat more complex than simply skipping events
and spans in a `Subscribe`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-subscriber
filtering for `tracing-subscriber` v0.2. It should be possible to add
this in a point release without a breaking change --- in particular, the
new per-subscriber filtering feature _doesn't_ interfere with the global
filtering behavior that subscribers currently have when not using the
new per-subscriber filtering APIs, so existing configurations should all
behave exactly as they do now.

### Implementation

The new per-subscriber filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Subscribe`. When `enabled` is called on a `Filtered`
subscriber, it checks the metadata against the `Filter` and stores
whether that filter enabled the span/event in a thread-local cell. If
every per-subscriber filter disables a span/event, and there are no
non-per-subscriber-filtered subscribers in use, the `Registry`'s
`enabled` will return `false`. Otherwise, when the span/event is
recorded, each `Filtered` subscriber's `on_event` and `new_span` method
checks with the `Registry` to see whether _it_ enabled or disabled that
span/event, and skips passing it to the wrapped subscriber if it was
disabled. When a span is recorded, the `Registry` which filters disabled
it, so that they can skip it when looking up spans in other callbacks.

 A new `on_subscriber` method was added to the `Subscribe` trait, which
 allows running a callback when adding a `Subscribe` to a `Subscriber`.
 This allows mutating both the `Subscribe` and the `Subscriber`, since
 they are both passed by value when subscribering a `Subscribe` onto a
 `Subscriber`, and a new `register_filter` method was added to
 `LookupSpan`, which returns a `FilterId` type. When a `Filtered`
 subscriber is added to a subscriber that implements `LookupSpan`, it
 calls `register_filter` in its `on_subscriber` hook to generate a
 unique ID for its filter. `FilterId` can be used to look up whether a
 span or event was enabled by the `Filtered` subscriber.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-subscriber filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` subscriber in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 subscribers, let alone 64 separate
per-subscriber filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` subscribers. When `Filtered` subscribers are
nested using the `Subscribeed`, the `Context` _combines_ the filter ID
of the inner and outer subscribers so that spans can be skipped if they
were disabled by either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `subscriber` module. Since
  we were adding more code to the `Subscribe` module (the `Filter`
  trait), the `subscriber.rs` file got significantly longer and was
  becoming somewhat hard to navigate. Therefore, I split out the
  `Context` and `Subscribeed` types into their own files. Most of the
  code in those files is the same as it was before, although some of it
  was changed in order to support per-subscriber filtering. Apologies in
  advance to reviewers for this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-subscriber filtering, I added a new
  `Subscribe` implementation that does the same thing, but implements
  `Subscribe` rather than `Subscriber`. This allows testing
  per-subscriber filter behavior in the same way we test global filter
  behavior. Reviewers should feel free to just skim the test the test
  support changes, or skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow up
changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-subscriber filters. This is
      in order to avoid stabilizing a lot of the per-subscriber
      filtering design as public API without taking the time to ensure
      there are no serious issues. In the future, we will want to add
      new APIs to allow users to implement their own root `Subscriber`s
      that can host subscribers with per-subscriber filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-subscriber filter. We should add
      an implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably
      _also_ be used as global filters. We could consider adding
      `Subscribe` implementations to allow them to be used for global
      filtering.
* [ ] We could consider adding new `Filter` implementations for
      performing common filtering predicates. For example, "only enable
      spans/events with a particular target or a set of targets", "only
      enable spans", "only enable events", etc. Then, we could add a
      combinator API to combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-subscriber filter
      evaluations, so that we don't need to always use `enabled` when
      there are several per-subscriber filters that disagree on whether
      or not they want a given span/event. There are a lot of unused
      bits in `Interest` that could potentially be used for this
      purpose. However, this would require new API changes in
      `tracing-core`, and is therefore out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Mar 24, 2022
## Motivation

Currently, filtering with `Subscribe`s is always _global_. If a
`Subscribe` performs filtering, it will disable a span or event for
_all_ subscribers that compose the current subscriber. In some cases,
however, it is often desirable for individual subscribers to see
different spans or events than the rest of the `Subscribe` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Subscribe`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in
`Subscribe::on_event` and `Subscribe::new_span` based on some filter is
relatively simple, this wouldn't really be an ideal solution, for a
number of reasons. A proper per-subscriber filtering implementation
would satisfy the following _desiderata_:

* If a per-subscriber filter disables a span, it shouldn't be present
  _for the subscriber that filter is attached to_ when iterating over
  span contexts (such as `Context::event_scope`, `SpanRef::scope`, etc),
  or when looking up a span's parents.
* When _all_ per-subscriber filters disable a span or event, it should
  be completely disabled, rather than simply skipped by those particular
  subscribers. This means that per-subscriber filters should participate
  in `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-subscriber filters shouldn't interfere with non-filtered
  `Subscribe`s. If a subscriber contains subscribers without any
  filters, as well as subscribers with per-subscriber filters, the
  non-filtered `Subscribe`s should behave exactly as they would without
  any per-subscriber filters present.
* Similarly, per-subscriber filters shouldn't interfere with global
  filtering. If a `Subscribe` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-subscriber filters should
  also be effected by the global filter.
* Per-subscriber filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-subscriber-filtered subscribers_.
* Per-subscriber filters should be _tree-shaped_. If a `Subscriber`
  consists of multiple subscribers that have been `Subscribeed` together
  to form new `Subscribe`s, and some of the `Subscribeed` subscribers
  have per-subscriber filters, those per-subscriber filters should
  effect all subscribers in that subtree. Similarly, if `Subscribe`s in
  a per-subscriber filtered subtree have their _own_ per-subscriber
  filters, those subscribers should be effected by the union of their
  own filters and any per-subscriber filters that wrap them at higher
  levels in the tree.

Meeting all these requirements means that implementing per-subscriber
filtering correctly is somewhat more complex than simply skipping events
and spans in a `Subscribe`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-subscriber
filtering for `tracing-subscriber` v0.2. It should be possible to add
this in a point release without a breaking change --- in particular, the
new per-subscriber filtering feature _doesn't_ interfere with the global
filtering behavior that subscribers currently have when not using the
new per-subscriber filtering APIs, so existing configurations should all
behave exactly as they do now.

### Implementation

The new per-subscriber filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Subscribe`. When `enabled` is called on a `Filtered`
subscriber, it checks the metadata against the `Filter` and stores
whether that filter enabled the span/event in a thread-local cell. If
every per-subscriber filter disables a span/event, and there are no
non-per-subscriber-filtered subscribers in use, the `Registry`'s
`enabled` will return `false`. Otherwise, when the span/event is
recorded, each `Filtered` subscriber's `on_event` and `new_span` method
checks with the `Registry` to see whether _it_ enabled or disabled that
span/event, and skips passing it to the wrapped subscriber if it was
disabled. When a span is recorded, the `Registry` which filters disabled
it, so that they can skip it when looking up spans in other callbacks.

 A new `on_subscriber` method was added to the `Subscribe` trait, which
 allows running a callback when adding a `Subscribe` to a `Subscriber`.
 This allows mutating both the `Subscribe` and the `Subscriber`, since
 they are both passed by value when subscribering a `Subscribe` onto a
 `Subscriber`, and a new `register_filter` method was added to
 `LookupSpan`, which returns a `FilterId` type. When a `Filtered`
 subscriber is added to a subscriber that implements `LookupSpan`, it
 calls `register_filter` in its `on_subscriber` hook to generate a
 unique ID for its filter. `FilterId` can be used to look up whether a
 span or event was enabled by the `Filtered` subscriber.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-subscriber filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` subscriber in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 subscribers, let alone 64 separate
per-subscriber filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` subscribers. When `Filtered` subscribers are
nested using the `Subscribeed`, the `Context` _combines_ the filter ID
of the inner and outer subscribers so that spans can be skipped if they
were disabled by either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `subscriber` module. Since
  we were adding more code to the `Subscribe` module (the `Filter`
  trait), the `subscriber.rs` file got significantly longer and was
  becoming somewhat hard to navigate. Therefore, I split out the
  `Context` and `Subscribeed` types into their own files. Most of the
  code in those files is the same as it was before, although some of it
  was changed in order to support per-subscriber filtering. Apologies in
  advance to reviewers for this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-subscriber filtering, I added a new
  `Subscribe` implementation that does the same thing, but implements
  `Subscribe` rather than `Subscriber`. This allows testing
  per-subscriber filter behavior in the same way we test global filter
  behavior. Reviewers should feel free to just skim the test the test
  support changes, or skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow up
changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-subscriber filters. This is
      in order to avoid stabilizing a lot of the per-subscriber
      filtering design as public API without taking the time to ensure
      there are no serious issues. In the future, we will want to add
      new APIs to allow users to implement their own root `Subscriber`s
      that can host subscribers with per-subscriber filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-subscriber filter. We should add
      an implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably
      _also_ be used as global filters. We could consider adding
      `Subscribe` implementations to allow them to be used for global
      filtering.
* [ ] We could consider adding new `Filter` implementations for
      performing common filtering predicates. For example, "only enable
      spans/events with a particular target or a set of targets", "only
      enable spans", "only enable events", etc. Then, we could add a
      combinator API to combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-subscriber filter
      evaluations, so that we don't need to always use `enabled` when
      there are several per-subscriber filters that disagree on whether
      or not they want a given span/event. There are a lot of unused
      bits in `Interest` that could potentially be used for this
      purpose. However, this would require new API changes in
      `tracing-core`, and is therefore out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Mar 24, 2022
## Motivation

Currently, filtering with `Subscribe`s is always _global_. If a
`Subscribe` performs filtering, it will disable a span or event for
_all_ subscribers that compose the current subscriber. In some cases,
however, it is often desirable for individual subscribers to see
different spans or events than the rest of the `Subscribe` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Subscribe`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in
`Subscribe::on_event` and `Subscribe::new_span` based on some filter is
relatively simple, this wouldn't really be an ideal solution, for a
number of reasons. A proper per-subscriber filtering implementation
would satisfy the following _desiderata_:

* If a per-subscriber filter disables a span, it shouldn't be present
  _for the subscriber that filter is attached to_ when iterating over
  span contexts (such as `Context::event_scope`, `SpanRef::scope`, etc),
  or when looking up a span's parents.
* When _all_ per-subscriber filters disable a span or event, it should
  be completely disabled, rather than simply skipped by those particular
  subscribers. This means that per-subscriber filters should participate
  in `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-subscriber filters shouldn't interfere with non-filtered
  `Subscribe`s. If a subscriber contains subscribers without any
  filters, as well as subscribers with per-subscriber filters, the
  non-filtered `Subscribe`s should behave exactly as they would without
  any per-subscriber filters present.
* Similarly, per-subscriber filters shouldn't interfere with global
  filtering. If a `Subscribe` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-subscriber filters should
  also be effected by the global filter.
* Per-subscriber filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-subscriber-filtered subscribers_.
* Per-subscriber filters should be _tree-shaped_. If a `Subscriber`
  consists of multiple subscribers that have been `Subscribeed` together
  to form new `Subscribe`s, and some of the `Subscribeed` subscribers
  have per-subscriber filters, those per-subscriber filters should
  effect all subscribers in that subtree. Similarly, if `Subscribe`s in
  a per-subscriber filtered subtree have their _own_ per-subscriber
  filters, those subscribers should be effected by the union of their
  own filters and any per-subscriber filters that wrap them at higher
  levels in the tree.

Meeting all these requirements means that implementing per-subscriber
filtering correctly is somewhat more complex than simply skipping events
and spans in a `Subscribe`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-subscriber
filtering for `tracing-subscriber` v0.2. It should be possible to add
this in a point release without a breaking change --- in particular, the
new per-subscriber filtering feature _doesn't_ interfere with the global
filtering behavior that subscribers currently have when not using the
new per-subscriber filtering APIs, so existing configurations should all
behave exactly as they do now.

### Implementation

The new per-subscriber filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Subscribe`. When `enabled` is called on a `Filtered`
subscriber, it checks the metadata against the `Filter` and stores
whether that filter enabled the span/event in a thread-local cell. If
every per-subscriber filter disables a span/event, and there are no
non-per-subscriber-filtered subscribers in use, the `Registry`'s
`enabled` will return `false`. Otherwise, when the span/event is
recorded, each `Filtered` subscriber's `on_event` and `new_span` method
checks with the `Registry` to see whether _it_ enabled or disabled that
span/event, and skips passing it to the wrapped subscriber if it was
disabled. When a span is recorded, the `Registry` which filters disabled
it, so that they can skip it when looking up spans in other callbacks.

 A new `on_subscriber` method was added to the `Subscribe` trait, which
 allows running a callback when adding a `Subscribe` to a `Subscriber`.
 This allows mutating both the `Subscribe` and the `Subscriber`, since
 they are both passed by value when subscribering a `Subscribe` onto a
 `Subscriber`, and a new `register_filter` method was added to
 `LookupSpan`, which returns a `FilterId` type. When a `Filtered`
 subscriber is added to a subscriber that implements `LookupSpan`, it
 calls `register_filter` in its `on_subscriber` hook to generate a
 unique ID for its filter. `FilterId` can be used to look up whether a
 span or event was enabled by the `Filtered` subscriber.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-subscriber filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` subscriber in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 subscribers, let alone 64 separate
per-subscriber filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` subscribers. When `Filtered` subscribers are
nested using the `Subscribeed`, the `Context` _combines_ the filter ID
of the inner and outer subscribers so that spans can be skipped if they
were disabled by either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `subscriber` module. Since
  we were adding more code to the `Subscribe` module (the `Filter`
  trait), the `subscriber.rs` file got significantly longer and was
  becoming somewhat hard to navigate. Therefore, I split out the
  `Context` and `Subscribeed` types into their own files. Most of the
  code in those files is the same as it was before, although some of it
  was changed in order to support per-subscriber filtering. Apologies in
  advance to reviewers for this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-subscriber filtering, I added a new
  `Subscribe` implementation that does the same thing, but implements
  `Subscribe` rather than `Subscriber`. This allows testing
  per-subscriber filter behavior in the same way we test global filter
  behavior. Reviewers should feel free to just skim the test the test
  support changes, or skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow up
changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-subscriber filters. This is
      in order to avoid stabilizing a lot of the per-subscriber
      filtering design as public API without taking the time to ensure
      there are no serious issues. In the future, we will want to add
      new APIs to allow users to implement their own root `Subscriber`s
      that can host subscribers with per-subscriber filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-subscriber filter. We should add
      an implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably
      _also_ be used as global filters. We could consider adding
      `Subscribe` implementations to allow them to be used for global
      filtering.
* [ ] We could consider adding new `Filter` implementations for
      performing common filtering predicates. For example, "only enable
      spans/events with a particular target or a set of targets", "only
      enable spans", "only enable events", etc. Then, we could add a
      combinator API to combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-subscriber filter
      evaluations, so that we don't need to always use `enabled` when
      there are several per-subscriber filters that disagree on whether
      or not they want a given span/event. There are a lot of unused
      bits in `Interest` that could potentially be used for this
      purpose. However, this would require new API changes in
      `tracing-core`, and is therefore out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Mar 24, 2022
## Motivation

Currently, filtering with `Subscribe`s is always _global_. If a
`Subscribe` performs filtering, it will disable a span or event for
_all_ subscribers that compose the current subscriber. In some cases,
however, it is often desirable for individual subscribers to see
different spans or events than the rest of the `Subscribe` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Subscribe`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in
`Subscribe::on_event` and `Subscribe::new_span` based on some filter is
relatively simple, this wouldn't really be an ideal solution, for a
number of reasons. A proper per-subscriber filtering implementation
would satisfy the following _desiderata_:

* If a per-subscriber filter disables a span, it shouldn't be present
  _for the subscriber that filter is attached to_ when iterating over
  span contexts (such as `Context::event_scope`, `SpanRef::scope`, etc),
  or when looking up a span's parents.
* When _all_ per-subscriber filters disable a span or event, it should
  be completely disabled, rather than simply skipped by those particular
  subscribers. This means that per-subscriber filters should participate
  in `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-subscriber filters shouldn't interfere with non-filtered
  `Subscribe`s. If a subscriber contains subscribers without any
  filters, as well as subscribers with per-subscriber filters, the
  non-filtered `Subscribe`s should behave exactly as they would without
  any per-subscriber filters present.
* Similarly, per-subscriber filters shouldn't interfere with global
  filtering. If a `Subscribe` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-subscriber filters should
  also be effected by the global filter.
* Per-subscriber filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-subscriber-filtered subscribers_.
* Per-subscriber filters should be _tree-shaped_. If a `Subscriber`
  consists of multiple subscribers that have been `Subscribeed` together
  to form new `Subscribe`s, and some of the `Subscribeed` subscribers
  have per-subscriber filters, those per-subscriber filters should
  effect all subscribers in that subtree. Similarly, if `Subscribe`s in
  a per-subscriber filtered subtree have their _own_ per-subscriber
  filters, those subscribers should be effected by the union of their
  own filters and any per-subscriber filters that wrap them at higher
  levels in the tree.

Meeting all these requirements means that implementing per-subscriber
filtering correctly is somewhat more complex than simply skipping events
and spans in a `Subscribe`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-subscriber
filtering for `tracing-subscriber` v0.2. It should be possible to add
this in a point release without a breaking change --- in particular, the
new per-subscriber filtering feature _doesn't_ interfere with the global
filtering behavior that subscribers currently have when not using the
new per-subscriber filtering APIs, so existing configurations should all
behave exactly as they do now.

### Implementation

The new per-subscriber filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Subscribe`. When `enabled` is called on a `Filtered`
subscriber, it checks the metadata against the `Filter` and stores
whether that filter enabled the span/event in a thread-local cell. If
every per-subscriber filter disables a span/event, and there are no
non-per-subscriber-filtered subscribers in use, the `Registry`'s
`enabled` will return `false`. Otherwise, when the span/event is
recorded, each `Filtered` subscriber's `on_event` and `new_span` method
checks with the `Registry` to see whether _it_ enabled or disabled that
span/event, and skips passing it to the wrapped subscriber if it was
disabled. When a span is recorded, the `Registry` which filters disabled
it, so that they can skip it when looking up spans in other callbacks.

 A new `on_subscriber` method was added to the `Subscribe` trait, which
 allows running a callback when adding a `Subscribe` to a `Subscriber`.
 This allows mutating both the `Subscribe` and the `Subscriber`, since
 they are both passed by value when subscribering a `Subscribe` onto a
 `Subscriber`, and a new `register_filter` method was added to
 `LookupSpan`, which returns a `FilterId` type. When a `Filtered`
 subscriber is added to a subscriber that implements `LookupSpan`, it
 calls `register_filter` in its `on_subscriber` hook to generate a
 unique ID for its filter. `FilterId` can be used to look up whether a
 span or event was enabled by the `Filtered` subscriber.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-subscriber filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` subscriber in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 subscribers, let alone 64 separate
per-subscriber filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` subscribers. When `Filtered` subscribers are
nested using the `Subscribeed`, the `Context` _combines_ the filter ID
of the inner and outer subscribers so that spans can be skipped if they
were disabled by either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `subscriber` module. Since
  we were adding more code to the `Subscribe` module (the `Filter`
  trait), the `subscriber.rs` file got significantly longer and was
  becoming somewhat hard to navigate. Therefore, I split out the
  `Context` and `Subscribeed` types into their own files. Most of the
  code in those files is the same as it was before, although some of it
  was changed in order to support per-subscriber filtering. Apologies in
  advance to reviewers for this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-subscriber filtering, I added a new
  `Subscribe` implementation that does the same thing, but implements
  `Subscribe` rather than `Subscriber`. This allows testing
  per-subscriber filter behavior in the same way we test global filter
  behavior. Reviewers should feel free to just skim the test the test
  support changes, or skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow up
changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-subscriber filters. This is
      in order to avoid stabilizing a lot of the per-subscriber
      filtering design as public API without taking the time to ensure
      there are no serious issues. In the future, we will want to add
      new APIs to allow users to implement their own root `Subscriber`s
      that can host subscribers with per-subscriber filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-subscriber filter. We should add
      an implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably
      _also_ be used as global filters. We could consider adding
      `Subscribe` implementations to allow them to be used for global
      filtering.
* [ ] We could consider adding new `Filter` implementations for
      performing common filtering predicates. For example, "only enable
      spans/events with a particular target or a set of targets", "only
      enable spans", "only enable events", etc. Then, we could add a
      combinator API to combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-subscriber filter
      evaluations, so that we don't need to always use `enabled` when
      there are several per-subscriber filters that disagree on whether
      or not they want a given span/event. There are a lot of unused
      bits in `Interest` that could potentially be used for this
      purpose. However, this would require new API changes in
      `tracing-core`, and is therefore out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Mar 24, 2022
## Motivation

Currently, filtering with `Subscribe`s is always _global_. If a
`Subscribe` performs filtering, it will disable a span or event for
_all_ subscribers that compose the current subscriber. In some cases,
however, it is often desirable for individual subscribers to see
different spans or events than the rest of the `Subscribe` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
#302, #597, and #1348, all indicate that there is significant demand for
the ability to add filters to individual `Subscribe`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in
`Subscribe::on_event` and `Subscribe::new_span` based on some filter is
relatively simple, this wouldn't really be an ideal solution, for a
number of reasons. A proper per-subscriber filtering implementation
would satisfy the following _desiderata_:

* If a per-subscriber filter disables a span, it shouldn't be present
  _for the subscriber that filter is attached to_ when iterating over
  span contexts (such as `Context::event_scope`, `SpanRef::scope`, etc),
  or when looking up a span's parents.
* When _all_ per-subscriber filters disable a span or event, it should
  be completely disabled, rather than simply skipped by those particular
  subscribers. This means that per-subscriber filters should participate
  in `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-subscriber filters shouldn't interfere with non-filtered
  `Subscribe`s. If a subscriber contains subscribers without any
  filters, as well as subscribers with per-subscriber filters, the
  non-filtered `Subscribe`s should behave exactly as they would without
  any per-subscriber filters present.
* Similarly, per-subscriber filters shouldn't interfere with global
  filtering. If a `Subscribe` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-subscriber filters should
  also be effected by the global filter.
* Per-subscriber filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-subscriber-filtered subscribers_.
* Per-subscriber filters should be _tree-shaped_. If a `Subscriber`
  consists of multiple subscribers that have been `Subscribeed` together
  to form new `Subscribe`s, and some of the `Subscribeed` subscribers
  have per-subscriber filters, those per-subscriber filters should
  effect all subscribers in that subtree. Similarly, if `Subscribe`s in
  a per-subscriber filtered subtree have their _own_ per-subscriber
  filters, those subscribers should be effected by the union of their
  own filters and any per-subscriber filters that wrap them at higher
  levels in the tree.

Meeting all these requirements means that implementing per-subscriber
filtering correctly is somewhat more complex than simply skipping events
and spans in a `Subscribe`'s `on_event` and `new_span` callbacks.

## Solution

This branch has a basic working implementation of per-subscriber
filtering for `tracing-subscriber` v0.2. It should be possible to add
this in a point release without a breaking change --- in particular, the
new per-subscriber filtering feature _doesn't_ interfere with the global
filtering behavior that subscribers currently have when not using the
new per-subscriber filtering APIs, so existing configurations should all
behave exactly as they do now.

### Implementation

The new per-subscriber filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Subscribe`. When `enabled` is called on a `Filtered`
subscriber, it checks the metadata against the `Filter` and stores
whether that filter enabled the span/event in a thread-local cell. If
every per-subscriber filter disables a span/event, and there are no
non-per-subscriber-filtered subscribers in use, the `Registry`'s
`enabled` will return `false`. Otherwise, when the span/event is
recorded, each `Filtered` subscriber's `on_event` and `new_span` method
checks with the `Registry` to see whether _it_ enabled or disabled that
span/event, and skips passing it to the wrapped subscriber if it was
disabled. When a span is recorded, the `Registry` which filters disabled
it, so that they can skip it when looking up spans in other callbacks.

 A new `on_subscriber` method was added to the `Subscribe` trait, which
 allows running a callback when adding a `Subscribe` to a `Subscriber`.
 This allows mutating both the `Subscribe` and the `Subscriber`, since
 they are both passed by value when subscribering a `Subscribe` onto a
 `Subscriber`, and a new `register_filter` method was added to
 `LookupSpan`, which returns a `FilterId` type. When a `Filtered`
 subscriber is added to a subscriber that implements `LookupSpan`, it
 calls `register_filter` in its `on_subscriber` hook to generate a
 unique ID for its filter. `FilterId` can be used to look up whether a
 span or event was enabled by the `Filtered` subscriber.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-subscriber filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` subscriber in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 subscribers, let alone 64 separate
per-subscriber filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` subscribers. When `Filtered` subscribers are
nested using the `Subscribeed`, the `Context` _combines_ the filter ID
of the inner and outer subscribers so that spans can be skipped if they
were disabled by either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

### Notes

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `subscriber` module. Since
  we were adding more code to the `Subscribe` module (the `Filter`
  trait), the `subscriber.rs` file got significantly longer and was
  becoming somewhat hard to navigate. Therefore, I split out the
  `Context` and `Subscribeed` types into their own files. Most of the
  code in those files is the same as it was before, although some of it
  was changed in order to support per-subscriber filtering. Apologies in
  advance to reviewers for this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-subscriber filtering, I added a new
  `Subscribe` implementation that does the same thing, but implements
  `Subscribe` rather than `Subscriber`. This allows testing
  per-subscriber filter behavior in the same way we test global filter
  behavior. Reviewers should feel free to just skim the test the test
  support changes, or skip reviewing them entirely.

## Future Work

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow up
changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-subscriber filters. This is
      in order to avoid stabilizing a lot of the per-subscriber
      filtering design as public API without taking the time to ensure
      there are no serious issues. In the future, we will want to add
      new APIs to allow users to implement their own root `Subscriber`s
      that can host subscribers with per-subscriber filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-subscriber filter. We should add
      an implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably
      _also_ be used as global filters. We could consider adding
      `Subscribe` implementations to allow them to be used for global
      filtering.
* [ ] We could consider adding new `Filter` implementations for
      performing common filtering predicates. For example, "only enable
      spans/events with a particular target or a set of targets", "only
      enable spans", "only enable events", etc. Then, we could add a
      combinator API to combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-subscriber filter
      evaluations, so that we don't need to always use `enabled` when
      there are several per-subscriber filters that disagree on whether
      or not they want a given span/event. There are a lot of unused
      bits in `Interest` that could potentially be used for this
      purpose. However, this would require new API changes in
      `tracing-core`, and is therefore out of scope for this PR.

Closes #302
Closes #597
Closes #1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
kaffarell pushed a commit to kaffarell/tracing that referenced this issue May 22, 2024
Currently, filtering with `Layer`s is always _global_. If a `Layer`
performs filtering, it will disable a span or event for _all_ layers
that compose the current subscriber. In some cases, however, it is often
desirable for individual layers to see different spans or events than
the rest of the `Layer` stack.

Issues in other projects, such as tokio-rs/console#64 and
tokio-rs/console#76, linkerd/linkerd2-proxy#601,
influxdata/influxdb_iox#1012 and influxdata/influxdb_iox#1681,
jackwaudby/spaghetti#86, etc; as well as `tracing` feature requests like
the ability to add filters to individual `Layer`s.

Unfortunately, doing this nicely is somewhat complicated. Although a
naive implementation that simply skips events/spans in `Layer::on_event`
and `Layer::new_span` based on some filter is relatively simple, this
wouldn't really be an ideal solution, for a number of reasons. A proper
per-layer filtering implementation would satisfy the following
_desiderata_:

* If a per-layer filter disables a span, it shouldn't be present _for
  the layer that filter is attached to_ when iterating over span
  contexts (such as `Context::event_scope`, `SpanRef::scope`, etc), or
  when looking up a span's parents.
* When _all_ per-layer filters disable a span or event, it should be
  completely disabled, rather than simply skipped by those particular
  layers. This means that per-layer filters should participate in
  `enabled`, as well as being able to skip spans and events in
  `new_span` and `on_event`.
* Per-layer filters shouldn't interfere with non-filtered `Layer`s.
  If a subscriber contains layers without any filters, as well as
  layers with per-layer filters, the non-filtered `Layer`s should
  behave exactly as they would without any per-layer filters present.
* Similarly, per-layer filters shouldn't interfere with global
  filtering. If a `Layer` in a stack is performing global filtering
  (e.g. the current filtering behavior), per-layer filters should also
  be effected by the global filter.
* Per-layer filters _should_ be able to participate in `Interest`
  caching, _but only when doing so doesn't interfere with
  non-per-layer-filtered layers_.
* Per-layer filters should be _tree-shaped_. If a `Subscriber` consists
  of multiple layers that have been `Layered` together to form new
  `Layer`s, and some of the `Layered` layers have per-layer filters,
  those per-layer filters should effect all layers in that subtree.
  Similarly, if `Layer`s in a per-layer filtered subtree have their
  _own_ per-layer filters, those layers should be effected by the union
  of their own filters and any per-layer filters that wrap them at
  higher levels in the tree.

Meeting all these requirements means that implementing per-layer
filtering correctly is somewhat more complex than simply skipping
events and spans in a `Layer`'s `on_event` and `new_span` callbacks.

This branch has a basic working implementation of per-layer filtering
for `tracing-subscriber` v0.2. It should be possible to add this in a
point release without a breaking change --- in particular, the new
per-layer filtering feature _doesn't_ interfere with the global
filtering behavior that layers currently have when not using the new
per-layer filtering APIs, so existing configurations should all behave
exactly as they do now.

The new per-layer filtering API consists of a `Filter` trait that
defines a filtering strategy and a `Filtered` type that combines a
`Filter` with a `Layer`. When `enabled` is called on a `Filtered` layer,
it checks the metadata against the `Filter` and stores whether that
filter enabled the span/event in a thread-local cell. If every per-layer
filter disables a span/event, and there are no non-per-layer-filtered
layers in use, the `Registry`'s `enabled` will return `false`.
Otherwise, when the span/event is recorded, each `Filtered` layer's
`on_event` and `new_span` method checks with the `Registry` to see
whether _it_ enabled or disabled that span/event, and skips passing it
to the wrapped layer if it was disabled. When a span is recorded, the
`Registry` which filters disabled it, so that they can skip it when
looking up spans in other callbacks.

 A new `on_layer` method was added to the `Layer` trait, which allows
 running a callback when adding a `Layer` to a `Subscriber`. This allows
 mutating both the `Layer` and the `Subscriber`, since they are both
 passed by value when layering a `Layer` onto a `Subscriber`, and a new
 `register_filter` method was added to `LookupSpan`, which returns a
 `FilterId` type. When a `Filtered` layer is added to a subscriber that
 implements `LookupSpan`, it calls `register_filter` in its `on_layer`
 hook to generate a unique ID for its filter. `FilterId` can be used to
 look up whether a span or event was enabled by the `Filtered` layer.

Internally, the filter results are stored in a simple bitset to reduce
the performance overhead of adding per-layer filtering as much as
possible. The `FilterId` type is actually a bitmask that's used to set
the bit corresponding to a `Filtered` layer in that bitmap when it
disables a span or event, and check whether the bit is set when querying
if a span or event was disabled. Setting a bit in the bitset and testing
whether its set is extremely efficient --- it's just a couple of bitwise
ops. Additionally, the "filter map" that tracks which filters disabled a
span or event is just a single `u64` --- we don't need to allocate a
costly `HashSet` or similar every time we want to filter an event. This
*does* mean that we are limited to 64 unique filters per
`Registry`...however, I would be _very_ surprised if anyone _ever_
builds a subscriber with 64 layers, let alone 64 separate per-layer
filters.

Additionally, the `Context` and `SpanRef` types now also potentially
carry `FilterId`s, so that they can filter span lookups and span
traversals for `Filtered` layers. When `Filtered` layers are nested
using the `Layered`, the `Context` _combines_ the filter ID of the inner
and outer layers so that spans can be skipped if they were disabled by
either.

Finally, an implementation of the `Filter` trait was added for
`LevelFilter`, and new `FilterFn` and `DynFilterFn` structs were added
to produce a `Filter` implementation from a function pointer or closure.

Some other miscellaneous factors to consider when reviewing this change
include:

* I did a bit of unrelated refactoring in the `layer` module. Since we
  were adding more code to the `Layer` module (the `Filter` trait), the
  `layer.rs` file got significantly longer and was becoming somewhat
  hard to navigate. Therefore, I split out the `Context` and `Layered`
  types into their own files. Most of the code in those files is the
  same as it was before, although some of it was changed in order to
  support per-layer filtering. Apologies in advance to reviewers for
  this...
* In order to test this change, I added some new test-support code in
  `tracing-subscriber`. The `tracing` test support code provides a
  `Subscriber` implementation that can be configured to expect a
  particular set of spans and events, and assert that they occurred as
  expected. In order to test per-layer filtering, I added a new `Layer`
  implementation that does the same thing, but implements `Layer`
  rather than `Subscriber`. This allows testing per-layer filter
  behavior in the same way we test global filter behavior. Reviewers
  should feel free to just skim the test the test support changes, or
  skip reviewing them entirely.

There is a bunch of additional stuff we can do once this change lands.
In order to limit the size of this PR, I didn't make these changes yet,
but once this merges, we will want to consider some important follow
up changes:

* [ ] Currently, _only_ `tracing-subscriber`'s `Registry` is capable of
      serving as a root subscriber for per-layer filters. This is in
      order to avoid stabilizing a lot of the per-layer filtering
      design as public API without taking the time to ensure there are
      no serious issues. In the future, we will want to add new APIs to
      allow users to implement their own root `Subscriber`s that can
      host layers with per-layer filtering.
* [ ] The `EnvFilter` type doesn't implement the `Filter` trait and thus
      cannot currently be used as a per-layer filter. We should add an
      implementation of `Filter` for `EnvFilter`.
* [ ] The new `FilterFn` and `DynFilterFn` types could conceivably _also_
      be used as global filters. We could consider adding `Layer`
      implementations to allow them to be used for global filtering.
* [ ] We could consider adding new `Filter` implementations for performing
      common filtering predicates. For example, "only enable spans/events
      with a particular target or a set of targets", "only enable spans",
      "only enable events", etc. Then, we could add a combinator API to
      combine these predicates to build new filters.
* [ ] It would be nice if the `Interest` system could be used to cache
      the result of multiple individual per-layer filter evaluations, so
      that we don't need to always use `enabled` when there are several
      per-layer filters that disagree on whether or not they want a
      given span/event. There are a lot of unused bits in `Interest`
      that could potentially be used for this purpose. However, this
      would require new API changes in `tracing-core`, and is therefore
      out of scope for this PR.

Closes tokio-rs#302
Closes tokio-rs#597
Closes tokio-rs#1348

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

No branches or pull requests

8 participants