Skip to content

Commit 5ddbd4e

Browse files
authored
subscriber: add combinators for combining filters (#1578)
## Motivation In some cases, it can be useful to express a more complex filtering behavior by composing multiple filters. This is especially useful when re-using the same logic in multiple filters. ## Solution This branch adds a new `FilterExt` extension trait with combinators for composing filters. The current set of combinators are `And` (enables spans/events that are enabled by *both* filters), `Or` (enables any span or event enabled by *either* filter), and `Not` (negate the output of a filter). Signed-off-by: Eliza Weisman <[email protected]>
1 parent 5d08634 commit 5ddbd4e

File tree

3 files changed

+676
-27
lines changed

3 files changed

+676
-27
lines changed
Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
//! Filter combinators
2+
use crate::layer::{Context, Filter};
3+
use std::{cmp, fmt, marker::PhantomData};
4+
use tracing_core::{subscriber::Interest, LevelFilter, Metadata};
5+
6+
/// Combines two [`Filter`]s so that spans and events are enabled if and only if
7+
/// *both* filters return `true`.
8+
///
9+
/// This type is typically returned by the [`FilterExt::and`] method. See that
10+
/// method's documentation for details.
11+
///
12+
/// [`Filter`]: crate::layer::Filter
13+
/// [`FilterExt::and`]: crate::filter::FilterExt::and
14+
pub struct And<A, B, S> {
15+
a: A,
16+
b: B,
17+
_s: PhantomData<fn(S)>,
18+
}
19+
20+
/// Combines two [`Filter`]s so that spans and events are enabled if *either* filter
21+
/// returns `true`.
22+
///
23+
/// This type is typically returned by the [`FilterExt::or`] method. See that
24+
/// method's documentation for details.
25+
///
26+
/// [`Filter`]: crate::layer::Filter
27+
/// [`FilterExt::or`]: crate::filter::FilterExt::or
28+
pub struct Or<A, B, S> {
29+
a: A,
30+
b: B,
31+
_s: PhantomData<fn(S)>,
32+
}
33+
34+
/// Inverts the result of a [`Filter`].
35+
///
36+
/// If the wrapped filter would enable a span or event, it will be disabled. If
37+
/// it would disable a span or event, that span or event will be enabled.
38+
///
39+
/// This type is typically returned by the [`FilterExt::or`] method. See that
40+
/// method's documentation for details.
41+
///
42+
/// [`Filter`]: crate::layer::Filter
43+
/// [`FilterExt::or`]: crate::filter::FilterExt::or
44+
pub struct Not<A, S> {
45+
a: A,
46+
_s: PhantomData<fn(S)>,
47+
}
48+
49+
// === impl And ===
50+
51+
impl<A, B, S> And<A, B, S>
52+
where
53+
A: Filter<S>,
54+
B: Filter<S>,
55+
{
56+
/// Combines two [`Filter`]s so that spans and events are enabled if and only if
57+
/// *both* filters return `true`.
58+
///
59+
/// # Examples
60+
///
61+
/// Enabling spans or events if they have both a particular target *and* are
62+
/// above a certain level:
63+
///
64+
/// ```ignore
65+
/// use tracing_subscriber::{
66+
/// filter::{filter_fn, LevelFilter, combinator::And},
67+
/// prelude::*,
68+
/// };
69+
///
70+
/// // Enables spans and events with targets starting with `interesting_target`:
71+
/// let target_filter = filter_fn(|meta| {
72+
/// meta.target().starts_with("interesting_target")
73+
/// });
74+
///
75+
/// // Enables spans and events with levels `INFO` and below:
76+
/// let level_filter = LevelFilter::INFO;
77+
///
78+
/// // Combine the two filters together so that a span or event is only enabled
79+
/// // if *both* filters would enable it:
80+
/// let filter = And::new(level_filter, target_filter);
81+
///
82+
/// tracing_subscriber::registry()
83+
/// .with(tracing_subscriber::fmt::layer().with_filter(filter))
84+
/// .init();
85+
///
86+
/// // This event will *not* be enabled:
87+
/// tracing::info!("an event with an uninteresting target");
88+
///
89+
/// // This event *will* be enabled:
90+
/// tracing::info!(target: "interesting_target", "a very interesting event");
91+
///
92+
/// // This event will *not* be enabled:
93+
/// tracing::debug!(target: "interesting_target", "interesting debug event...");
94+
/// ```
95+
///
96+
/// [`Filter`]: crate::layer::Filter
97+
pub(crate) fn new(a: A, b: B) -> Self {
98+
Self {
99+
a,
100+
b,
101+
_s: PhantomData,
102+
}
103+
}
104+
}
105+
106+
impl<A, B, S> Filter<S> for And<A, B, S>
107+
where
108+
A: Filter<S>,
109+
B: Filter<S>,
110+
{
111+
#[inline]
112+
fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool {
113+
self.a.enabled(meta, cx) && self.b.enabled(meta, cx)
114+
}
115+
116+
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
117+
let a = self.a.callsite_enabled(meta);
118+
if a.is_never() {
119+
return a;
120+
}
121+
122+
let b = self.b.callsite_enabled(meta);
123+
124+
if !b.is_always() {
125+
return b;
126+
}
127+
128+
a
129+
}
130+
131+
fn max_level_hint(&self) -> Option<LevelFilter> {
132+
// If either hint is `None`, return `None`. Otherwise, return the most restrictive.
133+
cmp::min(self.a.max_level_hint(), self.b.max_level_hint())
134+
}
135+
}
136+
137+
impl<A, B, S> Clone for And<A, B, S>
138+
where
139+
A: Clone,
140+
B: Clone,
141+
{
142+
fn clone(&self) -> Self {
143+
Self {
144+
a: self.a.clone(),
145+
b: self.b.clone(),
146+
_s: PhantomData,
147+
}
148+
}
149+
}
150+
151+
impl<A, B, S> fmt::Debug for And<A, B, S>
152+
where
153+
A: fmt::Debug,
154+
B: fmt::Debug,
155+
{
156+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157+
f.debug_struct("And")
158+
.field("a", &self.a)
159+
.field("b", &self.b)
160+
.finish()
161+
}
162+
}
163+
164+
// === impl Or ===
165+
166+
impl<A, B, S> Or<A, B, S>
167+
where
168+
A: Filter<S>,
169+
B: Filter<S>,
170+
{
171+
/// Combines two [`Filter`]s so that spans and events are enabled if *either* filter
172+
/// returns `true`.
173+
///
174+
/// # Examples
175+
///
176+
/// Enabling spans and events at the `INFO` level and above, and all spans
177+
/// and events with a particular target:
178+
///
179+
/// ```ignore
180+
/// use tracing_subscriber::{
181+
/// filter::{filter_fn, LevelFilter, combinator::Or},
182+
/// prelude::*,
183+
/// };
184+
///
185+
/// // Enables spans and events with targets starting with `interesting_target`:
186+
/// let target_filter = filter_fn(|meta| {
187+
/// meta.target().starts_with("interesting_target")
188+
/// });
189+
///
190+
/// // Enables spans and events with levels `INFO` and below:
191+
/// let level_filter = LevelFilter::INFO;
192+
///
193+
/// // Combine the two filters together so that a span or event is enabled
194+
/// // if it is at INFO or lower, or if it has a target starting with
195+
/// // `interesting_target`.
196+
/// let filter = Or::new(level_filter, target_filter);
197+
///
198+
/// tracing_subscriber::registry()
199+
/// .with(tracing_subscriber::fmt::layer().with_filter(filter))
200+
/// .init();
201+
///
202+
/// // This event will *not* be enabled:
203+
/// tracing::debug!("an uninteresting event");
204+
///
205+
/// // This event *will* be enabled:
206+
/// tracing::info!("an uninteresting INFO event");
207+
///
208+
/// // This event *will* be enabled:
209+
/// tracing::info!(target: "interesting_target", "a very interesting event");
210+
///
211+
/// // This event *will* be enabled:
212+
/// tracing::debug!(target: "interesting_target", "interesting debug event...");
213+
/// ```
214+
///
215+
/// Enabling a higher level for a particular target by using `Or` in
216+
/// conjunction with the [`And`] combinator:
217+
///
218+
/// ```ignore
219+
/// use tracing_subscriber::{
220+
/// filter::{filter_fn, LevelFilter, combinator},
221+
/// prelude::*,
222+
/// };
223+
///
224+
/// // This filter will enable spans and events with targets beginning with
225+
/// // `my_crate`:
226+
/// let my_crate = filter_fn(|meta| {
227+
/// meta.target().starts_with("my_crate")
228+
/// });
229+
///
230+
/// // Combine the `my_crate` filter with a `LevelFilter` to produce a filter
231+
/// // that will enable the `INFO` level and lower for spans and events with
232+
/// // `my_crate` targets:
233+
/// let filter = combinator::And::new(my_crate, LevelFilter::INFO);
234+
///
235+
/// // If a span or event *doesn't* have a target beginning with
236+
/// // `my_crate`, enable it if it has the `WARN` level or lower:
237+
/// // let filter = combinator::Or::new(filter, LevelFilter::WARN);
238+
///
239+
/// tracing_subscriber::registry()
240+
/// .with(tracing_subscriber::fmt::layer().with_filter(filter))
241+
/// .init();
242+
/// ```
243+
///
244+
/// [`Filter`]: crate::layer::Filter
245+
pub(crate) fn new(a: A, b: B) -> Self {
246+
Self {
247+
a,
248+
b,
249+
_s: PhantomData,
250+
}
251+
}
252+
}
253+
254+
impl<A, B, S> Filter<S> for Or<A, B, S>
255+
where
256+
A: Filter<S>,
257+
B: Filter<S>,
258+
{
259+
#[inline]
260+
fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool {
261+
self.a.enabled(meta, cx) || self.b.enabled(meta, cx)
262+
}
263+
264+
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
265+
let a = self.a.callsite_enabled(meta);
266+
let b = self.b.callsite_enabled(meta);
267+
268+
// If either filter will always enable the span or event, return `always`.
269+
if a.is_always() || b.is_always() {
270+
return Interest::always();
271+
}
272+
273+
// Okay, if either filter will sometimes enable the span or event,
274+
// return `sometimes`.
275+
if a.is_sometimes() || b.is_sometimes() {
276+
return Interest::sometimes();
277+
}
278+
279+
debug_assert!(
280+
a.is_never() && b.is_never(),
281+
"if neither filter was `always` or `sometimes`, both must be `never` (a={:?}; b={:?})",
282+
a,
283+
b,
284+
);
285+
Interest::never()
286+
}
287+
288+
fn max_level_hint(&self) -> Option<LevelFilter> {
289+
// If either hint is `None`, return `None`. Otherwise, return the less restrictive.
290+
Some(cmp::max(self.a.max_level_hint()?, self.b.max_level_hint()?))
291+
}
292+
}
293+
294+
impl<A, B, S> Clone for Or<A, B, S>
295+
where
296+
A: Clone,
297+
B: Clone,
298+
{
299+
fn clone(&self) -> Self {
300+
Self {
301+
a: self.a.clone(),
302+
b: self.b.clone(),
303+
_s: PhantomData,
304+
}
305+
}
306+
}
307+
308+
impl<A, B, S> fmt::Debug for Or<A, B, S>
309+
where
310+
A: fmt::Debug,
311+
B: fmt::Debug,
312+
{
313+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314+
f.debug_struct("Or")
315+
.field("a", &self.a)
316+
.field("b", &self.b)
317+
.finish()
318+
}
319+
}
320+
321+
// === impl Not ===
322+
323+
impl<A, S> Not<A, S>
324+
where
325+
A: Filter<S>,
326+
{
327+
/// Inverts the result of a [`Filter`].
328+
///
329+
/// If the wrapped filter would enable a span or event, it will be disabled. If
330+
/// it would disable a span or event, that span or event will be enabled.
331+
///
332+
/// [`Filter`]: crate::layer::Filter
333+
pub(crate) fn new(a: A) -> Self {
334+
Self { a, _s: PhantomData }
335+
}
336+
}
337+
338+
impl<A, S> Filter<S> for Not<A, S>
339+
where
340+
A: Filter<S>,
341+
{
342+
#[inline]
343+
fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool {
344+
!self.a.enabled(meta, cx)
345+
}
346+
347+
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
348+
match self.a.callsite_enabled(meta) {
349+
i if i.is_always() => Interest::never(),
350+
i if i.is_never() => Interest::always(),
351+
_ => Interest::sometimes(),
352+
}
353+
}
354+
355+
fn max_level_hint(&self) -> Option<LevelFilter> {
356+
// TODO(eliza): figure this out???
357+
None
358+
}
359+
}
360+
361+
impl<A, S> Clone for Not<A, S>
362+
where
363+
A: Clone,
364+
{
365+
fn clone(&self) -> Self {
366+
Self {
367+
a: self.a.clone(),
368+
_s: PhantomData,
369+
}
370+
}
371+
}
372+
373+
impl<A, S> fmt::Debug for Not<A, S>
374+
where
375+
A: fmt::Debug,
376+
{
377+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378+
f.debug_tuple("Not").field(&self.a).finish()
379+
}
380+
}

0 commit comments

Comments
 (0)