From 6433c6470778a341e1ca4ab31aefd70ae7b427a6 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 10 Sep 2021 09:09:03 -0700 Subject: [PATCH] subscriber: impl Subscribe for FilterFn and DynFilterFn (#1546) ## Motivation Currently, the `FilterFn` and `DynFilterFn` filters are only usable as `Filter`s for per-subscriber filtering. However, there's no real reason they can't _also_ have `Subscribe` implementations, making them usable as global filters as well. ## Solution This branch adds `Subscribe` implementations to `FilterFn` and `DynFilterFn`, and moves them into their own file. It also changes the feature flagging so that the "registry" feature is only required for the `Filter` implementations --- the types themselves don't involve any feature-flagged code, only the `Filter` trait. Signed-off-by: Eliza Weisman --- tracing-subscriber/src/filter/filter_fn.rs | 748 ++++++++++++++++++ tracing-subscriber/src/filter/mod.rs | 2 + .../src/filter/subscriber_filters/mod.rs | 650 +-------------- 3 files changed, 751 insertions(+), 649 deletions(-) create mode 100644 tracing-subscriber/src/filter/filter_fn.rs diff --git a/tracing-subscriber/src/filter/filter_fn.rs b/tracing-subscriber/src/filter/filter_fn.rs new file mode 100644 index 0000000000..9961161e94 --- /dev/null +++ b/tracing-subscriber/src/filter/filter_fn.rs @@ -0,0 +1,748 @@ +#[cfg(feature = "registry")] +use crate::subscribe::Filter; +use crate::{ + filter::LevelFilter, + subscribe::{Context, Subscribe}, +}; +use std::{any::type_name, fmt, marker::PhantomData}; +use tracing_core::{Collect, Interest, Metadata}; + +/// A filter implemented by a closure or function pointer that +/// determines whether a given span or event is enabled, based on its +/// [`Metadata`]. +/// +/// This type can be used for both [per-subscriber filtering][plf] (using its +/// [`Filter`] implementation) and [global filtering][global] (using its +/// [`Subscribe`] implementation). +/// +/// See the [documentation on filtering with subscribers][filtering] for details. +/// +/// [`Metadata`]: tracing_core::Metadata +/// [`Filter`]: crate::subscribe::Filter +/// [`Subscribe`]: crate::subscribe::Subscribe +/// [plf]: crate::subscribe#per-subscriber-filtering +/// [global]: crate::subscribe#global-filtering +/// [filtering]: crate::subscribe#filtering-with-subscribers +#[derive(Clone)] +pub struct FilterFn) -> bool> { + enabled: F, + max_level_hint: Option, +} + +/// A filter implemented by a closure or function pointer that +/// determines whether a given span or event is enabled _dynamically_, +/// potentially based on the current [span context]. +/// +/// This type can be used for both [per-subscriber filtering][plf] (using its +/// [`Filter`] implementation) and [global filtering][global] (using its +/// [`Subscribe`] implementation). +/// +/// See the [documentation on filtering with subscribers][filtering] for details. +/// +/// [span context]: crate::subscribe::Context +/// [`Filter`]: crate::subscribe::Filter +/// [`Subscribe`]: crate::subscribe::Subscribe +/// [plf]: crate::subscribe#per-subscriber-filtering +/// [global]: crate::subscribe#global-filtering +/// [filtering]: crate::subscribe#filtering-with-subscribers +pub struct DynFilterFn< + C, + // TODO(eliza): should these just be boxed functions? + F = fn(&Metadata<'_>, &Context<'_, C>) -> bool, + R = fn(&'static Metadata<'static>) -> Interest, +> { + enabled: F, + register_callsite: Option, + max_level_hint: Option, + _s: PhantomData, +} + +// === impl FilterFn === + +/// Constructs a [`FilterFn`], from a function or closure that returns `true` if +/// a span or event should be enabled, based on its [`Metadata`]. +/// +/// The returned [`FilterFn`] can be used for both [per-subscriber filtering][plf] +/// (using its [`Filter`] implementation) and [global filtering][global] (using +/// its [`Subscribe`] implementation). +/// +/// See the [documentation on filtering with subscribers][filtering] for details. +/// +/// This is equivalent to calling [`FilterFn::new`]. +/// +/// [`Metadata`]: tracing_core::Metadata +/// [`Filter`]: crate::subscribe::Filter +/// [`Subscribe`]: crate::subscribe::Subscribe +/// [plf]: crate::subscribe#per-subscriber-filtering +/// [global]: crate::subscribe#global-filtering +/// [filtering]: crate::subscribe#filtering-with-subscribers +/// +/// # Examples +/// +/// ``` +/// use tracing_subscriber::{ +/// subscribe::{Subscribe, CollectExt}, +/// filter, +/// util::SubscriberInitExt, +/// }; +/// +/// let my_filter = filter::filter_fn(|metadata| { +/// // Only enable spans or events with the target "interesting_things" +/// metadata.target() == "interesting_things" +/// }); +/// +/// let my_subscriber = tracing_subscriber::fmt::subscriber(); +/// +/// tracing_subscriber::registry() +/// .with(my_subscriber.with_filter(my_filter)) +/// .init(); +/// +/// // This event will not be enabled. +/// tracing::warn!("something important but uninteresting happened!"); +/// +/// // This event will be enabled. +/// tracing::debug!(target: "interesting_things", "an interesting minor detail..."); +/// ``` +pub fn filter_fn(f: F) -> FilterFn +where + F: Fn(&Metadata<'_>) -> bool, +{ + FilterFn::new(f) +} + +/// Constructs a [`DynFilterFn`] from a function or closure that returns `true` +/// if a span or event should be enabled within a particular [span context][`Context`]. +/// +/// This is equivalent to calling [`DynFilterFn::new`]. +/// +/// Unlike [`filter_fn`], this function takes a closure or function pointer +/// taking the [`Metadata`] for a span or event *and* the current [`Context`]. +/// This means that a [`DynFilterFn`] can choose whether to enable spans or +/// events based on information about the _current_ span (or its parents). +/// +/// If this is *not* necessary, use [`filter_fn`] instead. +/// +/// The returned [`DynFilterFn`] can be used for both [per-subscriber filtering][plf] +/// (using its [`Filter`] implementation) and [global filtering][global] (using +/// its [`Subscribe`] implementation). +/// +/// See the [documentation on filtering with subscribers][filtering] for details. +/// +/// # Examples +/// +/// ``` +/// use tracing_subscriber::{ +/// subscribe::{Subscribe, CollectExt}, +/// filter, +/// util::SubscriberInitExt, +/// }; +/// +/// // Only enable spans or events within a span named "interesting_span". +/// let my_filter = filter::dynamic_filter_fn(|metadata, cx| { +/// // If this *is* "interesting_span", make sure to enable it. +/// if metadata.is_span() && metadata.name() == "interesting_span" { +/// return true; +/// } +/// +/// // Otherwise, are we in an interesting span? +/// if let Some(current_span) = cx.lookup_current() { +/// return current_span.name() == "interesting_span"; +/// } +/// +/// false +/// }); +/// +/// let my_subscriber = tracing_subscriber::fmt::subscriber(); +/// +/// tracing_subscriber::registry() +/// .with(my_subscriber.with_filter(my_filter)) +/// .init(); +/// +/// // This event will not be enabled. +/// tracing::info!("something happened"); +/// +/// tracing::info_span!("interesting_span").in_scope(|| { +/// // This event will be enabled. +/// tracing::debug!("something else happened"); +/// }); +/// ``` +/// +/// [`Filter`]: crate::subscribe::Filter +/// [`Subscribe`]: crate::subscribe::Subscribe +/// [plf]: crate::subscribe#per-subscriber-filtering +/// [global]: crate::subscribe#global-filtering +/// [filtering]: crate::subscribe#filtering-with-subscribers +/// [`Context`]: crate::subscribe::Context +/// [`Metadata`]: tracing_core::Metadata +pub fn dynamic_filter_fn(f: F) -> DynFilterFn +where + F: Fn(&Metadata<'_>, &Context<'_, C>) -> bool, +{ + DynFilterFn::new(f) +} + +impl FilterFn +where + F: Fn(&Metadata<'_>) -> bool, +{ + /// Constructs a [`FilterFn`] from a function or closure that returns `true` + /// if a span or event should be enabled, based on its [`Metadata`]. + /// + /// If determining whether a span or event should be enabled also requires + /// information about the current span context, use [`DynFilterFn`] instead. + /// + /// See the [documentation on per-subscriber filtering][plf] for details on using + /// [`Filter`]s. + /// + /// [`Filter`]: crate::subscribe::Filter + /// [plf]: crate::subscribe#per-subscriber-filtering + /// [`Metadata`]: tracing_core::Metadata + /// + /// # Examples + /// + /// ``` + /// use tracing_subscriber::{ + /// subscribe::{Subscribe, CollectExt}, + /// filter::FilterFn, + /// util::SubscriberInitExt, + /// }; + /// + /// let my_filter = FilterFn::new(|metadata| { + /// // Only enable spans or events with the target "interesting_things" + /// metadata.target() == "interesting_things" + /// }); + /// + /// let my_subscriber = tracing_subscriber::fmt::subscriber(); + /// + /// tracing_subscriber::registry() + /// .with(my_subscriber.with_filter(my_filter)) + /// .init(); + /// + /// // This event will not be enabled. + /// tracing::warn!("something important but uninteresting happened!"); + /// + /// // This event will be enabled. + /// tracing::debug!(target: "interesting_things", "an interesting minor detail..."); + /// ``` + pub fn new(enabled: F) -> Self { + Self { + enabled, + max_level_hint: None, + } + } + + /// Sets the highest verbosity [`Level`] the filter function will enable. + /// + /// The value passed to this method will be returned by this `FilterFn`'s + /// [`Filter::max_level_hint`] method. + /// + /// If the provided function will not enable all levels, it is recommended + /// to call this method to configure it with the most verbose level it will + /// enable. + /// + /// # Examples + /// + /// ``` + /// use tracing_subscriber::{ + /// subscribe::{Subscribe, CollectExt}, + /// filter::{filter_fn, LevelFilter}, + /// util::SubscriberInitExt, + /// }; + /// use tracing_core::Level; + /// + /// let my_filter = filter_fn(|metadata| { + /// // Only enable spans or events with targets starting with `my_crate` + /// // and levels at or below `INFO`. + /// metadata.level() <= &Level::INFO && metadata.target().starts_with("my_crate") + /// }) + /// // Since the filter closure will only enable the `INFO` level and + /// // below, set the max level hint + /// .with_max_level_hint(LevelFilter::INFO); + /// + /// let my_subscriber = tracing_subscriber::fmt::subscriber(); + /// + /// tracing_subscriber::registry() + /// .with(my_subscriber.with_filter(my_filter)) + /// .init(); + /// ``` + /// + /// [`Level`]: tracing_core::Level + /// [`Filter::max_level_hint`]: crate::subscribe::Filter::max_level_hint + pub fn with_max_level_hint(self, max_level_hint: impl Into) -> Self { + Self { + max_level_hint: Some(max_level_hint.into()), + ..self + } + } + + #[inline] + pub(in crate::filter) fn is_enabled(&self, metadata: &Metadata<'_>) -> bool { + let enabled = (self.enabled)(metadata); + debug_assert!( + !enabled || self.is_below_max_level(metadata), + "FilterFn<{}> claimed it would only enable {:?} and below, \ + but it enabled metadata with the {:?} level\nmetadata={:#?}", + type_name::(), + self.max_level_hint.unwrap(), + metadata.level(), + metadata, + ); + + enabled + } + + #[inline] + pub(in crate::filter) fn is_callsite_enabled( + &self, + metadata: &'static Metadata<'static>, + ) -> Interest { + // Because `self.enabled` takes a `Metadata` only (and no `Context` + // parameter), we can reasonably assume its results are cachable, and + // just return `Interest::always`/`Interest::never`. + if (self.enabled)(metadata) { + debug_assert!( + self.is_below_max_level(metadata), + "FilterFn<{}> claimed it was only interested in {:?} and below, \ + but it enabled metadata with the {:?} level\nmetadata={:#?}", + type_name::(), + self.max_level_hint.unwrap(), + metadata.level(), + metadata, + ); + return Interest::always(); + } + + Interest::never() + } + + fn is_below_max_level(&self, metadata: &Metadata<'_>) -> bool { + self.max_level_hint + .as_ref() + .map(|hint| metadata.level() <= hint) + .unwrap_or(true) + } +} + +#[cfg(feature = "registry")] +#[cfg_attr(docsrs, doc(cfg(feature = "registry")))] +impl Filter for FilterFn +where + F: Fn(&Metadata<'_>) -> bool, +{ + fn enabled(&self, metadata: &Metadata<'_>, _: &Context<'_, C>) -> bool { + self.is_enabled(metadata) + } + + fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest { + self.is_callsite_enabled(metadata) + } + + fn max_level_hint(&self) -> Option { + self.max_level_hint + } +} + +impl Subscribe for FilterFn +where + F: Fn(&Metadata<'_>) -> bool + 'static, + C: Collect, +{ + fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, C>) -> bool { + self.is_enabled(metadata) + } + + fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { + self.is_callsite_enabled(metadata) + } + + fn max_level_hint(&self) -> Option { + self.max_level_hint + } +} + +impl From for FilterFn +where + F: Fn(&Metadata<'_>) -> bool, +{ + fn from(enabled: F) -> Self { + Self::new(enabled) + } +} + +impl fmt::Debug for FilterFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FilterFn") + .field("enabled", &format_args!("{}", type_name::())) + .field("max_level_hint", &self.max_level_hint) + .finish() + } +} + +// === impl DynFilterFn == + +impl DynFilterFn +where + F: Fn(&Metadata<'_>, &Context<'_, C>) -> bool, +{ + /// Constructs a [`Filter`] from a function or closure that returns `true` + /// if a span or event should be enabled in the current [span + /// context][`Context`]. + /// + /// Unlike [`FilterFn`], a `DynFilterFn` is constructed from a closure or + /// function pointer that takes both the [`Metadata`] for a span or event + /// *and* the current [`Context`]. This means that a [`DynFilterFn`] can + /// choose whether to enable spans or events based on information about the + /// _current_ span (or its parents). + /// + /// If this is *not* necessary, use [`FilterFn`] instead. + /// + /// See the [documentation on per-subscriber filtering][plf] for details on using + /// [`Filter`]s. + /// + /// [`Filter`]: crate::subscribe::Filter + /// [plf]: crate::subscribe#per-subscriber-filtering + /// [`Context`]: crate::subscribe::Context + /// [`Metadata`]: tracing_core::Metadata + /// + /// # Examples + /// + /// ``` + /// use tracing_subscriber::{ + /// subscribe::{Subscribe, CollectExt}, + /// filter::DynFilterFn, + /// util::SubscriberInitExt, + /// }; + /// + /// // Only enable spans or events within a span named "interesting_span". + /// let my_filter = DynFilterFn::new(|metadata, cx| { + /// // If this *is* "interesting_span", make sure to enable it. + /// if metadata.is_span() && metadata.name() == "interesting_span" { + /// return true; + /// } + /// + /// // Otherwise, are we in an interesting span? + /// if let Some(current_span) = cx.lookup_current() { + /// return current_span.name() == "interesting_span"; + /// } + /// + /// false + /// }); + /// + /// let my_subscriber = tracing_subscriber::fmt::subscriber(); + /// + /// tracing_subscriber::registry() + /// .with(my_subscriber.with_filter(my_filter)) + /// .init(); + /// + /// // This event will not be enabled. + /// tracing::info!("something happened"); + /// + /// tracing::info_span!("interesting_span").in_scope(|| { + /// // This event will be enabled. + /// tracing::debug!("something else happened"); + /// }); + /// ``` + pub fn new(enabled: F) -> Self { + Self { + enabled, + register_callsite: None, + max_level_hint: None, + _s: PhantomData, + } + } +} + +impl DynFilterFn +where + F: Fn(&Metadata<'_>, &Context<'_, C>) -> bool, +{ + /// Sets the highest verbosity [`Level`] the filter function will enable. + /// + /// The value passed to this method will be returned by this `DynFilterFn`'s + /// [`Filter::max_level_hint`] method. + /// + /// If the provided function will not enable all levels, it is recommended + /// to call this method to configure it with the most verbose level it will + /// enable. + /// + /// # Examples + /// + /// ``` + /// use tracing_subscriber::{ + /// subscribe::{Subscribe, CollectExt}, + /// filter::{DynFilterFn, LevelFilter}, + /// util::SubscriberInitExt, + /// }; + /// use tracing_core::Level; + /// + /// // Only enable spans or events with levels at or below `INFO`, if + /// // we are inside a span called "interesting_span". + /// let my_filter = DynFilterFn::new(|metadata, cx| { + /// // If the level is greater than INFO, disable it. + /// if metadata.level() > &Level::INFO { + /// return false; + /// } + /// + /// // If any span in the current scope is named "interesting_span", + /// // enable this span or event. + /// for span in cx.lookup_current().iter().flat_map(|span| span.scope()) { + /// if span.name() == "interesting_span" { + /// return true; + /// } + /// } + /// + /// // Otherwise, disable it. + /// false + /// }) + /// // Since the filter closure will only enable the `INFO` level and + /// // below, set the max level hint + /// .with_max_level_hint(LevelFilter::INFO); + /// + /// let my_subscriber = tracing_subscriber::fmt::subscriber(); + /// + /// tracing_subscriber::registry() + /// .with(my_subscriber.with_filter(my_filter)) + /// .init(); + /// ``` + /// + /// [`Level`]: tracing_core::Level + /// [`Filter::max_level_hint`]: crate::subscribe::Filter::max_level_hint + pub fn with_max_level_hint(self, max_level_hint: impl Into) -> Self { + Self { + max_level_hint: Some(max_level_hint.into()), + ..self + } + } + + /// Adds a function for filtering callsites to this filter. + /// + /// When this filter's [`Filter::callsite_enabled`][cse] method is called, + /// the provided function will be used rather than the default. + /// + /// By default, `DynFilterFn` assumes that, because the filter _may_ depend + /// dynamically on the current [span context], its result should never be + /// cached. However, some filtering strategies may require dynamic information + /// from the current span context in *some* cases, but are able to make + /// static filtering decisions from [`Metadata`] alone in others. + /// + /// For example, consider the filter given in the example for + /// [`DynFilterFn::new`]. That filter enables all spans named + /// "interesting_span", and any events and spans that occur inside of an + /// interesting span. Since the span's name is part of its static + /// [`Metadata`], the "interesting_span" can be enabled in + /// [`callsite_enabled`][cse]: + /// + /// ``` + /// use tracing_subscriber::{ + /// subscribe::{Subscribe, CollectExt}, + /// filter::DynFilterFn, + /// util::SubscriberInitExt, + /// }; + /// use tracing_core::collect::Interest; + /// + /// // Only enable spans or events within a span named "interesting_span". + /// let my_filter = DynFilterFn::new(|metadata, cx| { + /// // If this *is* "interesting_span", make sure to enable it. + /// if metadata.is_span() && metadata.name() == "interesting_span" { + /// return true; + /// } + /// + /// // Otherwise, are we in an interesting span? + /// if let Some(current_span) = cx.lookup_current() { + /// return current_span.name() == "interesting_span"; + /// } + /// + /// false + /// }).with_callsite_filter(|metadata| { + /// // If this is an "interesting_span", we know we will always + /// // enable it. + /// if metadata.is_span() && metadata.name() == "interesting_span" { + /// return Interest::always(); + /// } + /// + /// // Otherwise, it depends on whether or not we're in an interesting + /// // span. You'll have to ask us again for each span/event! + /// Interest::sometimes() + /// }); + /// + /// let my_subscriber = tracing_subscriber::fmt::subscriber(); + /// + /// tracing_subscriber::registry() + /// .with(my_subscriber.with_filter(my_filter)) + /// .init(); + /// ``` + /// + /// [cse]: crate::subscribe::Filter::callsite_enabled + /// [`enabled`]: crate::subscribe::Filter::enabled + /// [`Metadata`]: tracing_core::Metadata + /// [span context]: crate::subscribe::Context + pub fn with_callsite_filter(self, callsite_enabled: R2) -> DynFilterFn + where + R2: Fn(&'static Metadata<'static>) -> Interest, + { + let register_callsite = Some(callsite_enabled); + let DynFilterFn { + enabled, + max_level_hint, + _s, + .. + } = self; + DynFilterFn { + enabled, + register_callsite, + max_level_hint, + _s, + } + } + + fn default_callsite_enabled(&self, metadata: &Metadata<'_>) -> Interest { + // If it's below the configured max level, assume that `enabled` will + // never enable it... + if !is_below_max_level(&self.max_level_hint, metadata) { + debug_assert!( + !(self.enabled)(metadata, &Context::none()), + "DynFilterFn<{}> claimed it would only enable {:?} and below, \ + but it enabled metadata with the {:?} level\nmetadata={:#?}", + type_name::(), + self.max_level_hint.unwrap(), + metadata.level(), + metadata, + ); + return Interest::never(); + } + + // Otherwise, since this `enabled` function is dynamic and depends on + // the current context, we don't know whether this span or event will be + // enabled or not. Ask again every time it's recorded! + Interest::sometimes() + } +} + +impl DynFilterFn +where + F: Fn(&Metadata<'_>, &Context<'_, C>) -> bool, + R: Fn(&'static Metadata<'static>) -> Interest, +{ + #[inline] + fn is_enabled(&self, metadata: &Metadata<'_>, cx: &Context<'_, C>) -> bool { + let enabled = (self.enabled)(metadata, cx); + debug_assert!( + !enabled || is_below_max_level(&self.max_level_hint, metadata), + "DynFilterFn<{}> claimed it would only enable {:?} and below, \ + but it enabled metadata with the {:?} level\nmetadata={:#?}", + type_name::(), + self.max_level_hint.unwrap(), + metadata.level(), + metadata, + ); + + enabled + } + + #[inline] + fn is_callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest { + let interest = self + .register_callsite + .as_ref() + .map(|callsite_enabled| callsite_enabled(metadata)) + .unwrap_or_else(|| self.default_callsite_enabled(metadata)); + debug_assert!( + interest.is_never() || is_below_max_level(&self.max_level_hint, metadata), + "DynFilterFn<{}, {}> claimed it was only interested in {:?} and below, \ + but it enabled metadata with the {:?} level\nmetadata={:#?}", + type_name::(), + type_name::(), + self.max_level_hint.unwrap(), + metadata.level(), + metadata, + ); + + interest + } +} + +#[cfg(feature = "registry")] +#[cfg_attr(docsrs, doc(cfg(feature = "registry")))] +impl Filter for DynFilterFn +where + F: Fn(&Metadata<'_>, &Context<'_, C>) -> bool, + R: Fn(&'static Metadata<'static>) -> Interest, +{ + fn enabled(&self, metadata: &Metadata<'_>, cx: &Context<'_, C>) -> bool { + self.is_enabled(metadata, cx) + } + + fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest { + self.is_callsite_enabled(metadata) + } + + fn max_level_hint(&self) -> Option { + self.max_level_hint + } +} + +impl Subscribe for DynFilterFn +where + F: Fn(&Metadata<'_>, &Context<'_, C>) -> bool + 'static, + R: Fn(&'static Metadata<'static>) -> Interest + 'static, + C: Collect, +{ + fn enabled(&self, metadata: &Metadata<'_>, cx: Context<'_, C>) -> bool { + self.is_enabled(metadata, &cx) + } + + fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { + self.is_callsite_enabled(metadata) + } + + fn max_level_hint(&self) -> Option { + self.max_level_hint + } +} + +impl fmt::Debug for DynFilterFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = f.debug_struct("DynFilterFn"); + s.field("enabled", &format_args!("{}", type_name::())); + if self.register_callsite.is_some() { + s.field( + "register_callsite", + &format_args!("Some({})", type_name::()), + ); + } else { + s.field("register_callsite", &format_args!("None")); + } + + s.field("max_level_hint", &self.max_level_hint).finish() + } +} + +impl Clone for DynFilterFn +where + F: Clone, + R: Clone, +{ + fn clone(&self) -> Self { + Self { + enabled: self.enabled.clone(), + register_callsite: self.register_callsite.clone(), + max_level_hint: self.max_level_hint, + _s: PhantomData, + } + } +} + +impl From for DynFilterFn +where + F: Fn(&Metadata<'_>, &Context<'_, C>) -> bool, +{ + fn from(f: F) -> Self { + Self::new(f) + } +} + +fn is_below_max_level(hint: &Option, metadata: &Metadata<'_>) -> bool { + hint.as_ref() + .map(|hint| metadata.level() <= hint) + .unwrap_or(true) +} diff --git a/tracing-subscriber/src/filter/mod.rs b/tracing-subscriber/src/filter/mod.rs index c7f839a1d0..fe6b1fda28 100644 --- a/tracing-subscriber/src/filter/mod.rs +++ b/tracing-subscriber/src/filter/mod.rs @@ -10,10 +10,12 @@ //! [`Subscribe`]: crate::subscribe #[cfg(feature = "env-filter")] mod env; +mod filter_fn; mod level; #[cfg(feature = "registry")] mod subscriber_filters; +pub use self::filter_fn::*; #[cfg(not(feature = "registry"))] pub(crate) use self::has_plf_stubs::*; #[cfg(feature = "registry")] diff --git a/tracing-subscriber/src/filter/subscriber_filters/mod.rs b/tracing-subscriber/src/filter/subscriber_filters/mod.rs index fd1c1e3886..989b82f27a 100644 --- a/tracing-subscriber/src/filter/subscriber_filters/mod.rs +++ b/tracing-subscriber/src/filter/subscriber_filters/mod.rs @@ -33,7 +33,7 @@ use crate::{ subscribe::{self, Context, Subscribe}, }; use std::{ - any::{type_name, TypeId}, + any::TypeId, cell::{Cell, RefCell}, fmt, marker::PhantomData, @@ -65,44 +65,6 @@ pub struct Filtered { _s: PhantomData, } -/// A per-subscriber [`Filter`] implemented by a closure or function pointer that -/// determines whether a given span or event is enabled _dynamically_, -/// potentially based on the current [span context]. -/// -/// See the [documentation on per-subscriber filtering][plf] for details. -/// -/// [span context]: crate::subscribe::Context -/// [`Filter`]: crate::subscribe::Filter -/// [plf]: crate::subscribe#per-subscriber-filtering -#[cfg_attr(docsrs, doc(cfg(feature = "registry")))] -pub struct DynFilterFn< - C, - // TODO(eliza): should these just be boxed functions? - F = fn(&Metadata<'_>, &Context<'_, C>) -> bool, - R = fn(&'static Metadata<'static>) -> Interest, -> { - enabled: F, - register_callsite: Option, - max_level_hint: Option, - _s: PhantomData, -} - -/// A per-subscriber [`Filter`] implemented by a closure or function pointer that -/// determines whether a given span or event is enabled, based on its -/// [`Metadata`]. -/// -/// See the [documentation on per-subscriber filtering][plf] for details. -/// -/// [`Metadata`]: tracing_core::Metadata -/// [`Filter`]: crate::subscribe::Filter -/// [plf]: crate::subscribe#per-subscriber-filtering -#[cfg_attr(docsrs, doc(cfg(feature = "registry")))] -#[derive(Clone)] -pub struct FilterFn) -> bool> { - enabled: F, - max_level_hint: Option, -} - /// Uniquely identifies an individual [`Filter`] instance in the context of /// a [collector]. /// @@ -700,610 +662,6 @@ where } } -// === impl FilterFn === - -/// Constructs a [`FilterFn`], which implements the [`Filter`] trait, from -/// a function or closure that returns `true` if a span or event should be enabled. -/// -/// This is equivalent to calling [`FilterFn::new`]. -/// -/// See the [documentation on per-subscriber filtering][plf] for details on using -/// [`Filter`]s. -/// -/// [`Filter`]: crate::subscribe::Filter -/// [plf]: crate::subscribe#per-subscriber-filtering -/// -/// # Examples -/// -/// ``` -/// use tracing_subscriber::{ -/// subscribe::{Subscribe, CollectExt}, -/// filter, -/// util::SubscriberInitExt, -/// }; -/// -/// let my_filter = filter::filter_fn(|metadata| { -/// // Only enable spans or events with the target "interesting_things" -/// metadata.target() == "interesting_things" -/// }); -/// -/// let my_subscriber = tracing_subscriber::fmt::subscriber(); -/// -/// tracing_subscriber::registry() -/// .with(my_subscriber.with_filter(my_filter)) -/// .init(); -/// -/// // This event will not be enabled. -/// tracing::warn!("something important but uninteresting happened!"); -/// -/// // This event will be enabled. -/// tracing::debug!(target: "interesting_things", "an interesting minor detail..."); -/// ``` -#[cfg_attr(docsrs, doc(cfg(feature = "registry")))] -pub fn filter_fn(f: F) -> FilterFn -where - F: Fn(&Metadata<'_>) -> bool, -{ - FilterFn::new(f) -} - -/// Constructs a [`DynFilterFn`], which implements the [`Filter`] trait, from -/// a function or closure that returns `true` if a span or event should be -/// enabled within a particular [span context][`Context`]. -/// -/// This is equivalent to calling [`DynFilterFn::new`]. -/// -/// Unlike [`filter_fn`], this function takes a closure or function pointer -/// taking the [`Metadata`] for a span or event *and* the current [`Context`]. -/// This means that a [`DynFilterFn`] can choose whether to enable spans or -/// events based on information about the _current_ span (or its parents). -/// -/// If this is *not* necessary, use [`filter_fn`] instead. -/// -/// See the [documentation on per-subscriber filtering][plf] for details on using -/// [`Filter`]s. -/// -/// # Examples -/// -/// ``` -/// use tracing_subscriber::{ -/// subscribe::{Subscribe, CollectExt}, -/// filter, -/// util::SubscriberInitExt, -/// }; -/// -/// // Only enable spans or events within a span named "interesting_span". -/// let my_filter = filter::dynamic_filter_fn(|metadata, cx| { -/// // If this *is* "interesting_span", make sure to enable it. -/// if metadata.is_span() && metadata.name() == "interesting_span" { -/// return true; -/// } -/// -/// // Otherwise, are we in an interesting span? -/// if let Some(current_span) = cx.lookup_current() { -/// return current_span.name() == "interesting_span"; -/// } -/// -/// false -/// }); -/// -/// let my_subscriber = tracing_subscriber::fmt::subscriber(); -/// -/// tracing_subscriber::registry() -/// .with(my_subscriber.with_filter(my_filter)) -/// .init(); -/// -/// // This event will not be enabled. -/// tracing::info!("something happened"); -/// -/// tracing::info_span!("interesting_span").in_scope(|| { -/// // This event will be enabled. -/// tracing::debug!("something else happened"); -/// }); -/// ``` -/// -/// [`Filter`]: crate::subscribe::Filter -/// [plf]: crate::subscribe#per-subscriber-filtering -/// [`Context`]: crate::subscribe::Context -/// [`Metadata`]: tracing_core::Metadata -#[cfg_attr(docsrs, doc(cfg(feature = "registry")))] -pub fn dynamic_filter_fn(f: F) -> DynFilterFn -where - F: Fn(&Metadata<'_>, &Context<'_, S>) -> bool, -{ - DynFilterFn::new(f) -} - -impl FilterFn -where - F: Fn(&Metadata<'_>) -> bool, -{ - /// Constructs a [`Filter`] from a function or closure that returns `true` - /// if a span or event should be enabled, based on its [`Metadata`]. - /// - /// If determining whether a span or event should be enabled also requires - /// information about the current span context, use [`DynFilterFn`] instead. - /// - /// See the [documentation on per-subscriber filtering][plf] for details on using - /// [`Filter`]s. - /// - /// [`Filter`]: crate::subscribe::Filter - /// [plf]: crate::subscribe#per-subscriber-filtering - /// [`Metadata`]: tracing_core::Metadata - /// - /// # Examples - /// - /// ``` - /// use tracing_subscriber::{ - /// subscribe::{Subscribe, CollectExt}, - /// filter::FilterFn, - /// util::SubscriberInitExt, - /// }; - /// - /// let my_filter = FilterFn::new(|metadata| { - /// // Only enable spans or events with the target "interesting_things" - /// metadata.target() == "interesting_things" - /// }); - /// - /// let my_subscriber = tracing_subscriber::fmt::subscriber(); - /// - /// tracing_subscriber::registry() - /// .with(my_subscriber.with_filter(my_filter)) - /// .init(); - /// - /// // This event will not be enabled. - /// tracing::warn!("something important but uninteresting happened!"); - /// - /// // This event will be enabled. - /// tracing::debug!(target: "interesting_things", "an interesting minor detail..."); - /// ``` - pub fn new(enabled: F) -> Self { - Self { - enabled, - max_level_hint: None, - } - } - - /// Sets the highest verbosity [`Level`] the filter function will enable. - /// - /// The value passed to this method will be returned by this `FilterFn`'s - /// [`Filter::max_level_hint`] method. - /// - /// If the provided function will not enable all levels, it is recommended - /// to call this method to configure it with the most verbose level it will - /// enable. - /// - /// # Examples - /// - /// ``` - /// use tracing_subscriber::{ - /// subscribe::{Subscribe, CollectExt}, - /// filter::{filter_fn, LevelFilter}, - /// util::SubscriberInitExt, - /// }; - /// use tracing_core::Level; - /// - /// let my_filter = filter_fn(|metadata| { - /// // Only enable spans or events with targets starting with `my_crate` - /// // and levels at or below `INFO`. - /// metadata.level() <= &Level::INFO && metadata.target().starts_with("my_crate") - /// }) - /// // Since the filter closure will only enable the `INFO` level and - /// // below, set the max level hint - /// .with_max_level_hint(LevelFilter::INFO); - /// - /// let my_subscriber = tracing_subscriber::fmt::subscriber(); - /// - /// tracing_subscriber::registry() - /// .with(my_subscriber.with_filter(my_filter)) - /// .init(); - /// ``` - /// - /// [`Level`]: tracing_core::Level - /// [`Filter::max_level_hint`]: crate::subscribe::Filter::max_level_hint - pub fn with_max_level_hint(self, max_level_hint: impl Into) -> Self { - Self { - max_level_hint: Some(max_level_hint.into()), - ..self - } - } - - fn is_below_max_level(&self, metadata: &Metadata<'_>) -> bool { - self.max_level_hint - .as_ref() - .map(|hint| metadata.level() <= hint) - .unwrap_or(true) - } -} - -impl subscribe::Filter for FilterFn -where - F: Fn(&Metadata<'_>) -> bool, -{ - fn enabled(&self, metadata: &Metadata<'_>, _: &Context<'_, S>) -> bool { - let enabled = (self.enabled)(metadata); - debug_assert!( - !enabled || self.is_below_max_level(metadata), - "FilterFn<{}> claimed it would only enable {:?} and below, \ - but it enabled metadata with the {:?} level\nmetadata={:#?}", - type_name::(), - self.max_level_hint.unwrap(), - metadata.level(), - metadata, - ); - - enabled - } - - fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest { - // Because `self.enabled` takes a `Metadata` only (and no `Context` - // parameter), we can reasonably assume its results are cachable, and - // just return `Interest::always`/`Interest::never`. - if (self.enabled)(metadata) { - debug_assert!( - self.is_below_max_level(metadata), - "FilterFn<{}> claimed it was only interested in {:?} and below, \ - but it enabled metadata with the {:?} level\nmetadata={:#?}", - type_name::(), - self.max_level_hint.unwrap(), - metadata.level(), - metadata, - ); - return Interest::always(); - } - - Interest::never() - } - - fn max_level_hint(&self) -> Option { - self.max_level_hint - } -} - -impl From for FilterFn -where - F: Fn(&Metadata<'_>) -> bool, -{ - fn from(enabled: F) -> Self { - Self::new(enabled) - } -} - -impl fmt::Debug for FilterFn { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FilterFn") - .field("enabled", &format_args!("{}", type_name::())) - .field("max_level_hint", &self.max_level_hint) - .finish() - } -} - -// === impl DynFilterFn == - -impl DynFilterFn -where - F: Fn(&Metadata<'_>, &Context<'_, S>) -> bool, -{ - /// Constructs a [`Filter`] from a function or closure that returns `true` - /// if a span or event should be enabled in the current [span - /// context][`Context`]. - /// - /// Unlike [`FilterFn`], a `DynFilterFn` is constructed from a closure or - /// function pointer that takes both the [`Metadata`] for a span or event - /// *and* the current [`Context`]. This means that a [`DynFilterFn`] can - /// choose whether to enable spans or events based on information about the - /// _current_ span (or its parents). - /// - /// If this is *not* necessary, use [`FilterFn`] instead. - /// - /// See the [documentation on per-subscriber filtering][plf] for details on using - /// [`Filter`]s. - /// - /// [`Filter`]: crate::subscribe::Filter - /// [plf]: crate::subscribe#per-subscriber-filtering - /// [`Context`]: crate::subscribe::Context - /// [`Metadata`]: tracing_core::Metadata - /// - /// # Examples - /// - /// ``` - /// use tracing_subscriber::{ - /// subscribe::{Subscribe, CollectExt}, - /// filter::DynFilterFn, - /// util::SubscriberInitExt, - /// }; - /// - /// // Only enable spans or events within a span named "interesting_span". - /// let my_filter = DynFilterFn::new(|metadata, cx| { - /// // If this *is* "interesting_span", make sure to enable it. - /// if metadata.is_span() && metadata.name() == "interesting_span" { - /// return true; - /// } - /// - /// // Otherwise, are we in an interesting span? - /// if let Some(current_span) = cx.lookup_current() { - /// return current_span.name() == "interesting_span"; - /// } - /// - /// false - /// }); - /// - /// let my_subscriber = tracing_subscriber::fmt::subscriber(); - /// - /// tracing_subscriber::registry() - /// .with(my_subscriber.with_filter(my_filter)) - /// .init(); - /// - /// // This event will not be enabled. - /// tracing::info!("something happened"); - /// - /// tracing::info_span!("interesting_span").in_scope(|| { - /// // This event will be enabled. - /// tracing::debug!("something else happened"); - /// }); - /// ``` - pub fn new(enabled: F) -> Self { - Self { - enabled, - register_callsite: None, - max_level_hint: None, - _s: PhantomData, - } - } -} - -impl DynFilterFn -where - F: Fn(&Metadata<'_>, &Context<'_, S>) -> bool, -{ - /// Sets the highest verbosity [`Level`] the filter function will enable. - /// - /// The value passed to this method will be returned by this `DynFilterFn`'s - /// [`Filter::max_level_hint`] method. - /// - /// If the provided function will not enable all levels, it is recommended - /// to call this method to configure it with the most verbose level it will - /// enable. - /// - /// # Examples - /// - /// ``` - /// use tracing_subscriber::{ - /// subscribe::{Subscribe, CollectExt}, - /// filter::{DynFilterFn, LevelFilter}, - /// util::SubscriberInitExt, - /// }; - /// use tracing_core::Level; - /// - /// // Only enable spans or events with levels at or below `INFO`, if - /// // we are inside a span called "interesting_span". - /// let my_filter = DynFilterFn::new(|metadata, cx| { - /// // If the level is greater than INFO, disable it. - /// if metadata.level() > &Level::INFO { - /// return false; - /// } - /// - /// // If any span in the current scope is named "interesting_span", - /// // enable this span or event. - /// for span in cx.lookup_current().iter().flat_map(|span| span.scope()) { - /// if span.name() == "interesting_span" { - /// return true; - /// } - /// } - /// - /// // Otherwise, disable it. - /// false - /// }) - /// // Since the filter closure will only enable the `INFO` level and - /// // below, set the max level hint - /// .with_max_level_hint(LevelFilter::INFO); - /// - /// let my_subscriber = tracing_subscriber::fmt::subscriber(); - /// - /// tracing_subscriber::registry() - /// .with(my_subscriber.with_filter(my_filter)) - /// .init(); - /// ``` - /// - /// [`Level`]: tracing_core::Level - /// [`Filter::max_level_hint`]: crate::subscribe::Filter::max_level_hint - pub fn with_max_level_hint(self, max_level_hint: impl Into) -> Self { - Self { - max_level_hint: Some(max_level_hint.into()), - ..self - } - } - - /// Adds a function for filtering callsites to this filter. - /// - /// When this filter's [`Filter::callsite_enabled`][cse] method is called, - /// the provided function will be used rather than the default. - /// - /// By default, `DynFilterFn` assumes that, because the filter _may_ depend - /// dynamically on the current [span context], its result should never be - /// cached. However, some filtering strategies may require dynamic information - /// from the current span context in *some* cases, but are able to make - /// static filtering decisions from [`Metadata`] alone in others. - /// - /// For example, consider the filter given in the example for - /// [`DynFilterFn::new`]. That filter enables all spans named - /// "interesting_span", and any events and spans that occur inside of an - /// interesting span. Since the span's name is part of its static - /// [`Metadata`], the "interesting_span" can be enabled in - /// [`callsite_enabled`][cse]: - /// - /// ``` - /// use tracing_subscriber::{ - /// subscribe::{Subscribe, CollectExt}, - /// filter::DynFilterFn, - /// util::SubscriberInitExt, - /// }; - /// use tracing_core::collect::Interest; - /// - /// // Only enable spans or events within a span named "interesting_span". - /// let my_filter = DynFilterFn::new(|metadata, cx| { - /// // If this *is* "interesting_span", make sure to enable it. - /// if metadata.is_span() && metadata.name() == "interesting_span" { - /// return true; - /// } - /// - /// // Otherwise, are we in an interesting span? - /// if let Some(current_span) = cx.lookup_current() { - /// return current_span.name() == "interesting_span"; - /// } - /// - /// false - /// }).with_callsite_filter(|metadata| { - /// // If this is an "interesting_span", we know we will always - /// // enable it. - /// if metadata.is_span() && metadata.name() == "interesting_span" { - /// return Interest::always(); - /// } - /// - /// // Otherwise, it depends on whether or not we're in an interesting - /// // span. You'll have to ask us again for each span/event! - /// Interest::sometimes() - /// }); - /// - /// let my_subscriber = tracing_subscriber::fmt::subscriber(); - /// - /// tracing_subscriber::registry() - /// .with(my_subscriber.with_filter(my_filter)) - /// .init(); - /// ``` - /// - /// [cse]: crate::subscribe::Filter::callsite_enabled - /// [`enabled`]: crate::subscribe::Filter::enabled - /// [`Metadata`]: tracing_core::Metadata - /// [span context]: crate::subscribe::Context - pub fn with_callsite_filter(self, callsite_enabled: R2) -> DynFilterFn - where - R2: Fn(&'static Metadata<'static>) -> Interest, - { - let register_callsite = Some(callsite_enabled); - let DynFilterFn { - enabled, - max_level_hint, - _s, - .. - } = self; - DynFilterFn { - enabled, - register_callsite, - max_level_hint, - _s, - } - } - - fn default_callsite_enabled(&self, metadata: &Metadata<'_>) -> Interest { - // If it's below the configured max level, assume that `enabled` will - // never enable it... - if !is_below_max_level(&self.max_level_hint, metadata) { - debug_assert!( - !(self.enabled)(metadata, &Context::none()), - "DynFilterFn<{}> claimed it would only enable {:?} and below, \ - but it enabled metadata with the {:?} level\nmetadata={:#?}", - type_name::(), - self.max_level_hint.unwrap(), - metadata.level(), - metadata, - ); - return Interest::never(); - } - - // Otherwise, since this `enabled` function is dynamic and depends on - // the current context, we don't know whether this span or event will be - // enabled or not. Ask again every time it's recorded! - Interest::sometimes() - } -} - -impl subscribe::Filter for DynFilterFn -where - F: Fn(&Metadata<'_>, &Context<'_, S>) -> bool, - R: Fn(&'static Metadata<'static>) -> Interest, -{ - fn enabled(&self, metadata: &Metadata<'_>, cx: &Context<'_, S>) -> bool { - let enabled = (self.enabled)(metadata, cx); - debug_assert!( - !enabled || is_below_max_level(&self.max_level_hint, metadata), - "DynFilterFn<{}> claimed it would only enable {:?} and below, \ - but it enabled metadata with the {:?} level\nmetadata={:#?}", - type_name::(), - self.max_level_hint.unwrap(), - metadata.level(), - metadata, - ); - - enabled - } - - fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest { - let interest = self - .register_callsite - .as_ref() - .map(|callsite_enabled| callsite_enabled(metadata)) - .unwrap_or_else(|| self.default_callsite_enabled(metadata)); - debug_assert!( - interest.is_never() || is_below_max_level(&self.max_level_hint, metadata), - "DynFilterFn<{}, {}> claimed it was only interested in {:?} and below, \ - but it enabled metadata with the {:?} level\nmetadata={:#?}", - type_name::(), - type_name::(), - self.max_level_hint.unwrap(), - metadata.level(), - metadata, - ); - - interest - } - - fn max_level_hint(&self) -> Option { - self.max_level_hint - } -} - -impl fmt::Debug for DynFilterFn { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut s = f.debug_struct("DynFilterFn"); - s.field("enabled", &format_args!("{}", type_name::())); - if self.register_callsite.is_some() { - s.field( - "register_callsite", - &format_args!("Some({})", type_name::()), - ); - } else { - s.field("register_callsite", &format_args!("None")); - } - - s.field("max_level_hint", &self.max_level_hint).finish() - } -} - -impl Clone for DynFilterFn -where - F: Clone, - R: Clone, -{ - fn clone(&self) -> Self { - Self { - enabled: self.enabled.clone(), - register_callsite: self.register_callsite.clone(), - max_level_hint: self.max_level_hint, - _s: PhantomData, - } - } -} - -impl From for DynFilterFn -where - F: Fn(&Metadata<'_>, &Context<'_, S>) -> bool, -{ - fn from(f: F) -> Self { - Self::new(f) - } -} - // === impl FilterId === impl FilterId { @@ -1725,9 +1083,3 @@ impl fmt::Debug for FmtBitset { set.finish() } } - -fn is_below_max_level(hint: &Option, metadata: &Metadata<'_>) -> bool { - hint.as_ref() - .map(|hint| metadata.level() <= hint) - .unwrap_or(true) -}