diff --git a/tracing-subscriber/src/filter/layer_filters/mod.rs b/tracing-subscriber/src/filter/layer_filters/mod.rs index e50ee6f007..a8b3ef7a1d 100644 --- a/tracing-subscriber/src/filter/layer_filters/mod.rs +++ b/tracing-subscriber/src/filter/layer_filters/mod.rs @@ -351,10 +351,10 @@ pub trait FilterExt: layer::Filter { // globally applied to events where it doesn't today, since we can't know // what `event_enabled` will say until we have the event to call it with. /// - /// [`Filter`]: crate::subscribe::Filter - /// [`enabled`]: crate::subscribe::Filter::enabled - /// [`event_enabled`]: crate::subscribe::Filter::event_enabled - /// [`callsite_enabled`]: crate::subscribe::Filter::callsite_enabled + /// [`Filter`]: crate::layer::Filter + /// [`enabled`]: crate::layer::Filter::enabled + /// [`event_enabled`]: crate::layer::Filter::event_enabled + /// [`callsite_enabled`]: crate::layer::Filter::callsite_enabled fn not(self) -> combinator::Not where Self: Sized, @@ -478,6 +478,36 @@ macro_rules! filter_impl_body { fn max_level_hint(&self) -> Option { self.deref().max_level_hint() } + + #[inline] + fn event_enabled(&self, event: &Event<'_>, cx: &Context<'_, S>) -> bool { + self.deref().event_enabled(event, cx) + } + + #[inline] + fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) { + self.deref().on_new_span(attrs, id, ctx) + } + + #[inline] + fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) { + self.deref().on_record(id, values, ctx) + } + + #[inline] + fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) { + self.deref().on_enter(id, ctx) + } + + #[inline] + fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) { + self.deref().on_exit(id, ctx) + } + + #[inline] + fn on_close(&self, id: span::Id, ctx: Context<'_, S>) { + self.deref().on_close(id, ctx) + } }; } @@ -493,6 +523,75 @@ impl layer::Filter for Box + Send + Sync + 'static> { filter_impl_body!(); } +// Implement Filter for Option where None => allow +#[cfg(feature = "registry")] +#[cfg_attr(docsrs, doc(cfg(feature = "registry")))] +impl layer::Filter for Option +where + F: layer::Filter, +{ + #[inline] + fn enabled(&self, meta: &Metadata<'_>, ctx: &Context<'_, S>) -> bool { + self.as_ref() + .map(|inner| inner.enabled(meta, ctx)) + .unwrap_or(true) + } + + #[inline] + fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest { + self.as_ref() + .map(|inner| inner.callsite_enabled(meta)) + .unwrap_or_else(Interest::always) + } + + #[inline] + fn max_level_hint(&self) -> Option { + self.as_ref().and_then(|inner| inner.max_level_hint()) + } + + #[inline] + fn event_enabled(&self, event: &Event<'_>, ctx: &Context<'_, S>) -> bool { + self.as_ref() + .map(|inner| inner.event_enabled(event, ctx)) + .unwrap_or(true) + } + + #[inline] + fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) { + if let Some(inner) = self { + inner.on_new_span(attrs, id, ctx) + } + } + + #[inline] + fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) { + if let Some(inner) = self { + inner.on_record(id, values, ctx) + } + } + + #[inline] + fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) { + if let Some(inner) = self { + inner.on_enter(id, ctx) + } + } + + #[inline] + fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) { + if let Some(inner) = self { + inner.on_exit(id, ctx) + } + } + + #[inline] + fn on_close(&self, id: span::Id, ctx: Context<'_, S>) { + if let Some(inner) = self { + inner.on_close(id, ctx) + } + } +} + // === impl Filtered === impl Filtered { diff --git a/tracing-subscriber/src/layer/mod.rs b/tracing-subscriber/src/layer/mod.rs index bdc154301a..9dcfcc927c 100644 --- a/tracing-subscriber/src/layer/mod.rs +++ b/tracing-subscriber/src/layer/mod.rs @@ -468,6 +468,24 @@ //! function pointer. In addition, when more control is required, the [`Filter`] //! trait may also be implemented for user-defined types. //! +//! //! [`Option`] also implements [`Filter`], which allows for an optional +//! filter. [`None`](Option::None) filters out _nothing_ (that is, allows +//! everything through). For example: +//! +//! ```rust +//! # use tracing_subscriber::{filter::filter_fn, Layer}; +//! # use tracing_core::{Metadata, subscriber::Subscriber}; +//! # struct MyLayer(std::marker::PhantomData); +//! # impl MyLayer { fn new() -> Self { Self(std::marker::PhantomData)} } +//! # impl Layer for MyLayer {} +//! # fn my_filter(_: &str) -> impl Fn(&Metadata) -> bool { |_| true } +//! fn setup_tracing(filter_config: Option<&str>) { +//! let layer = MyLayer::::new() +//! .with_filter(filter_config.map(|config| filter_fn(my_filter(config)))); +//! //... +//! } +//! ``` +//! //!
 //!     Warning: Currently, the 
 //!     Registry type defined in this crate is the only root
diff --git a/tracing-subscriber/tests/layer_filters/main.rs b/tracing-subscriber/tests/layer_filters/main.rs
index 170074e8b3..80b929cbd6 100644
--- a/tracing-subscriber/tests/layer_filters/main.rs
+++ b/tracing-subscriber/tests/layer_filters/main.rs
@@ -2,6 +2,7 @@
 mod boxed;
 mod downcast_raw;
 mod filter_scopes;
+mod option;
 mod per_event;
 mod targets;
 mod trees;
@@ -12,7 +13,7 @@ use tracing_mock::{event, expect, layer, subscriber};
 use tracing_subscriber::{filter, prelude::*, Layer};
 
 #[test]
-fn basic_subscriber_filters() {
+fn basic_layer_filters() {
     let (trace_layer, trace_handle) = layer::named("trace")
         .event(expect::event().at_level(Level::TRACE))
         .event(expect::event().at_level(Level::DEBUG))
@@ -47,7 +48,7 @@ fn basic_subscriber_filters() {
 }
 
 #[test]
-fn basic_subscriber_filters_spans() {
+fn basic_layer_filter_spans() {
     let (trace_layer, trace_handle) = layer::named("trace")
         .new_span(expect::span().at_level(Level::TRACE))
         .new_span(expect::span().at_level(Level::DEBUG))
diff --git a/tracing-subscriber/tests/layer_filters/option.rs b/tracing-subscriber/tests/layer_filters/option.rs
new file mode 100644
index 0000000000..6eb4c40bb1
--- /dev/null
+++ b/tracing-subscriber/tests/layer_filters/option.rs
@@ -0,0 +1,143 @@
+use super::*;
+use tracing::Subscriber;
+use tracing_subscriber::{
+    filter::{self, LevelFilter},
+    prelude::*,
+    Layer,
+};
+
+fn filter_out_everything() -> filter::DynFilterFn {
+    // Use dynamic filter fn to disable interest caching and max-level hints,
+    // allowing us to put all of these tests in the same file.
+    filter::dynamic_filter_fn(|_, _| false)
+}
+
+#[test]
+fn option_some() {
+    let (layer, handle) = layer::mock().only().run_with_handle();
+    let layer = layer.with_filter(Some(filter_out_everything()));
+
+    let _guard = tracing_subscriber::registry().with(layer).set_default();
+
+    for i in 0..2 {
+        tracing::info!(i);
+    }
+
+    handle.assert_finished();
+}
+
+#[test]
+fn option_none() {
+    let (layer, handle) = layer::mock()
+        .event(expect::event())
+        .event(expect::event())
+        .only()
+        .run_with_handle();
+    let layer = layer.with_filter(None::>);
+
+    let _guard = tracing_subscriber::registry().with(layer).set_default();
+
+    for i in 0..2 {
+        tracing::info!(i);
+    }
+
+    handle.assert_finished();
+}
+
+#[test]
+fn option_mixed() {
+    let (layer, handle) = layer::mock()
+        .event(expect::event())
+        .only()
+        .run_with_handle();
+    let layer = layer
+        .with_filter(filter::dynamic_filter_fn(|meta, _ctx| {
+            meta.target() == "interesting"
+        }))
+        .with_filter(None::>);
+
+    let _guard = tracing_subscriber::registry().with(layer).set_default();
+
+    tracing::info!(target: "interesting", x="foo");
+    tracing::info!(target: "boring", x="bar");
+
+    handle.assert_finished();
+}
+
+/// The lack of a max level hint from a `None` filter should result in no max
+/// level hint when combined with other filters/layer.
+#[test]
+fn none_max_level_hint() {
+    let (layer_some, handle_none) = layer::mock()
+        .event(expect::event())
+        .event(expect::event())
+        .only()
+        .run_with_handle();
+    let subscribe_none = layer_some.with_filter(None::>);
+    assert!(subscribe_none.max_level_hint().is_none());
+
+    let (layer_filter_fn, handle_filter_fn) = layer::mock()
+        .event(expect::event())
+        .only()
+        .run_with_handle();
+    let max_level = Level::INFO;
+    let layer_filter_fn = layer_filter_fn.with_filter(
+        filter::dynamic_filter_fn(move |meta, _| return meta.level() <= &max_level)
+            .with_max_level_hint(max_level),
+    );
+    assert_eq!(layer_filter_fn.max_level_hint(), Some(LevelFilter::INFO));
+
+    let subscriber = tracing_subscriber::registry()
+        .with(subscribe_none)
+        .with(layer_filter_fn);
+    // The absence of a hint from the `None` filter upgrades the `INFO` hint
+    // from the filter fn layer.
+    assert!(subscriber.max_level_hint().is_none());
+
+    let _guard = subscriber.set_default();
+    tracing::info!(target: "interesting", x="foo");
+    tracing::debug!(target: "sometimes_interesting", x="bar");
+
+    handle_none.assert_finished();
+    handle_filter_fn.assert_finished();
+}
+
+/// The max level hint from inside a `Some(filter)` filter should be propagated
+/// and combined with other filters/layers.
+#[test]
+fn some_max_level_hint() {
+    let (layer_some, handle_some) = layer::mock()
+        .event(expect::event())
+        .event(expect::event())
+        .only()
+        .run_with_handle();
+    let layer_some = layer_some.with_filter(Some(
+        filter::dynamic_filter_fn(move |meta, _| return meta.level() <= &Level::DEBUG)
+            .with_max_level_hint(Level::DEBUG),
+    ));
+    assert_eq!(layer_some.max_level_hint(), Some(LevelFilter::DEBUG));
+
+    let (layer_filter_fn, handle_filter_fn) = layer::mock()
+        .event(expect::event())
+        .only()
+        .run_with_handle();
+    let layer_filter_fn = layer_filter_fn.with_filter(
+        filter::dynamic_filter_fn(move |meta, _| return meta.level() <= &Level::INFO)
+            .with_max_level_hint(Level::INFO),
+    );
+    assert_eq!(layer_filter_fn.max_level_hint(), Some(LevelFilter::INFO));
+
+    let subscriber = tracing_subscriber::registry()
+        .with(layer_some)
+        .with(layer_filter_fn);
+    // The `DEBUG` hint from the `Some` filter upgrades the `INFO` hint from the
+    // filter fn layer.
+    assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::DEBUG));
+
+    let _guard = subscriber.set_default();
+    tracing::info!(target: "interesting", x="foo");
+    tracing::debug!(target: "sometimes_interesting", x="bar");
+
+    handle_some.assert_finished();
+    handle_filter_fn.assert_finished();
+}
diff --git a/tracing-subscriber/tests/layer_filters/vec.rs b/tracing-subscriber/tests/layer_filters/vec.rs
index 3555e19545..42aa308f6b 100644
--- a/tracing-subscriber/tests/layer_filters/vec.rs
+++ b/tracing-subscriber/tests/layer_filters/vec.rs
@@ -116,6 +116,6 @@ fn all_filtered_max_level_hint() {
 #[test]
 fn empty_vec() {
     // Just a None means everything is off
-    let collector = tracing_subscriber::registry().with(Vec::::new());
-    assert_eq!(collector.max_level_hint(), Some(LevelFilter::OFF));
+    let subscriber = tracing_subscriber::registry().with(Vec::::new());
+    assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::OFF));
 }
diff --git a/tracing-subscriber/tests/option_filter_interest_caching.rs b/tracing-subscriber/tests/option_filter_interest_caching.rs
new file mode 100644
index 0000000000..609007cceb
--- /dev/null
+++ b/tracing-subscriber/tests/option_filter_interest_caching.rs
@@ -0,0 +1,53 @@
+// A separate test crate for `Option` for isolation from other tests
+// that may influence the interest cache.
+
+use std::sync::{
+    atomic::{AtomicUsize, Ordering},
+    Arc,
+};
+use tracing_mock::{expect, layer};
+use tracing_subscriber::{filter, prelude::*, Layer};
+
+/// A `None` filter should always be interested in events, and it should not
+/// needlessly degrade the caching of other filters.
+#[test]
+fn none_interest_cache() {
+    let (layer_none, handle_none) = layer::mock()
+        .event(expect::event())
+        .event(expect::event())
+        .only()
+        .run_with_handle();
+    let layer_none = layer_none.with_filter(None::>);
+
+    let times_filtered = Arc::new(AtomicUsize::new(0));
+    let (layer_filter_fn, handle_filter_fn) = layer::mock()
+        .event(expect::event())
+        .event(expect::event())
+        .only()
+        .run_with_handle();
+    let layer_filter_fn = layer_filter_fn.with_filter(filter::filter_fn({
+        let times_filtered = Arc::clone(×_filtered);
+        move |_| {
+            times_filtered.fetch_add(1, Ordering::Relaxed);
+            true
+        }
+    }));
+
+    let subscriber = tracing_subscriber::registry()
+        .with(layer_none)
+        .with(layer_filter_fn);
+
+    let _guard = subscriber.set_default();
+    for _ in 0..2 {
+        tracing::debug!(target: "always_interesting", x="bar");
+    }
+
+    // The `None` filter is unchanging and performs no filtering, so it should
+    // be cacheable and always be interested in events. The filter fn is a
+    // non-dynamic filter fn, which means the result can be cached per callsite.
+    // The filter fn should only need to be called once, and the `Option` filter
+    // should not interfere in the caching of that result.
+    assert_eq!(times_filtered.load(Ordering::Relaxed), 1);
+    handle_none.assert_finished();
+    handle_filter_fn.assert_finished();
+}
diff --git a/tracing-subscriber/tests/vec_subscriber_filter_interests_cached.rs b/tracing-subscriber/tests/vec_subscriber_filter_interests_cached.rs
index 8e1ecabce1..e80cfa09cf 100644
--- a/tracing-subscriber/tests/vec_subscriber_filter_interests_cached.rs
+++ b/tracing-subscriber/tests/vec_subscriber_filter_interests_cached.rs
@@ -8,7 +8,7 @@ use tracing_mock::{layer::MockLayer, *};
 use tracing_subscriber::{filter, prelude::*};
 
 #[test]
-fn vec_subscriber_filter_interests_are_cached() {
+fn vec_layer_filter_interests_are_cached() {
     let mk_filtered = |level: Level, subscriber: MockLayer| {
         let seen = Arc::new(Mutex::new(HashMap::new()));
         let filter = filter::filter_fn({
diff --git a/tracing/tests/no_subscriber.rs b/tracing/tests/no_subscriber.rs
index 77e2c106c1..d15a2c8a18 100644
--- a/tracing/tests/no_subscriber.rs
+++ b/tracing/tests/no_subscriber.rs
@@ -4,7 +4,7 @@ use tracing_mock::subscriber;
 
 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
 #[test]
-fn no_collector_disables_global() {
+fn no_subscriber_disables_global() {
     // Reproduces https://github.com/tokio-rs/tracing/issues/1999
     let (subscriber, handle) = subscriber::mock().only().run_with_handle();
     tracing::subscriber::set_global_default(subscriber)