Skip to content

Commit 5b74376

Browse files
jsgfdavidbarskyryanthomasjohnsonhawkw
committed
subscriber: Implement layer::Filter for Option<Filter> (#2407)
It's currently awkward to have an optional per-layer filter. Implement `Filter<L>` for `Option<F> where F: Filter<L>`, following the example of `Layer`. A `None` filter passes everything through. Also, it looks like Filter for Arc/Box doesn't pass through all the methods, so extend the `filter_impl_body` macro to include them. This also adds a couple of tests and updates the docs. --------- Co-authored-by: David Barsky <[email protected]> Co-authored-by: Ryan Johnson <[email protected]> Co-authored-by: Eliza Weisman <[email protected]>
1 parent 9afa906 commit 5b74376

File tree

5 files changed

+320
-0
lines changed

5 files changed

+320
-0
lines changed

tracing-subscriber/src/filter/layer_filters/mod.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,36 @@ macro_rules! filter_impl_body {
478478
fn max_level_hint(&self) -> Option<LevelFilter> {
479479
self.deref().max_level_hint()
480480
}
481+
482+
#[inline]
483+
fn event_enabled(&self, event: &Event<'_>, cx: &Context<'_, S>) -> bool {
484+
self.deref().event_enabled(event, cx)
485+
}
486+
487+
#[inline]
488+
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
489+
self.deref().on_new_span(attrs, id, ctx)
490+
}
491+
492+
#[inline]
493+
fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
494+
self.deref().on_record(id, values, ctx)
495+
}
496+
497+
#[inline]
498+
fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
499+
self.deref().on_enter(id, ctx)
500+
}
501+
502+
#[inline]
503+
fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
504+
self.deref().on_exit(id, ctx)
505+
}
506+
507+
#[inline]
508+
fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
509+
self.deref().on_close(id, ctx)
510+
}
481511
};
482512
}
483513

@@ -493,6 +523,75 @@ impl<S> layer::Filter<S> for Box<dyn layer::Filter<S> + Send + Sync + 'static> {
493523
filter_impl_body!();
494524
}
495525

526+
// Implement Filter for Option<Filter> where None => allow
527+
#[cfg(feature = "registry")]
528+
#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
529+
impl<F, S> layer::Filter<S> for Option<F>
530+
where
531+
F: layer::Filter<S>,
532+
{
533+
#[inline]
534+
fn enabled(&self, meta: &Metadata<'_>, ctx: &Context<'_, S>) -> bool {
535+
self.as_ref()
536+
.map(|inner| inner.enabled(meta, ctx))
537+
.unwrap_or(true)
538+
}
539+
540+
#[inline]
541+
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
542+
self.as_ref()
543+
.map(|inner| inner.callsite_enabled(meta))
544+
.unwrap_or_else(Interest::always)
545+
}
546+
547+
#[inline]
548+
fn max_level_hint(&self) -> Option<LevelFilter> {
549+
self.as_ref().and_then(|inner| inner.max_level_hint())
550+
}
551+
552+
#[inline]
553+
fn event_enabled(&self, event: &Event<'_>, ctx: &Context<'_, S>) -> bool {
554+
self.as_ref()
555+
.map(|inner| inner.event_enabled(event, ctx))
556+
.unwrap_or(true)
557+
}
558+
559+
#[inline]
560+
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
561+
if let Some(inner) = self {
562+
inner.on_new_span(attrs, id, ctx)
563+
}
564+
}
565+
566+
#[inline]
567+
fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
568+
if let Some(inner) = self {
569+
inner.on_record(id, values, ctx)
570+
}
571+
}
572+
573+
#[inline]
574+
fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
575+
if let Some(inner) = self {
576+
inner.on_enter(id, ctx)
577+
}
578+
}
579+
580+
#[inline]
581+
fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
582+
if let Some(inner) = self {
583+
inner.on_exit(id, ctx)
584+
}
585+
}
586+
587+
#[inline]
588+
fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
589+
if let Some(inner) = self {
590+
inner.on_close(id, ctx)
591+
}
592+
}
593+
}
594+
496595
// === impl Filtered ===
497596

498597
impl<L, F, S> Filtered<L, F, S> {

tracing-subscriber/src/layer/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,24 @@
468468
//! function pointer. In addition, when more control is required, the [`Filter`]
469469
//! trait may also be implemented for user-defined types.
470470
//!
471+
//! //! [`Option<Filter>`] also implements [`Filter`], which allows for an optional
472+
//! filter. [`None`](Option::None) filters out _nothing_ (that is, allows
473+
//! everything through). For example:
474+
//!
475+
//! ```rust
476+
//! # use tracing_subscriber::{filter::filter_fn, Layer};
477+
//! # use tracing_core::{Metadata, subscriber::Subscriber};
478+
//! # struct MyLayer<S>(std::marker::PhantomData<S>);
479+
//! # impl<S> MyLayer<S> { fn new() -> Self { Self(std::marker::PhantomData)} }
480+
//! # impl<S: Subscriber> Layer<S> for MyLayer<S> {}
481+
//! # fn my_filter(_: &str) -> impl Fn(&Metadata) -> bool { |_| true }
482+
//! fn setup_tracing<S: Subscriber>(filter_config: Option<&str>) {
483+
//! let layer = MyLayer::<S>::new()
484+
//! .with_filter(filter_config.map(|config| filter_fn(my_filter(config))));
485+
//! //...
486+
//! }
487+
//! ```
488+
//!
471489
//! <pre class="compile_fail" style="white-space:normal;font:inherit;">
472490
//! <strong>Warning</strong>: Currently, the <a href="../struct.Registry.html">
473491
//! <code>Registry</code></a> type defined in this crate is the only root

tracing-subscriber/tests/layer_filters/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
mod boxed;
33
mod downcast_raw;
44
mod filter_scopes;
5+
mod option;
56
mod per_event;
67
mod targets;
78
mod trees;
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use super::*;
2+
use tracing::Subscriber;
3+
use tracing_subscriber::{
4+
filter::{self, LevelFilter},
5+
prelude::*,
6+
Layer,
7+
};
8+
9+
fn filter_out_everything<S>() -> filter::DynFilterFn<S> {
10+
// Use dynamic filter fn to disable interest caching and max-level hints,
11+
// allowing us to put all of these tests in the same file.
12+
filter::dynamic_filter_fn(|_, _| false)
13+
}
14+
15+
#[test]
16+
fn option_some() {
17+
let (layer, handle) = layer::mock().only().run_with_handle();
18+
let layer = layer.with_filter(Some(filter_out_everything()));
19+
20+
let _guard = tracing_subscriber::registry().with(layer).set_default();
21+
22+
for i in 0..2 {
23+
tracing::info!(i);
24+
}
25+
26+
handle.assert_finished();
27+
}
28+
29+
#[test]
30+
fn option_none() {
31+
let (layer, handle) = layer::mock()
32+
.event(expect::event())
33+
.event(expect::event())
34+
.only()
35+
.run_with_handle();
36+
let layer = layer.with_filter(None::<filter::DynFilterFn<_>>);
37+
38+
let _guard = tracing_subscriber::registry().with(layer).set_default();
39+
40+
for i in 0..2 {
41+
tracing::info!(i);
42+
}
43+
44+
handle.assert_finished();
45+
}
46+
47+
#[test]
48+
fn option_mixed() {
49+
let (layer, handle) = layer::mock()
50+
.event(expect::event())
51+
.only()
52+
.run_with_handle();
53+
let layer = layer
54+
.with_filter(filter::dynamic_filter_fn(|meta, _ctx| {
55+
meta.target() == "interesting"
56+
}))
57+
.with_filter(None::<filter::DynFilterFn<_>>);
58+
59+
let _guard = tracing_subscriber::registry().with(layer).set_default();
60+
61+
tracing::info!(target: "interesting", x="foo");
62+
tracing::info!(target: "boring", x="bar");
63+
64+
handle.assert_finished();
65+
}
66+
67+
/// The lack of a max level hint from a `None` filter should result in no max
68+
/// level hint when combined with other filters/subscribers.
69+
#[test]
70+
fn none_max_level_hint() {
71+
let (layer_some, handle_none) = layer::mock()
72+
.event(expect::event())
73+
.event(expect::event())
74+
.only()
75+
.run_with_handle();
76+
let subscribe_none = layer_some.with_filter(None::<filter::DynFilterFn<_>>);
77+
assert!(subscribe_none.max_level_hint().is_none());
78+
79+
let (subscribe_filter_fn, handle_filter_fn) = layer::mock()
80+
.event(expect::event())
81+
.only()
82+
.run_with_handle();
83+
let max_level = Level::INFO;
84+
let subscribe_filter_fn = subscribe_filter_fn.with_filter(
85+
filter::dynamic_filter_fn(move |meta, _| return meta.level() <= &max_level)
86+
.with_max_level_hint(max_level),
87+
);
88+
assert_eq!(
89+
subscribe_filter_fn.max_level_hint(),
90+
Some(LevelFilter::INFO)
91+
);
92+
93+
let subscriber = tracing_subscriber::registry()
94+
.with(subscribe_none)
95+
.with(subscribe_filter_fn);
96+
// The absence of a hint from the `None` filter upgrades the `INFO` hint
97+
// from the filter fn subscriber.
98+
assert!(subscriber.max_level_hint().is_none());
99+
100+
let _guard = subscriber.set_default();
101+
tracing::info!(target: "interesting", x="foo");
102+
tracing::debug!(target: "sometimes_interesting", x="bar");
103+
104+
handle_none.assert_finished();
105+
handle_filter_fn.assert_finished();
106+
}
107+
108+
/// The max level hint from inside a `Some(filter)` filter should be propagated
109+
/// and combined with other filters/subscribers.
110+
#[test]
111+
fn some_max_level_hint() {
112+
let (layer_some, handle_some) = layer::mock()
113+
.event(expect::event())
114+
.event(expect::event())
115+
.only()
116+
.run_with_handle();
117+
let layer_some = layer_some.with_filter(Some(
118+
filter::dynamic_filter_fn(move |meta, _| return meta.level() <= &Level::DEBUG)
119+
.with_max_level_hint(Level::DEBUG),
120+
));
121+
assert_eq!(layer_some.max_level_hint(), Some(LevelFilter::DEBUG));
122+
123+
let (subscribe_filter_fn, handle_filter_fn) = layer::mock()
124+
.event(expect::event())
125+
.only()
126+
.run_with_handle();
127+
let subscribe_filter_fn = subscribe_filter_fn.with_filter(
128+
filter::dynamic_filter_fn(move |meta, _| return meta.level() <= &Level::INFO)
129+
.with_max_level_hint(Level::INFO),
130+
);
131+
assert_eq!(
132+
subscribe_filter_fn.max_level_hint(),
133+
Some(LevelFilter::INFO)
134+
);
135+
136+
let subscriber = tracing_subscriber::registry()
137+
.with(layer_some)
138+
.with(subscribe_filter_fn);
139+
// The `DEBUG` hint from the `Some` filter upgrades the `INFO` hint from the
140+
// filter fn subscriber.
141+
assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::DEBUG));
142+
143+
let _guard = subscriber.set_default();
144+
tracing::info!(target: "interesting", x="foo");
145+
tracing::debug!(target: "sometimes_interesting", x="bar");
146+
147+
handle_some.assert_finished();
148+
handle_filter_fn.assert_finished();
149+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// A separate test crate for `Option<Filter>` for isolation from other tests
2+
// that may influence the interest cache.
3+
4+
use std::sync::{
5+
atomic::{AtomicUsize, Ordering},
6+
Arc,
7+
};
8+
use tracing_mock::{expect, layer};
9+
use tracing_subscriber::{filter, prelude::*, Layer};
10+
11+
/// A `None` filter should always be interested in events, and it should not
12+
/// needlessly degrade the caching of other filters.
13+
#[test]
14+
fn none_interest_cache() {
15+
let (layer_none, handle_none) = layer::mock()
16+
.event(expect::event())
17+
.event(expect::event())
18+
.only()
19+
.run_with_handle();
20+
let layer_none = layer_none.with_filter(None::<filter::DynFilterFn<_>>);
21+
22+
let times_filtered = Arc::new(AtomicUsize::new(0));
23+
let (layer_filter_fn, handle_filter_fn) = layer::mock()
24+
.event(expect::event())
25+
.event(expect::event())
26+
.only()
27+
.run_with_handle();
28+
let layer_filter_fn = layer_filter_fn.with_filter(filter::filter_fn({
29+
let times_filtered = Arc::clone(&times_filtered);
30+
move |_| {
31+
times_filtered.fetch_add(1, Ordering::Relaxed);
32+
true
33+
}
34+
}));
35+
36+
let subscriber = tracing_subscriber::registry()
37+
.with(layer_none)
38+
.with(layer_filter_fn);
39+
40+
let _guard = subscriber.set_default();
41+
for _ in 0..2 {
42+
tracing::debug!(target: "always_interesting", x="bar");
43+
}
44+
45+
// The `None` filter is unchanging and performs no filtering, so it should
46+
// be cacheable and always be interested in events. The filter fn is a
47+
// non-dynamic filter fn, which means the result can be cached per callsite.
48+
// The filter fn should only need to be called once, and the `Option` filter
49+
// should not interfere in the caching of that result.
50+
assert_eq!(times_filtered.load(Ordering::Relaxed), 1);
51+
handle_none.assert_finished();
52+
handle_filter_fn.assert_finished();
53+
}

0 commit comments

Comments
 (0)