diff --git a/tracing-subscriber/src/filter/subscriber_filters/combinator.rs b/tracing-subscriber/src/filter/subscriber_filters/combinator.rs new file mode 100644 index 0000000000..55bd250a42 --- /dev/null +++ b/tracing-subscriber/src/filter/subscriber_filters/combinator.rs @@ -0,0 +1,380 @@ +//! Filter combinators +use crate::subscribe::{Context, Filter}; +use std::{cmp, fmt, marker::PhantomData}; +use tracing_core::{collect::Interest, LevelFilter, Metadata}; + +/// Combines two [`Filter`]s so that spans and events are enabled if and only if +/// *both* filters return `true`. +/// +/// This type is typically returned by the [`FilterExt::and`] method. See that +/// method's documentation for details. +/// +/// [`Filter`]: crate::subscribe::Filter +/// [`FilterExt::and`]: crate::filter::FilterExt::and +pub struct And { + a: A, + b: B, + _s: PhantomData, +} + +/// Combines two [`Filter`]s so that spans and events are enabled if *either* filter +/// returns `true`. +/// +/// This type is typically returned by the [`FilterExt::or`] method. See that +/// method's documentation for details. +/// +/// [`Filter`]: crate::subscribe::Filter +/// [`FilterExt::or`]: crate::filter::FilterExt::or +pub struct Or { + a: A, + b: B, + _s: PhantomData, +} + +/// Inverts the result of a [`Filter`]. +/// +/// If the wrapped filter would enable a span or event, it will be disabled. If +/// it would disable a span or event, that span or event will be enabled. +/// +/// This type is typically returned by the [`FilterExt::or`] method. See that +/// method's documentation for details. +/// +/// [`Filter`]: crate::subscribe::Filter +/// [`FilterExt::or`]: crate::filter::FilterExt::or +pub struct Not { + a: A, + _s: PhantomData, +} + +// === impl And === + +impl And +where + A: Filter, + B: Filter, +{ + /// Combines two [`Filter`]s so that spans and events are enabled if and only if + /// *both* filters return `true`. + /// + /// # Examples + /// + /// Enabling spans or events if they have both a particular target *and* are + /// above a certain level: + /// + /// ```ignore + /// use tracing_subscriber::{ + /// filter::{filter_fn, LevelFilter, combinator::And}, + /// prelude::*, + /// }; + /// + /// // Enables spans and events with targets starting with `interesting_target`: + /// let target_filter = filter_fn(|meta| { + /// meta.target().starts_with("interesting_target") + /// }); + /// + /// // Enables spans and events with levels `INFO` and below: + /// let level_filter = LevelFilter::INFO; + /// + /// // Combine the two filters together so that a span or event is only enabled + /// // if *both* filters would enable it: + /// let filter = And::new(level_filter, target_filter); + /// + /// tracing_subscriber::registry() + /// .with(tracing_subscriber::fmt::subscriber().with_filter(filter)) + /// .init(); + /// + /// // This event will *not* be enabled: + /// tracing::info!("an event with an uninteresting target"); + /// + /// // This event *will* be enabled: + /// tracing::info!(target: "interesting_target", "a very interesting event"); + /// + /// // This event will *not* be enabled: + /// tracing::debug!(target: "interesting_target", "interesting debug event..."); + /// ``` + /// + /// [`Filter`]: crate::subscribe::Filter + pub(crate) fn new(a: A, b: B) -> Self { + Self { + a, + b, + _s: PhantomData, + } + } +} + +impl Filter for And +where + A: Filter, + B: Filter, +{ + #[inline] + fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool { + self.a.enabled(meta, cx) && self.b.enabled(meta, cx) + } + + fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest { + let a = self.a.callsite_enabled(meta); + if a.is_never() { + return a; + } + + let b = self.b.callsite_enabled(meta); + + if !b.is_always() { + return b; + } + + a + } + + fn max_level_hint(&self) -> Option { + // If either hint is `None`, return `None`. Otherwise, return the most restrictive. + cmp::min(self.a.max_level_hint(), self.b.max_level_hint()) + } +} + +impl Clone for And +where + A: Clone, + B: Clone, +{ + fn clone(&self) -> Self { + Self { + a: self.a.clone(), + b: self.b.clone(), + _s: PhantomData, + } + } +} + +impl fmt::Debug for And +where + A: fmt::Debug, + B: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("And") + .field("a", &self.a) + .field("b", &self.b) + .finish() + } +} + +// === impl Or === + +impl Or +where + A: Filter, + B: Filter, +{ + /// Combines two [`Filter`]s so that spans and events are enabled if *either* filter + /// returns `true`. + /// + /// # Examples + /// + /// Enabling spans and events at the `INFO` level and above, and all spans + /// and events with a particular target: + /// + /// ```ignore + /// use tracing_subscriber::{ + /// filter::{filter_fn, LevelFilter, combinator::Or}, + /// prelude::*, + /// }; + /// + /// // Enables spans and events with targets starting with `interesting_target`: + /// let target_filter = filter_fn(|meta| { + /// meta.target().starts_with("interesting_target") + /// }); + /// + /// // Enables spans and events with levels `INFO` and below: + /// let level_filter = LevelFilter::INFO; + /// + /// // Combine the two filters together so that a span or event is enabled + /// // if it is at INFO or lower, or if it has a target starting with + /// // `interesting_target`. + /// let filter = Or::new(level_filter, target_filter); + /// + /// tracing_subscriber::registry() + /// .with(tracing_subscriber::fmt::subscriber().with_filter(filter)) + /// .init(); + /// + /// // This event will *not* be enabled: + /// tracing::debug!("an uninteresting event"); + /// + /// // This event *will* be enabled: + /// tracing::info!("an uninteresting INFO event"); + /// + /// // This event *will* be enabled: + /// tracing::info!(target: "interesting_target", "a very interesting event"); + /// + /// // This event *will* be enabled: + /// tracing::debug!(target: "interesting_target", "interesting debug event..."); + /// ``` + /// + /// Enabling a higher level for a particular target by using `Or` in + /// conjunction with the [`And`] combinator: + /// + /// ```ignore + /// use tracing_subscriber::{ + /// filter::{filter_fn, LevelFilter, combinator}, + /// prelude::*, + /// }; + /// + /// // This filter will enable spans and events with targets beginning with + /// // `my_crate`: + /// let my_crate = filter_fn(|meta| { + /// meta.target().starts_with("my_crate") + /// }); + /// + /// // Combine the `my_crate` filter with a `LevelFilter` to produce a filter + /// // that will enable the `INFO` level and lower for spans and events with + /// // `my_crate` targets: + /// let filter = combinator::And::new(my_crate, LevelFilter::INFO); + /// + /// // If a span or event *doesn't* have a target beginning with + /// // `my_crate`, enable it if it has the `WARN` level or lower: + /// // let filter = combinator::Or::new(filter, LevelFilter::WARN); + /// + /// tracing_subscriber::registry() + /// .with(tracing_subscriber::fmt::subscriber().with_filter(filter)) + /// .init(); + /// ``` + /// + /// [`Filter`]: crate::subscribe::Filter + pub(crate) fn new(a: A, b: B) -> Self { + Self { + a, + b, + _s: PhantomData, + } + } +} + +impl Filter for Or +where + A: Filter, + B: Filter, +{ + #[inline] + fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool { + self.a.enabled(meta, cx) || self.b.enabled(meta, cx) + } + + fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest { + let a = self.a.callsite_enabled(meta); + let b = self.b.callsite_enabled(meta); + + // If either filter will always enable the span or event, return `always`. + if a.is_always() || b.is_always() { + return Interest::always(); + } + + // Okay, if either filter will sometimes enable the span or event, + // return `sometimes`. + if a.is_sometimes() || b.is_sometimes() { + return Interest::sometimes(); + } + + debug_assert!( + a.is_never() && b.is_never(), + "if neither filter was `always` or `sometimes`, both must be `never` (a={:?}; b={:?})", + a, + b, + ); + Interest::never() + } + + fn max_level_hint(&self) -> Option { + // If either hint is `None`, return `None`. Otherwise, return the less restrictive. + Some(cmp::max(self.a.max_level_hint()?, self.b.max_level_hint()?)) + } +} + +impl Clone for Or +where + A: Clone, + B: Clone, +{ + fn clone(&self) -> Self { + Self { + a: self.a.clone(), + b: self.b.clone(), + _s: PhantomData, + } + } +} + +impl fmt::Debug for Or +where + A: fmt::Debug, + B: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Or") + .field("a", &self.a) + .field("b", &self.b) + .finish() + } +} + +// === impl Not === + +impl Not +where + A: Filter, +{ + /// Inverts the result of a [`Filter`]. + /// + /// If the wrapped filter would enable a span or event, it will be disabled. If + /// it would disable a span or event, that span or event will be enabled. + /// + /// [`Filter`]: crate::subscribe::Filter + pub(crate) fn new(a: A) -> Self { + Self { a, _s: PhantomData } + } +} + +impl Filter for Not +where + A: Filter, +{ + #[inline] + fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool { + !self.a.enabled(meta, cx) + } + + fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest { + match self.a.callsite_enabled(meta) { + i if i.is_always() => Interest::never(), + i if i.is_never() => Interest::always(), + _ => Interest::sometimes(), + } + } + + fn max_level_hint(&self) -> Option { + // TODO(eliza): figure this out??? + None + } +} + +impl Clone for Not +where + A: Clone, +{ + fn clone(&self) -> Self { + Self { + a: self.a.clone(), + _s: PhantomData, + } + } +} + +impl fmt::Debug for Not +where + A: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Not").field(&self.a).finish() + } +} diff --git a/tracing-subscriber/src/filter/subscriber_filters.rs b/tracing-subscriber/src/filter/subscriber_filters/mod.rs similarity index 82% rename from tracing-subscriber/src/filter/subscriber_filters.rs rename to tracing-subscriber/src/filter/subscriber_filters/mod.rs index 356d0709a6..9af45781bc 100644 --- a/tracing-subscriber/src/filter/subscriber_filters.rs +++ b/tracing-subscriber/src/filter/subscriber_filters/mod.rs @@ -37,6 +37,7 @@ use std::{ cell::{Cell, RefCell}, fmt, marker::PhantomData, + ops::Deref, ptr::NonNull, sync::Arc, thread_local, @@ -45,6 +46,7 @@ use tracing_core::{ collect::{Collect, Interest}, span, Event, Metadata, }; +pub mod combinator; /// A [`Subscribe`] that wraps an inner [`Subscribe`] and adds a [`Filter`] which /// controls what spans and events are enabled for that subscriber. @@ -104,7 +106,7 @@ pub struct FilterFn) -> bool> { /// Uniquely identifies an individual [`Filter`] instance in the context of /// a [collector]. /// -/// When adding a [`Filtered`] [`Subscribe`] to a [collector], the [collector]] +/// When adding a [`Filtered`] [`Subscribe`] to a [collector], the [collector] /// generates a `FilterId` for that [`Filtered`] subscriber. The [`Filtered`] subscriber /// will then use the generated ID to query whether a particular span was /// previously enabled by that subscriber's [`Filter`]. @@ -196,7 +198,231 @@ thread_local! { pub(crate) static FILTERING: FilterState = FilterState::new(); } +/// Extension trait adding [combinators] for combining [`Filter`]. +/// +/// [combinators]: crate::filter::combinator +/// [`Filter`]: crate::subscribe::Filter +pub trait FilterExt: subscribe::Filter { + /// Combines this [`Filter`] with another [`Filter`] s so that spans and + /// events are enabled if and only if *both* filters return `true`. + /// + /// # Examples + /// + /// Enabling spans or events if they have both a particular target *and* are + /// above a certain level: + /// + /// ``` + /// use tracing_subscriber::{ + /// filter::{filter_fn, LevelFilter, FilterExt}, + /// prelude::*, + /// }; + /// + /// // Enables spans and events with targets starting with `interesting_target`: + /// let target_filter = filter_fn(|meta| { + /// meta.target().starts_with("interesting_target") + /// }); + /// + /// // Enables spans and events with levels `INFO` and below: + /// let level_filter = LevelFilter::INFO; + /// + /// // Combine the two filters together, returning a filter that only enables + /// // spans and events that *both* filters will enable: + /// let filter = target_filter.and(level_filter); + /// + /// tracing_subscriber::registry() + /// .with(tracing_subscriber::fmt::subscriber().with_filter(filter)) + /// .init(); + /// + /// // This event will *not* be enabled: + /// tracing::info!("an event with an uninteresting target"); + /// + /// // This event *will* be enabled: + /// tracing::info!(target: "interesting_target", "a very interesting event"); + /// + /// // This event will *not* be enabled: + /// tracing::debug!(target: "interesting_target", "interesting debug event..."); + /// ``` + /// + /// [`Filter`]: crate::subscribe::Filter + fn and(self, other: B) -> combinator::And + where + Self: Sized, + B: subscribe::Filter, + { + combinator::And::new(self, other) + } + + /// Combines two [`Filter`]s so that spans and events are enabled if *either* filter + /// returns `true`. + /// + /// # Examples + /// + /// Enabling spans and events at the `INFO` level and above, and all spans + /// and events with a particular target: + /// ``` + /// use tracing_subscriber::{ + /// filter::{filter_fn, LevelFilter, FilterExt}, + /// prelude::*, + /// }; + /// + /// // Enables spans and events with targets starting with `interesting_target`: + /// let target_filter = filter_fn(|meta| { + /// meta.target().starts_with("interesting_target") + /// }); + /// + /// // Enables spans and events with levels `INFO` and below: + /// let level_filter = LevelFilter::INFO; + /// + /// // Combine the two filters together so that a span or event is enabled + /// // if it is at INFO or lower, or if it has a target starting with + /// // `interesting_target`. + /// let filter = level_filter.or(target_filter); + /// + /// tracing_subscriber::registry() + /// .with(tracing_subscriber::fmt::subscriber().with_filter(filter)) + /// .init(); + /// + /// // This event will *not* be enabled: + /// tracing::debug!("an uninteresting event"); + /// + /// // This event *will* be enabled: + /// tracing::info!("an uninteresting INFO event"); + /// + /// // This event *will* be enabled: + /// tracing::info!(target: "interesting_target", "a very interesting event"); + /// + /// // This event *will* be enabled: + /// tracing::debug!(target: "interesting_target", "interesting debug event..."); + /// ``` + /// + /// Enabling a higher level for a particular target by using `or` in + /// conjunction with the [`and`] combinator: + /// + /// ``` + /// use tracing_subscriber::{ + /// filter::{filter_fn, LevelFilter, FilterExt}, + /// prelude::*, + /// }; + /// + /// // This filter will enable spans and events with targets beginning with + /// // `my_crate`: + /// let my_crate = filter_fn(|meta| { + /// meta.target().starts_with("my_crate") + /// }); + /// + /// let filter = my_crate + /// // Combine the `my_crate` filter with a `LevelFilter` to produce a + /// // filter that will enable the `INFO` level and lower for spans and + /// // events with `my_crate` targets: + /// .and(LevelFilter::INFO) + /// // If a span or event *doesn't* have a target beginning with + /// // `my_crate`, enable it if it has the `WARN` level or lower: + /// .or(LevelFilter::WARN); + /// + /// tracing_subscriber::registry() + /// .with(tracing_subscriber::fmt::subscriber().with_filter(filter)) + /// .init(); + /// ``` + /// + /// [`Filter`]: crate::subscribe::Filter + /// [`and`]: FilterExt::and + fn or(self, other: B) -> combinator::Or + where + Self: Sized, + B: subscribe::Filter, + { + combinator::Or::new(self, other) + } + + /// Inverts `self`, returning a filter that enables spans and events only if + /// `self` would *not* enable them. + fn not(self) -> combinator::Not + where + Self: Sized, + { + combinator::Not::new(self) + } + + /// [Boxes] `self`, erasing its concrete type. + /// + /// This is equivalent to calling [`Box::new`], but in method form, so that + /// it can be used when chaining combinator methods. + /// + /// # Examples + /// + /// When different combinations of filters are used conditionally, they may + /// have different types. For example, the following code won't compile, + /// since the `if` and `else` clause produce filters of different types: + /// + /// ```compile_fail + /// use tracing_subscriber::{ + /// filter::{filter_fn, LevelFilter, FilterExt}, + /// prelude::*, + /// }; + /// + /// let enable_bar_target: bool = // ... + /// # false; + /// + /// let filter = if enable_bar_target { + /// filter_fn(|meta| meta.target().starts_with("foo")) + /// // If `enable_bar_target` is true, add a `filter_fn` enabling + /// // spans and events with the target `bar`: + /// .or(filter_fn(|meta| meta.target().starts_with("bar"))) + /// .and(LevelFilter::INFO) + /// } else { + /// filter_fn(|meta| meta.target().starts_with("foo")) + /// .and(LevelFilter::INFO) + /// }; + /// + /// tracing_subscriber::registry() + /// .with(tracing_subscriber::fmt::subscriber().with_filter(filter)) + /// .init(); + /// ``` + /// + /// By using `boxed`, the types of the two different branches can be erased, + /// so the assignment to the `filter` variable is valid (as both branches + /// have the type `Box + Send + Sync + 'static>`). The + /// following code *does* compile: + /// + /// ``` + /// use tracing_subscriber::{ + /// filter::{filter_fn, LevelFilter, FilterExt}, + /// prelude::*, + /// }; + /// + /// let enable_bar_target: bool = // ... + /// # false; + /// + /// let filter = if enable_bar_target { + /// filter_fn(|meta| meta.target().starts_with("foo")) + /// .or(filter_fn(|meta| meta.target().starts_with("bar"))) + /// .and(LevelFilter::INFO) + /// // Boxing the filter erases its type, so both branches now + /// // have the same type. + /// .boxed() + /// } else { + /// filter_fn(|meta| meta.target().starts_with("foo")) + /// .and(LevelFilter::INFO) + /// .boxed() + /// }; + /// + /// tracing_subscriber::registry() + /// .with(tracing_subscriber::fmt::subscriber().with_filter(filter)) + /// .init(); + /// ``` + /// + /// [Boxes]: std::boxed + /// [`Box::new`]: std::boxed::Box::new + fn boxed(self) -> Box + Send + Sync + 'static> + where + Self: Sized + Send + Sync + 'static, + { + Box::new(self) + } +} + // === impl Filter === + #[cfg(feature = "registry")] #[cfg_attr(docsrs, doc(cfg(feature = "registry")))] impl subscribe::Filter for LevelFilter { @@ -217,38 +443,35 @@ impl subscribe::Filter for LevelFilter { } } -impl subscribe::Filter for Arc + Send + Sync + 'static> { - #[inline] - fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, C>) -> bool { - (**self).enabled(meta, cx) - } +macro_rules! filter_impl_body { + () => { + #[inline] + fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool { + self.deref().enabled(meta, cx) + } - #[inline] - fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest { - (**self).callsite_enabled(meta) - } + #[inline] + fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest { + self.deref().callsite_enabled(meta) + } - #[inline] - fn max_level_hint(&self) -> Option { - (**self).max_level_hint() - } + #[inline] + fn max_level_hint(&self) -> Option { + self.deref().max_level_hint() + } + }; } -impl subscribe::Filter for Box + Send + Sync + 'static> { - #[inline] - fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, C>) -> bool { - (**self).enabled(meta, cx) - } - - #[inline] - fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest { - (**self).callsite_enabled(meta) - } +#[cfg(feature = "registry")] +#[cfg_attr(docsrs, doc(cfg(feature = "registry")))] +impl subscribe::Filter for Arc + Send + Sync + 'static> { + filter_impl_body!(); +} - #[inline] - fn max_level_hint(&self) -> Option { - (**self).max_level_hint() - } +#[cfg(feature = "registry")] +#[cfg_attr(docsrs, doc(cfg(feature = "registry")))] +impl subscribe::Filter for Box + Send + Sync + 'static> { + filter_impl_body!(); } // === impl Filtered === @@ -280,6 +503,40 @@ impl Filtered { fn did_enable(&self, f: impl FnOnce()) { FILTERING.with(|filtering| filtering.did_enable(self.id(), f)) } + + /// Borrows the [`Filter`](crate::subscribe::Filter) used by this subscribe. + pub fn filter(&self) -> &F { + &self.filter + } + + /// Mutably borrows the [`Filter`](crate::subscribe::Filter) used by this subscriber. + /// + /// When this subscriber can be mutably borrowed, this may be used to mutate the filter. + /// Generally, this will primarily be used with the + /// [`reload::Handle::modify`](crate::reload::Handle::modify) method. + /// + /// # Examples + /// + /// ``` + /// # use tracing::info; + /// # use tracing_subscriber::{filter,fmt,reload,Registry,prelude::*}; + /// # fn main() { + /// let filtered_subscriber = fmt::subscriber().with_filter(filter::LevelFilter::WARN); + /// let (filtered_subscriber, reload_handle) = reload::Subscriber::new(filtered_subscriber); + /// # + /// # // specifying the Registry type is required + /// # let _: &reload::Handle, + /// # filter::LevelFilter, Registry>> + /// # = &reload_handle; + /// # + /// info!("This will be ignored"); + /// reload_handle.modify(|subscriber| *subscriber.filter_mut() = filter::LevelFilter::INFO); + /// info!("This will be logged"); + /// # } + /// ``` + pub fn filter_mut(&mut self) -> &mut F { + &mut self.filter + } } impl Subscribe for Filtered @@ -321,7 +578,7 @@ where // per-subscriber filters, the `Registry` will return the actual `Interest` // value that's the sum of all the `register_callsite` calls to those // per-subscriber filters. if we returned an actual `never` interest here, a - // `Subscribeed` subscriber would short-circuit and not allow any `Filtered` + // `Layered` subscriber would short-circuit and not allow any `Filtered` // subscribers below us if _they_ are interested in the callsite. Interest::always() } @@ -1073,7 +1330,7 @@ impl FilterId { /// ```text /// Filtered { /// filter1, - /// Subscribeed { + /// Layered { /// subscriber1, /// Filtered { /// filter2, @@ -1178,6 +1435,10 @@ impl fmt::Binary for FilterId { } } +// === impl FilterExt === + +impl FilterExt for F where F: subscribe::Filter {} + // === impl FilterMap === impl FilterMap { @@ -1343,6 +1604,22 @@ impl FilterState { } } + /// Clears the current in-progress filter state. + /// + /// This resets the [`FilterMap`] and current [`Interest`] as well as + /// clearing the debug counters. + pub(crate) fn clear_enabled() { + // Drop the `Result` returned by `try_with` --- if we are in the middle + // a panic and the thread-local has been torn down, that's fine, just + // ignore it ratehr than panicking. + let _ = FILTERING.try_with(|filtering| { + filtering.enabled.set(FilterMap::default()); + + #[cfg(debug_assertions)] + filtering.counters.in_filter_pass.set(0); + }); + } + pub(crate) fn take_interest() -> Option { FILTERING .try_with(|filtering| { diff --git a/tracing-subscriber/src/reload.rs b/tracing-subscriber/src/reload.rs index 5827165667..f2c577b32f 100644 --- a/tracing-subscriber/src/reload.rs +++ b/tracing-subscriber/src/reload.rs @@ -58,10 +58,6 @@ where S: crate::Subscribe + 'static, C: Collect, { - fn on_subscribe(&mut self, collector: &mut C) { - try_lock!(self.inner.write(), else return).on_subscribe(collector); - } - #[inline] fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { try_lock!(self.inner.read(), else return Interest::sometimes()).register_callsite(metadata) @@ -146,6 +142,11 @@ impl Subscriber { impl Handle { /// Replace the current subscriber with the provided `new_subscriber`. + /// + /// **Warning:** The [`Filtered`](crate::filter::Filtered) type currently can't be changed + /// at runtime via the [`Handle::reload`] method. + /// Use the [`Handle::modify`] method to change the filter instead. + /// (see ) pub fn reload(&self, new_subscriber: impl Into) -> Result<(), Error> { self.modify(|subscriber| { *subscriber = new_subscriber.into(); diff --git a/tracing-subscriber/src/subscribe/context.rs b/tracing-subscriber/src/subscribe/context.rs index 6a649bf947..ffff62ae14 100644 --- a/tracing-subscriber/src/subscribe/context.rs +++ b/tracing-subscriber/src/subscribe/context.rs @@ -406,7 +406,7 @@ where // If we already have our own `FilterId`, combine it with the provided // one. That way, the new `FilterId` will consider a span to be disabled // if it was disabled by the given `FilterId` *or* any `FilterId`s for - // layers "above" us in the stack. + // subscribers "above" us in the stack. // // See the doc comment for `FilterId::and` for details. let filter = self.filter.and(filter); diff --git a/tracing-subscriber/src/subscribe/layered.rs b/tracing-subscriber/src/subscribe/layered.rs index cce97c6861..cf6b65fba3 100644 --- a/tracing-subscriber/src/subscribe/layered.rs +++ b/tracing-subscriber/src/subscribe/layered.rs @@ -78,7 +78,14 @@ where // if the outer subscriber enables the callsite metadata, ask the collector. self.inner.enabled(metadata) } else { - // otherwise, the callsite is disabled by the collector + // otherwise, the callsite is disabled by the subscriber + + // If per-subscriber filters are in use, and we are short-circuiting + // (rather than calling into the inner type), clear the current + // per-subscriber filter `enabled` state. + #[cfg(feature = "registry")] + filter::FilterState::clear_enabled(); + false } } diff --git a/tracing-subscriber/src/subscribe/mod.rs b/tracing-subscriber/src/subscribe/mod.rs index c7676e3e89..607bded441 100644 --- a/tracing-subscriber/src/subscribe/mod.rs +++ b/tracing-subscriber/src/subscribe/mod.rs @@ -419,7 +419,11 @@ //! [`LevelFilter`]: crate::filter::LevelFilter //! [feat]: crate#feature-flags use crate::filter; -use std::{any::TypeId, ops::Deref, ptr::NonNull, sync::Arc}; +use std::{ + any::TypeId, + ops::{Deref, DerefMut}, + ptr::NonNull, +}; use tracing_core::{ collect::{Collect, Interest}, metadata::Metadata, @@ -1087,6 +1091,11 @@ where macro_rules! subscriber_impl_body { () => { + #[inline] + fn on_subscribe(&mut self, collect: &mut C) { + self.deref_mut().on_subscribe(collect); + } + #[inline] fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, C>) { self.deref().on_new_span(attrs, id, ctx) @@ -1150,21 +1159,6 @@ macro_rules! subscriber_impl_body { }; } -impl Subscribe for Arc -where - S: Subscribe, - C: Collect, -{ - subscriber_impl_body! {} -} - -impl Subscribe for Arc> -where - C: Collect, -{ - subscriber_impl_body! {} -} - impl Subscribe for Box where S: Subscribe, diff --git a/tracing-subscriber/tests/layer_filters/targets.rs b/tracing-subscriber/tests/layer_filters/targets.rs new file mode 100644 index 0000000000..74d8acae65 --- /dev/null +++ b/tracing-subscriber/tests/layer_filters/targets.rs @@ -0,0 +1,59 @@ +use super::*; +use tracing_subscriber::{ + filter::{filter_fn, Targets}, + prelude::*, +}; + +#[test] +#[cfg_attr(not(feature = "tracing-log"), ignore)] +fn log_events() { + // Reproduces https://github.com/tokio-rs/tracing/issues/1563 + mod inner { + pub(super) const MODULE_PATH: &str = module_path!(); + + #[tracing::instrument] + pub(super) fn logs() { + log::debug!("inner"); + } + } + + let filter = Targets::new() + .with_default(LevelFilter::DEBUG) + .with_target(inner::MODULE_PATH, LevelFilter::WARN); + + let subscriber = + tracing_subscriber::subscribe::Identity::new().with_filter(filter_fn(move |_meta| true)); + + let _guard = tracing_subscriber::registry() + .with(filter) + .with(subscriber) + .set_default(); + + inner::logs(); +} + +#[test] +fn inner_layer_short_circuits() { + // This test ensures that when a global filter short-circuits `Interest` + // evaluation, we aren't left with a "dirty" per-layer filter state. + + let (subscriber, handle) = subscriber::mock() + .event(event::msg("hello world")) + .done() + .run_with_handle(); + + let filter = Targets::new().with_target("magic_target", LevelFilter::DEBUG); + + let _guard = tracing_subscriber::registry() + // Note: we don't just use a `LevelFilter` for the global filter here, + // because it will just return a max level filter, and the chain of + // `register_callsite` calls that would trigger the bug never happens... + .with(filter::filter_fn(|meta| meta.level() <= &Level::INFO)) + .with(subscriber.with_filter(filter)) + .set_default(); + + tracing::debug!("skip me please!"); + tracing::info!(target: "magic_target", "hello world"); + + handle.assert_finished(); +}