From 95cdfc541527c687fc8082abf4cf7a059a3b7e91 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Sun, 12 Sep 2021 09:57:50 -0700 Subject: [PATCH] subscriber: add `Targets` filter, a lighter-weight `EnvFilter ` (#1550) This branch adds a new `Targets` filter to `tracing_subscriber`. The `Targets` filter is very similar to `EnvFilter`, but it _only_ consists of filtering directives consisting of a target and level. Because it doesn't support filtering on field names, span contexts, or field values, the implementation is *much* simpler, and it doesn't require the `env_filter` feature flag. Also, `Targets` can easily implement the `Filter` trait for per-layer filtering, while adding a `Filter` implementation for `EnvFilter` will require additional effort. Because the `Targets` filter doesn't allow specifiyng span or field-value filters, the syntax for parsing one from a string is significantly simpler than `EnvFilter`'s. Therefore, it can have a very simple handwritten parser implementation that doesn't require the `regex` crate. This should be useful for users who are concerned about the number of dependencies required by `EnvFilter`. The new implementation is quite small, as it mostly uses the same code as the static filter subset of `EnvFilter`. This code was factored out into a shared module for use in both `EnvFilter` and `Targets`. The code required for _dynamic_ filtering with `EnvFilter` (i.e. on fields and spans) is still in the `filter::env` module and is only enabled by the `env-filter` feature flag. I'm open to renaming the new type; I thought `filter::Targets` seemed good, but would also be willing to go with `TargetFilter` or something. Signed-off-by: Eliza Weisman --- tracing-subscriber/src/filter/directive.rs | 395 +++++++++++++++ .../src/filter/env/directive.rs | 314 +----------- tracing-subscriber/src/filter/env/mod.rs | 23 +- tracing-subscriber/src/filter/mod.rs | 6 + tracing-subscriber/src/filter/targets.rs | 459 ++++++++++++++++++ tracing-subscriber/src/lib.rs | 2 +- tracing-subscriber/src/subscribe/context.rs | 2 +- tracing-subscriber/src/subscribe/mod.rs | 4 +- .../tests/layer_filters/main.rs | 1 + 9 files changed, 899 insertions(+), 307 deletions(-) create mode 100644 tracing-subscriber/src/filter/directive.rs create mode 100644 tracing-subscriber/src/filter/targets.rs diff --git a/tracing-subscriber/src/filter/directive.rs b/tracing-subscriber/src/filter/directive.rs new file mode 100644 index 0000000000..545018e8b7 --- /dev/null +++ b/tracing-subscriber/src/filter/directive.rs @@ -0,0 +1,395 @@ +use crate::filter::level::{self, LevelFilter}; +use std::{cmp::Ordering, error::Error, fmt, iter::FromIterator, str::FromStr}; +use tracing_core::Metadata; +/// Indicates that a string could not be parsed as a filtering directive. +#[derive(Debug)] +pub struct DirectiveParseError { + kind: ParseErrorKind, +} + +/// A directive which will statically enable or disable a given callsite. +/// +/// Unlike a dynamic directive, this can be cached by the callsite. +#[derive(Debug, PartialEq, Eq, Clone)] +pub(crate) struct StaticDirective { + pub(in crate::filter) target: Option, + pub(in crate::filter) field_names: FilterVec, + pub(in crate::filter) level: LevelFilter, +} + +#[cfg(feature = "smallvec")] +pub(in crate::filter) type FilterVec = smallvec::SmallVec<[T; 8]>; +#[cfg(not(feature = "smallvec"))] +pub(in crate::filter) type FilterVec = Vec; + +#[derive(Debug, PartialEq, Clone)] +pub(in crate::filter) struct DirectiveSet { + directives: FilterVec, + pub(in crate::filter) max_level: LevelFilter, +} + +pub(in crate::filter) trait Match { + fn cares_about(&self, meta: &Metadata<'_>) -> bool; + fn level(&self) -> &LevelFilter; +} + +#[derive(Debug)] +enum ParseErrorKind { + Field(Box), + Level(level::ParseError), + Other(Option<&'static str>), +} + +// === impl DirectiveSet === + +impl DirectiveSet { + pub(crate) fn is_empty(&self) -> bool { + self.directives.is_empty() + } + + pub(crate) fn iter(&self) -> std::slice::Iter<'_, T> { + self.directives.iter() + } +} + +impl Default for DirectiveSet { + fn default() -> Self { + Self { + directives: FilterVec::new(), + max_level: LevelFilter::OFF, + } + } +} + +impl DirectiveSet { + pub(crate) fn directives(&self) -> impl Iterator { + self.directives.iter() + } + + pub(crate) fn directives_for<'a>( + &'a self, + metadata: &'a Metadata<'a>, + ) -> impl Iterator + 'a { + self.directives().filter(move |d| d.cares_about(metadata)) + } + + pub(crate) fn add(&mut self, directive: T) { + // does this directive enable a more verbose level than the current + // max? if so, update the max level. + let level = *directive.level(); + if level > self.max_level { + self.max_level = level; + } + // insert the directive into the vec of directives, ordered by + // specificity (length of target + number of field filters). this + // ensures that, when finding a directive to match a span or event, we + // search the directive set in most specific first order. + match self.directives.binary_search(&directive) { + Ok(i) => self.directives[i] = directive, + Err(i) => self.directives.insert(i, directive), + } + } + + #[cfg(test)] + pub(in crate::filter) fn into_vec(self) -> FilterVec { + self.directives + } +} + +impl FromIterator for DirectiveSet { + fn from_iter>(iter: I) -> Self { + let mut this = Self::default(); + this.extend(iter); + this + } +} + +impl Extend for DirectiveSet { + fn extend>(&mut self, iter: I) { + for directive in iter.into_iter() { + self.add(directive); + } + } +} + +// === impl Statics === + +impl DirectiveSet { + pub(crate) fn enabled(&self, meta: &Metadata<'_>) -> bool { + let level = meta.level(); + match self.directives_for(meta).next() { + Some(d) => d.level >= *level, + None => false, + } + } +} + +// === impl StaticDirective === + +impl StaticDirective { + pub(in crate::filter) fn new( + target: Option, + field_names: FilterVec, + level: LevelFilter, + ) -> Self { + Self { + target, + field_names, + level, + } + } +} + +impl Ord for StaticDirective { + fn cmp(&self, other: &StaticDirective) -> Ordering { + // We attempt to order directives by how "specific" they are. This + // ensures that we try the most specific directives first when + // attempting to match a piece of metadata. + + // First, we compare based on whether a target is specified, and the + // lengths of those targets if both have targets. + let ordering = self + .target + .as_ref() + .map(String::len) + .cmp(&other.target.as_ref().map(String::len)) + // Then we compare how many field names are matched by each directive. + .then_with(|| self.field_names.len().cmp(&other.field_names.len())) + // Finally, we fall back to lexicographical ordering if the directives are + // equally specific. Although this is no longer semantically important, + // we need to define a total ordering to determine the directive's place + // in the BTreeMap. + .then_with(|| { + self.target + .cmp(&other.target) + .then_with(|| self.field_names[..].cmp(&other.field_names[..])) + }) + .reverse(); + + #[cfg(debug_assertions)] + { + if ordering == Ordering::Equal { + debug_assert_eq!( + self.target, other.target, + "invariant violated: Ordering::Equal must imply a.target == b.target" + ); + debug_assert_eq!( + self.field_names, other.field_names, + "invariant violated: Ordering::Equal must imply a.field_names == b.field_names" + ); + } + } + + ordering + } +} + +impl PartialOrd for StaticDirective { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Match for StaticDirective { + fn cares_about(&self, meta: &Metadata<'_>) -> bool { + // Does this directive have a target filter, and does it match the + // metadata's target? + if let Some(ref target) = self.target { + if !meta.target().starts_with(&target[..]) { + return false; + } + } + + if meta.is_event() && !self.field_names.is_empty() { + let fields = meta.fields(); + for name in &self.field_names { + if fields.field(name).is_none() { + return false; + } + } + } + + true + } + + fn level(&self) -> &LevelFilter { + &self.level + } +} + +impl Default for StaticDirective { + fn default() -> Self { + StaticDirective { + target: None, + field_names: FilterVec::new(), + level: LevelFilter::ERROR, + } + } +} + +impl fmt::Display for StaticDirective { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut wrote_any = false; + if let Some(ref target) = self.target { + fmt::Display::fmt(target, f)?; + wrote_any = true; + } + + if !self.field_names.is_empty() { + f.write_str("[")?; + + let mut fields = self.field_names.iter(); + if let Some(field) = fields.next() { + write!(f, "{{{}", field)?; + for field in fields { + write!(f, ",{}", field)?; + } + f.write_str("}")?; + } + + f.write_str("]")?; + wrote_any = true; + } + + if wrote_any { + f.write_str("=")?; + } + + fmt::Display::fmt(&self.level, f) + } +} + +impl FromStr for StaticDirective { + type Err = DirectiveParseError; + + fn from_str(s: &str) -> Result { + // This method parses a filtering directive in one of the following + // forms: + // + // * `foo=trace` (TARGET=LEVEL) + // * `foo[{bar,baz}]=info` (TARGET[{FIELD,+}]=LEVEL) + // * `trace` (bare LEVEL) + // * `foo` (bare TARGET) + let mut split = s.split('='); + let part0 = split + .next() + .ok_or_else(|| DirectiveParseError::msg("string must not be empty"))?; + + // Directive includes an `=`: + // * `foo=trace` + // * `foo[{bar}]=trace` + // * `foo[{bar,baz}]=trace` + if let Some(part1) = split.next() { + if split.next().is_some() { + return Err(DirectiveParseError::msg( + "too many '=' in filter directive, expected 0 or 1", + )); + } + + let mut split = part0.split("[{"); + let target = split.next().map(String::from); + let mut field_names = FilterVec::new(); + // Directive includes fields: + // * `foo[{bar}]=trace` + // * `foo[{bar,baz}]=trace` + if let Some(maybe_fields) = split.next() { + if split.next().is_some() { + return Err(DirectiveParseError::msg( + "too many '[{' in filter directive, expected 0 or 1", + )); + } + + let fields = maybe_fields.strip_suffix("}]").ok_or_else(|| { + DirectiveParseError::msg("expected fields list to end with '}]'") + })?; + field_names.extend(fields.split(',').filter_map(|s| { + if s.is_empty() { + None + } else { + Some(String::from(s)) + } + })); + }; + let level = part1.parse()?; + return Ok(Self { + level, + field_names, + target, + }); + } + + // Okay, the part after the `=` was empty, the directive is either a + // bare level or a bare target. + // * `foo` + // * `info` + Ok(match part0.parse::() { + Ok(level) => Self { + level, + target: None, + field_names: FilterVec::new(), + }, + Err(_) => Self { + target: Some(String::from(part0)), + level: LevelFilter::TRACE, + field_names: FilterVec::new(), + }, + }) + } +} + +// === impl ParseError === + +impl DirectiveParseError { + pub(crate) fn new() -> Self { + DirectiveParseError { + kind: ParseErrorKind::Other(None), + } + } + + pub(crate) fn msg(s: &'static str) -> Self { + DirectiveParseError { + kind: ParseErrorKind::Other(Some(s)), + } + } +} + +impl fmt::Display for DirectiveParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.kind { + ParseErrorKind::Other(None) => f.pad("invalid filter directive"), + ParseErrorKind::Other(Some(msg)) => write!(f, "invalid filter directive: {}", msg), + ParseErrorKind::Level(ref l) => l.fmt(f), + ParseErrorKind::Field(ref e) => write!(f, "invalid field filter: {}", e), + } + } +} + +impl Error for DirectiveParseError { + fn description(&self) -> &str { + "invalid filter directive" + } + + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self.kind { + ParseErrorKind::Other(_) => None, + ParseErrorKind::Level(ref l) => Some(l), + ParseErrorKind::Field(ref n) => Some(n.as_ref()), + } + } +} + +impl From> for DirectiveParseError { + fn from(e: Box) -> Self { + Self { + kind: ParseErrorKind::Field(e), + } + } +} + +impl From for DirectiveParseError { + fn from(l: level::ParseError) -> Self { + Self { + kind: ParseErrorKind::Level(l), + } + } +} diff --git a/tracing-subscriber/src/filter/env/directive.rs b/tracing-subscriber/src/filter/env/directive.rs index 02989b4f04..2f3eea4c96 100644 --- a/tracing-subscriber/src/filter/env/directive.rs +++ b/tracing-subscriber/src/filter/env/directive.rs @@ -1,8 +1,13 @@ -use super::super::level::{self, LevelFilter}; -use super::{field, FieldMap, FilterVec}; +use super::FilterVec; +pub(crate) use crate::filter::directive::{DirectiveParseError, StaticDirective}; +use crate::filter::{ + directive::{DirectiveSet, Match}, + env::{field, FieldMap}, + level::LevelFilter, +}; use lazy_static::lazy_static; use regex::Regex; -use std::{cmp::Ordering, error::Error, fmt, iter::FromIterator, str::FromStr}; +use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr}; use tracing_core::{span, Level, Metadata}; /// A single filtering directive. @@ -16,32 +21,11 @@ pub struct Directive { pub(crate) level: LevelFilter, } -/// A directive which will statically enable or disable a given callsite. -/// -/// Unlike a dynamic directive, this can be cached by the callsite. -#[derive(Debug, PartialEq, Eq)] -pub(crate) struct StaticDirective { - target: Option, - field_names: FilterVec, - level: LevelFilter, -} - -pub(crate) trait Match { - fn cares_about(&self, meta: &Metadata<'_>) -> bool; - fn level(&self) -> &LevelFilter; -} - /// A set of dynamic filtering directives. -pub(crate) type Dynamics = DirectiveSet; +pub(super) type Dynamics = DirectiveSet; /// A set of static filtering directives. -pub(crate) type Statics = DirectiveSet; - -#[derive(Debug, PartialEq)] -pub(crate) struct DirectiveSet { - directives: Vec, - pub(crate) max_level: LevelFilter, -} +pub(super) type Statics = DirectiveSet; pub(crate) type CallsiteMatcher = MatchSet; pub(crate) type SpanMatcher = MatchSet; @@ -52,19 +36,6 @@ pub(crate) struct MatchSet { base_level: LevelFilter, } -/// Indicates that a string could not be parsed as a filtering directive. -#[derive(Debug)] -pub struct ParseError { - kind: ParseErrorKind, -} - -#[derive(Debug)] -enum ParseErrorKind { - Field(Box), - Level(level::ParseError), - Other, -} - impl Directive { pub(super) fn has_name(&self) -> bool { self.in_span.is_some() @@ -83,11 +54,11 @@ impl Directive { // `Arc`ing them to make this more efficient... let field_names = self.fields.iter().map(field::Match::name).collect(); - Some(StaticDirective { - target: self.target.clone(), + Some(StaticDirective::new( + self.target.clone(), field_names, - level: self.level, - }) + self.level, + )) } fn is_static(&self) -> bool { @@ -174,7 +145,7 @@ impl Match for Directive { } impl FromStr for Directive { - type Err = ParseError; + type Err = DirectiveParseError; fn from_str(from: &str) -> Result { lazy_static! { static ref DIRECTIVE_RE: Regex = Regex::new( @@ -212,7 +183,9 @@ impl FromStr for Directive { "#).unwrap(); } - let caps = DIRECTIVE_RE.captures(from).ok_or_else(ParseError::new)?; + let caps = DIRECTIVE_RE + .captures(from) + .ok_or_else(DirectiveParseError::new)?; if let Some(level) = caps .name("global_level") @@ -387,71 +360,6 @@ impl From for Directive { } } -// === impl DirectiveSet === - -impl DirectiveSet { - pub(crate) fn is_empty(&self) -> bool { - self.directives.is_empty() - } - - pub(crate) fn iter(&self) -> std::slice::Iter<'_, T> { - self.directives.iter() - } -} - -impl Default for DirectiveSet { - fn default() -> Self { - Self { - directives: Vec::new(), - max_level: LevelFilter::OFF, - } - } -} - -impl DirectiveSet { - fn directives_for<'a>( - &'a self, - metadata: &'a Metadata<'a>, - ) -> impl Iterator + 'a { - self.directives - .iter() - .filter(move |d| d.cares_about(metadata)) - } - - pub(crate) fn add(&mut self, directive: T) { - // does this directive enable a more verbose level than the current - // max? if so, update the max level. - let level = *directive.level(); - if level > self.max_level { - self.max_level = level; - } - // insert the directive into the vec of directives, ordered by - // specificity (length of target + number of field filters). this - // ensures that, when finding a directive to match a span or event, we - // search the directive set in most specific first order. - match self.directives.binary_search(&directive) { - Ok(i) => self.directives[i] = directive, - Err(i) => self.directives.insert(i, directive), - } - } -} - -impl FromIterator for DirectiveSet { - fn from_iter>(iter: I) -> Self { - let mut this = Self::default(); - this.extend(iter); - this - } -} - -impl Extend for DirectiveSet { - fn extend>(&mut self, iter: I) { - for directive in iter.into_iter() { - self.add(directive); - } - } -} - // === impl Dynamics === impl Dynamics { @@ -488,195 +396,11 @@ impl Dynamics { } pub(crate) fn has_value_filters(&self) -> bool { - self.directives - .iter() + self.directives() .any(|d| d.fields.iter().any(|f| f.value.is_some())) } } -// === impl Statics === - -impl Statics { - pub(crate) fn enabled(&self, meta: &Metadata<'_>) -> bool { - let level = meta.level(); - match self.directives_for(meta).next() { - Some(d) => d.level >= *level, - None => false, - } - } -} - -impl Ord for StaticDirective { - fn cmp(&self, other: &StaticDirective) -> Ordering { - // We attempt to order directives by how "specific" they are. This - // ensures that we try the most specific directives first when - // attempting to match a piece of metadata. - - // First, we compare based on whether a target is specified, and the - // lengths of those targets if both have targets. - let ordering = self - .target - .as_ref() - .map(String::len) - .cmp(&other.target.as_ref().map(String::len)) - // Then we compare how many field names are matched by each directive. - .then_with(|| self.field_names.len().cmp(&other.field_names.len())) - // Finally, we fall back to lexicographical ordering if the directives are - // equally specific. Although this is no longer semantically important, - // we need to define a total ordering to determine the directive's place - // in the BTreeMap. - .then_with(|| { - self.target - .cmp(&other.target) - .then_with(|| self.field_names[..].cmp(&other.field_names[..])) - }) - .reverse(); - - #[cfg(debug_assertions)] - { - if ordering == Ordering::Equal { - debug_assert_eq!( - self.target, other.target, - "invariant violated: Ordering::Equal must imply a.target == b.target" - ); - debug_assert_eq!( - self.field_names, other.field_names, - "invariant violated: Ordering::Equal must imply a.field_names == b.field_names" - ); - } - } - - ordering - } -} - -impl PartialOrd for StaticDirective { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -// ===== impl StaticDirective ===== - -impl Match for StaticDirective { - fn cares_about(&self, meta: &Metadata<'_>) -> bool { - // Does this directive have a target filter, and does it match the - // metadata's target? - if let Some(ref target) = self.target { - if !meta.target().starts_with(&target[..]) { - return false; - } - } - - if meta.is_event() && !self.field_names.is_empty() { - let fields = meta.fields(); - for name in &self.field_names { - if fields.field(name).is_none() { - return false; - } - } - } - - true - } - - fn level(&self) -> &LevelFilter { - &self.level - } -} - -impl Default for StaticDirective { - fn default() -> Self { - StaticDirective { - target: None, - field_names: FilterVec::new(), - level: LevelFilter::ERROR, - } - } -} - -impl fmt::Display for StaticDirective { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut wrote_any = false; - if let Some(ref target) = self.target { - fmt::Display::fmt(target, f)?; - wrote_any = true; - } - - if !self.field_names.is_empty() { - f.write_str("[")?; - - let mut fields = self.field_names.iter(); - if let Some(field) = fields.next() { - write!(f, "{{{}", field)?; - for field in fields { - write!(f, ",{}", field)?; - } - f.write_str("}")?; - } - - f.write_str("]")?; - wrote_any = true; - } - - if wrote_any { - f.write_str("=")?; - } - - fmt::Display::fmt(&self.level, f) - } -} - -// ===== impl ParseError ===== - -impl ParseError { - fn new() -> Self { - ParseError { - kind: ParseErrorKind::Other, - } - } -} - -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.kind { - ParseErrorKind::Other => f.pad("invalid filter directive"), - ParseErrorKind::Level(ref l) => l.fmt(f), - ParseErrorKind::Field(ref e) => write!(f, "invalid field filter: {}", e), - } - } -} - -impl Error for ParseError { - fn description(&self) -> &str { - "invalid filter directive" - } - - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self.kind { - ParseErrorKind::Other => None, - ParseErrorKind::Level(ref l) => Some(l), - ParseErrorKind::Field(ref n) => Some(n.as_ref()), - } - } -} - -impl From> for ParseError { - fn from(e: Box) -> Self { - Self { - kind: ParseErrorKind::Field(e), - } - } -} - -impl From for ParseError { - fn from(l: level::ParseError) -> Self { - Self { - kind: ParseErrorKind::Level(l), - } - } -} - // ===== impl DynamicMatch ===== impl CallsiteMatcher { diff --git a/tracing-subscriber/src/filter/env/mod.rs b/tracing-subscriber/src/filter/env/mod.rs index 2c633f1d6b..84c22e7906 100644 --- a/tracing-subscriber/src/filter/env/mod.rs +++ b/tracing-subscriber/src/filter/env/mod.rs @@ -4,10 +4,7 @@ // these are publicly re-exported, but the compiler doesn't realize // that for some reason. #[allow(unreachable_pub)] -pub use self::{ - directive::{Directive, ParseError}, - field::BadName as BadFieldName, -}; +pub use self::{directive::Directive, field::BadName as BadFieldName}; mod directive; mod field; @@ -16,6 +13,7 @@ use crate::{ subscribe::{Context, Subscribe}, sync::RwLock, }; +use directive::DirectiveParseError; use std::{cell::RefCell, collections::HashMap, env, error::Error, fmt, str::FromStr}; use tracing_core::{ callsite, @@ -90,6 +88,11 @@ use tracing_core::{ /// - which has a field named `name` with value `bob`, /// - at _any_ level. /// +/// The [`Targets`] type implements a similar form of filtering, but without the +/// ability to dynamically enable events based on the current span context, and +/// without filtering on field values. When these features are not required, +/// [`Targets`] provides a lighter-weight alternative to [`EnvFilter`]. +/// /// [`Subscriber`]: Subscribe /// [`env_logger`]: https://docs.rs/env_logger/0.7.1/env_logger/#enabling-logging /// [`Span`]: tracing_core::span @@ -97,7 +100,9 @@ use tracing_core::{ /// [`Event`]: tracing_core::Event /// [`level`]: tracing_core::Level /// [`Metadata`]: tracing_core::Metadata +/// [`Targets`]: crate::filter::Targets #[cfg_attr(docsrs, doc(cfg(all(feature = "env-filter", feature = "std"))))] +#[cfg(feature = "env-filter")] #[derive(Debug)] pub struct EnvFilter { statics: directive::Statics, @@ -128,7 +133,7 @@ pub struct FromEnvError { #[derive(Debug)] enum ErrorKind { - Parse(ParseError), + Parse(DirectiveParseError), Env(env::VarError), } @@ -165,7 +170,7 @@ impl EnvFilter { /// Returns a new `EnvFilter` from the directives in the given string, /// or an error if any are invalid. - pub fn try_new>(dirs: S) -> Result { + pub fn try_new>(dirs: S) -> Result { let directives = dirs .as_ref() .split(',') @@ -483,7 +488,7 @@ impl Subscribe for EnvFilter { } impl FromStr for EnvFilter { - type Err = ParseError; + type Err = directive::DirectiveParseError; fn from_str(spec: &str) -> Result { Self::try_new(spec) @@ -534,8 +539,8 @@ impl fmt::Display for EnvFilter { // ===== impl FromEnvError ===== -impl From for FromEnvError { - fn from(p: ParseError) -> Self { +impl From for FromEnvError { + fn from(p: directive::DirectiveParseError) -> Self { Self { kind: ErrorKind::Parse(p), } diff --git a/tracing-subscriber/src/filter/mod.rs b/tracing-subscriber/src/filter/mod.rs index fe6b1fda28..63bcfc675d 100644 --- a/tracing-subscriber/src/filter/mod.rs +++ b/tracing-subscriber/src/filter/mod.rs @@ -8,16 +8,20 @@ //! //! [`subscribe` module documentation]: crate::subscribe#filtering-with-subscribers //! [`Subscribe`]: crate::subscribe +mod directive; #[cfg(feature = "env-filter")] mod env; mod filter_fn; mod level; #[cfg(feature = "registry")] mod subscriber_filters; +mod targets; +pub use self::directive::DirectiveParseError; pub use self::filter_fn::*; #[cfg(not(feature = "registry"))] pub(crate) use self::has_plf_stubs::*; + #[cfg(feature = "registry")] #[cfg_attr(docsrs, doc(cfg(feature = "registry")))] pub use self::subscriber_filters::*; @@ -28,6 +32,8 @@ pub use self::level::{LevelFilter, ParseError as LevelParseError}; #[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))] pub use self::env::*; +pub use self::targets::Targets; + /// Stub implementations of the per-layer-fitler detection functions for when the /// `registry` feature is disabled. #[cfg(not(feature = "registry"))] diff --git a/tracing-subscriber/src/filter/targets.rs b/tracing-subscriber/src/filter/targets.rs new file mode 100644 index 0000000000..1850e25c8b --- /dev/null +++ b/tracing-subscriber/src/filter/targets.rs @@ -0,0 +1,459 @@ +use crate::{ + filter::{ + directive::{DirectiveParseError, DirectiveSet, StaticDirective}, + LevelFilter, + }, + subscribe, +}; +use std::{ + iter::{Extend, FromIterator}, + str::FromStr, +}; +use tracing_core::{Collect, Interest, Metadata}; + +/// A filter that enables or disables spans and events based on their [target] +/// and [level]. +/// +/// Targets are typically equal to the Rust module path of the code where the +/// span or event was recorded, although they may be overridden. +/// +/// 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. +/// +/// # Filtering With `Targets` +/// +/// A `Targets` filter consists of one or more [target] prefixes, paired with +/// [`LevelFilter`]s. If a span or event's [target] begins with one of those +/// prefixes, and its [level] is at or below the [`LevelFilter`] enabled for +/// that prefix, then the span or event will be enabled. +/// +/// This is similar to the behavior implemented by the [`env_logger` crate] in +/// the `log` ecosystem. +/// +/// The [`EnvFilter`] type also provided by this crate is very similar to `Targets`, +/// but is capable of a more sophisticated form of filtering where events may +/// also be enabled or disabled based on the span they are recorded in. +/// `Targets` can be thought of as a lighter-weight form of [`EnvFilter`] that +/// can be used instead when this dynamic filtering is not required. +/// +/// # Examples +/// +/// A `Targets` filter can be constructed by programmatically adding targets and +/// levels to enable: +/// +/// ``` +/// use tracing_subscriber::{filter, prelude::*}; +/// use tracing_core::Level; +/// +/// let filter = filter::Targets::new() +/// // Enable the `INFO` level for anything in `my_crate` +/// .with_target("my_crate", Level::INFO) +/// // Enable the `DEBUG` level for a specific module. +/// .with_target("my_crate::interesting_module", Level::DEBUG); +/// +/// // Build a new collector with the `fmt` subscriber using the `Targets` +/// // filter we constructed above. +/// tracing_subscriber::registry() +/// .with(tracing_subscriber::fmt::subscriber()) +/// .with(filter) +/// .init(); +/// ``` +/// +/// [`LevelFilter::OFF`] can be used to disable a particular target: +/// ``` +/// use tracing_subscriber::filter::{Targets, LevelFilter}; +/// use tracing_core::Level; +/// +/// let filter = Targets::new() +/// .with_target("my_crate", Level::INFO) +/// // Disable all traces from `annoying_module`. +/// .with_target("my_crate::annoying_module", LevelFilter::OFF); +/// # drop(filter); +/// ``` +/// +/// Alternatively, `Targets` implements [`std::str::FromStr`], allowing it to be +/// parsed from a comma-delimited list of `target=level` pairs. For example: +/// +/// ```rust +/// # fn main() -> Result<(), Box> { +/// use tracing_subscriber::filter; +/// use tracing_core::Level; +/// +/// let filter = "my_crate=info,my_crate::interesting_module=trace,other_crate=debug" +/// .parse::()?; +/// +/// // The parsed filter is identical to a filter constructed using `with_target`: +/// assert_eq!( +/// filter, +/// filter::Targets::new() +/// .with_target("my_crate", Level::INFO) +/// .with_target("my_crate::interesting_module", Level::TRACE) +/// .with_target("other_crate", Level::DEBUG) +/// ); +/// # Ok(()) } +/// ``` +/// +/// This is particularly useful when the list of enabled targets is configurable +/// by the user at runtime. +/// +/// The `Targets` filter can be used as a [per-subscriber filter][plf] *and* as a +/// [global filter]: +/// +/// ```rust +/// use tracing_subscriber::{ +/// fmt, +/// filter::{Targets, LevelFilter}, +/// prelude::*, +/// }; +/// use tracing_core::Level; +/// use std::fs::File; +/// # fn main() -> Result<(), Box> { +/// +/// // A subscriber that logs events to stdout using the human-readable "pretty" +/// // format. +/// let stdout_log = fmt::subscriber().pretty(); +/// +/// // A subscriber that logs events to a file, using the JSON format. +/// let file = File::create("debug_log.json")?; +/// let debug_log = fmt::subscriber() +/// .with_writer(file) +/// .json(); +/// +/// tracing_subscriber::registry() +/// // Only log INFO and above to stdout, unless the span or event +/// // has the `my_crate::cool_module` target prefix. +/// .with(stdout_log +/// .with_filter( +/// Targets::default() +/// .with_target("my_crate::cool_module", Level::DEBUG) +/// .with_default(Level::INFO) +/// ) +/// ) +/// // Log everything enabled by the global filter to `debug_log.json`. +/// .with(debug_log) +/// // Configure a global filter for the whole subscriber stack. This will +/// // control what spans and events are recorded by both the `debug_log` +/// // and the `stdout_log` subscribers, and `stdout_log` will *additionally* be +/// // filtered by its per-subscriber filter. +/// .with( +/// Targets::default() +/// .with_target("my_crate", Level::TRACE) +/// .with_target("other_crate", Level::INFO) +/// .with_target("other_crate::annoying_module", LevelFilter::OFF) +/// .with_target("third_crate", Level::DEBUG) +/// ).init(); +/// # Ok(()) } +///``` +/// +/// [target]: tracing_core::Metadata::target +/// [level]: tracing_core::Level +/// [`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 +/// [`env_logger` crate]: https://docs.rs/env_logger/0.9.0/env_logger/index.html#enabling-logging +/// [`EnvFilter`]: crate::filter::EnvFilter +#[derive(Debug, Default, Clone, PartialEq)] +pub struct Targets(DirectiveSet); + +impl Targets { + /// Returns a new `Targets` filter. + /// + /// This filter will enable no targets. Call [`with_target`] or [`with_targets`] + /// to add enabled targets, and [`with_default`] to change the default level + /// enabled for spans and events that didn't match any of the provided targets. + /// + /// [`with_target`]: Targets::with_target + /// [`with_targets`]: Targets::with_targets + /// [`with_default`]: Targets::with_default + pub fn new() -> Self { + Self::default() + } + + /// Enables spans and events with [target]s starting with the provided target + /// prefix if they are at or below the provided [`LevelFilter`]. + /// + /// # Examples + /// + /// ``` + /// use tracing_subscriber::filter; + /// use tracing_core::Level; + /// + /// let filter = filter::Targets::new() + /// // Enable the `INFO` level for anything in `my_crate` + /// .with_target("my_crate", Level::INFO) + /// // Enable the `DEBUG` level for a specific module. + /// .with_target("my_crate::interesting_module", Level::DEBUG); + /// # drop(filter); + /// ``` + /// + /// [`LevelFilter::OFF`] can be used to disable a particular target: + /// ``` + /// use tracing_subscriber::filter::{Targets, LevelFilter}; + /// use tracing_core::Level; + /// + /// let filter = Targets::new() + /// .with_target("my_crate", Level::INFO) + /// // Disable all traces from `annoying_module`. + /// .with_target("my_crate::interesting_module", LevelFilter::OFF); + /// # drop(filter); + /// ``` + /// + /// [target]: tracing_core::Metadata::target + pub fn with_target(mut self, target: impl Into, level: impl Into) -> Self { + self.0.add(StaticDirective::new( + Some(target.into()), + Default::default(), + level.into(), + )); + self + } + /// Adds [target]s from an iterator of [target]-[`LevelFilter`] pairs to this filter. + /// + /// # Examples + /// + /// ``` + /// use tracing_subscriber::filter; + /// use tracing_core::Level; + /// + /// let filter = filter::Targets::new() + /// .with_targets(vec![ + /// ("my_crate", Level::INFO), + /// ("my_crate::some_module", Level::DEBUG), + /// ("my_crate::other_module::cool_stuff", Level::TRACE), + /// ("other_crate", Level::WARN) + /// ]); + /// # drop(filter); + /// ``` + /// + /// [`LevelFilter::OFF`] can be used to disable a particular target: + /// ``` + /// use tracing_subscriber::filter::{Targets, LevelFilter}; + /// use tracing_core::Level; + /// + /// let filter = Targets::new() + /// .with_target("my_crate", Level::INFO) + /// // Disable all traces from `annoying_module`. + /// .with_target("my_crate::interesting_module", LevelFilter::OFF); + /// # drop(filter); + /// ``` + /// + /// [target]: tracing_core::Metadata::target + pub fn with_targets(mut self, targets: impl IntoIterator) -> Self + where + String: From, + LevelFilter: From, + { + self.extend(targets); + self + } + + /// Sets the default level to enable for spans and events whose targets did + /// not match any of the configured prefixes. + /// + /// By default, this is [`LevelFilter::OFF`]. This means that spans and + /// events will only be enabled if they match one of the configured target + /// prefixes. If this is changed to a different [`LevelFilter`], spans and + /// events with targets that did not match any of the configured prefixes + /// will be enabled if their level is at or below the provided level. + pub fn with_default(mut self, level: impl Into) -> Self { + self.0 + .add(StaticDirective::new(None, Default::default(), level.into())); + self + } + + #[inline] + fn interested(&self, metadata: &'static Metadata<'static>) -> Interest { + if self.0.enabled(metadata) { + Interest::always() + } else { + Interest::never() + } + } +} + +impl Extend<(T, L)> for Targets +where + T: Into, + L: Into, +{ + fn extend>(&mut self, iter: I) { + let iter = iter.into_iter().map(|(target, level)| { + StaticDirective::new(Some(target.into()), Default::default(), level.into()) + }); + self.0.extend(iter); + } +} + +impl FromIterator<(T, L)> for Targets +where + T: Into, + L: Into, +{ + fn from_iter>(iter: I) -> Self { + let mut this = Self::default(); + this.extend(iter); + this + } +} + +impl FromStr for Targets { + type Err = DirectiveParseError; + fn from_str(s: &str) -> Result { + s.split(',') + .map(StaticDirective::from_str) + .collect::>() + .map(Self) + } +} + +impl subscribe::Subscribe for Targets +where + C: Collect, +{ + fn enabled(&self, metadata: &Metadata<'_>, _: subscribe::Context<'_, C>) -> bool { + self.0.enabled(metadata) + } + + fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { + self.interested(metadata) + } + + fn max_level_hint(&self) -> Option { + Some(self.0.max_level) + } +} + +#[cfg(feature = "registry")] +#[cfg_attr(docsrs, doc(cfg(feature = "registry")))] +impl subscribe::Filter for Targets { + fn enabled(&self, metadata: &Metadata<'_>, _: &subscribe::Context<'_, C>) -> bool { + self.0.enabled(metadata) + } + + fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest { + self.interested(metadata) + } + + fn max_level_hint(&self) -> Option { + Some(self.0.max_level) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::filter::directive::FilterVec; + + fn expect_parse(s: &str) -> FilterVec { + match dbg!(s).parse::() { + Err(e) => panic!("string {:?} did not parse successfully: {}", s, e), + Ok(e) => e.0.into_vec(), + } + } + + fn expect_parse_ralith(s: &str) { + let dirs = expect_parse(s); + assert_eq!(dirs.len(), 2, "\nparsed: {:#?}", dirs); + assert_eq!(dirs[0].target, Some("server".to_string())); + assert_eq!(dirs[0].level, LevelFilter::DEBUG); + assert_eq!(dirs[0].field_names, FilterVec::::default()); + + assert_eq!(dirs[1].target, Some("common".to_string())); + assert_eq!(dirs[1].level, LevelFilter::INFO); + assert_eq!(dirs[1].field_names, FilterVec::::default()); + } + + fn expect_parse_level_directives(s: &str) { + let dirs = expect_parse(s); + assert_eq!(dirs.len(), 6, "\nparsed: {:#?}", dirs); + + assert_eq!(dirs[0].target, Some("crate3::mod2::mod1".to_string())); + assert_eq!(dirs[0].level, LevelFilter::OFF); + assert_eq!(dirs[0].field_names, FilterVec::::default()); + + assert_eq!(dirs[1].target, Some("crate1::mod2::mod3".to_string())); + assert_eq!(dirs[1].level, LevelFilter::INFO); + assert_eq!(dirs[1].field_names, FilterVec::::default()); + + assert_eq!(dirs[2].target, Some("crate1::mod2".to_string())); + assert_eq!(dirs[2].level, LevelFilter::WARN); + assert_eq!(dirs[2].field_names, FilterVec::::default()); + + assert_eq!(dirs[3].target, Some("crate1::mod1".to_string())); + assert_eq!(dirs[3].level, LevelFilter::ERROR); + assert_eq!(dirs[3].field_names, FilterVec::::default()); + + assert_eq!(dirs[4].target, Some("crate3".to_string())); + assert_eq!(dirs[4].level, LevelFilter::TRACE); + assert_eq!(dirs[4].field_names, FilterVec::::default()); + + assert_eq!(dirs[5].target, Some("crate2".to_string())); + assert_eq!(dirs[5].level, LevelFilter::DEBUG); + assert_eq!(dirs[5].field_names, FilterVec::::default()); + } + + #[test] + fn parse_ralith() { + expect_parse_ralith("common=info,server=debug"); + } + + #[test] + fn parse_ralith_uc() { + expect_parse_ralith("common=INFO,server=DEBUG"); + } + + #[test] + fn parse_ralith_mixed() { + expect_parse("common=iNfo,server=dEbUg"); + } + + #[test] + fn expect_parse_valid() { + let dirs = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off"); + assert_eq!(dirs.len(), 4, "\nparsed: {:#?}", dirs); + assert_eq!(dirs[0].target, Some("crate1::mod2".to_string())); + assert_eq!(dirs[0].level, LevelFilter::TRACE); + assert_eq!(dirs[0].field_names, FilterVec::::default()); + + assert_eq!(dirs[1].target, Some("crate1::mod1".to_string())); + assert_eq!(dirs[1].level, LevelFilter::ERROR); + assert_eq!(dirs[1].field_names, FilterVec::::default()); + + assert_eq!(dirs[2].target, Some("crate3".to_string())); + assert_eq!(dirs[2].level, LevelFilter::OFF); + assert_eq!(dirs[2].field_names, FilterVec::::default()); + + assert_eq!(dirs[3].target, Some("crate2".to_string())); + assert_eq!(dirs[3].level, LevelFilter::DEBUG); + assert_eq!(dirs[3].field_names, FilterVec::::default()); + } + + #[test] + fn parse_level_directives() { + expect_parse_level_directives( + "crate1::mod1=error,crate1::mod2=warn,crate1::mod2::mod3=info,\ + crate2=debug,crate3=trace,crate3::mod2::mod1=off", + ) + } + + #[test] + fn parse_uppercase_level_directives() { + expect_parse_level_directives( + "crate1::mod1=ERROR,crate1::mod2=WARN,crate1::mod2::mod3=INFO,\ + crate2=DEBUG,crate3=TRACE,crate3::mod2::mod1=OFF", + ) + } + + #[test] + fn parse_numeric_level_directives() { + expect_parse_level_directives( + "crate1::mod1=1,crate1::mod2=2,crate1::mod2::mod3=3,crate2=4,\ + crate3=5,crate3::mod2::mod1=0", + ) + } +} diff --git a/tracing-subscriber/src/lib.rs b/tracing-subscriber/src/lib.rs index e26609a183..62ee58743e 100644 --- a/tracing-subscriber/src/lib.rs +++ b/tracing-subscriber/src/lib.rs @@ -41,7 +41,7 @@ //! //! ## Included Collectors //! -//! The following [ollectors] are provided for application authors: +//! The following [collector]s are provided for application authors: //! //! - [`fmt`] - Formats and logs tracing data (requires the `fmt` feature flag) //! diff --git a/tracing-subscriber/src/subscribe/context.rs b/tracing-subscriber/src/subscribe/context.rs index ffff62ae14..f0563e7fef 100644 --- a/tracing-subscriber/src/subscribe/context.rs +++ b/tracing-subscriber/src/subscribe/context.rs @@ -3,7 +3,7 @@ use tracing_core::{collect::Collect, metadata::Metadata, span, Event}; use crate::registry::{self, LookupSpan, SpanRef}; #[cfg(feature = "registry")] use crate::{filter::FilterId, registry::Registry}; -/// Represents information about the current context provided to [subscribers]s by the +/// Represents information about the current context provided to [subscriber]s by the /// wrapped [collector]. /// /// To access [stored data] keyed by a span ID, implementors of the [`Subscribe`] diff --git a/tracing-subscriber/src/subscribe/mod.rs b/tracing-subscriber/src/subscribe/mod.rs index 2b9005c72c..ce52358c6e 100644 --- a/tracing-subscriber/src/subscribe/mod.rs +++ b/tracing-subscriber/src/subscribe/mod.rs @@ -447,6 +447,7 @@ pub(crate) mod tests; /// [module-level documentation](crate::subscribe) for details. /// /// [collector]: tracing_core::Collect +#[cfg_attr(docsrs, doc(notable_trait))] pub trait Subscribe where C: Collect, @@ -795,7 +796,8 @@ where /// A per-[`Subscribe`] filter that determines whether a span or event is enabled /// for an individual subscriber. #[cfg(feature = "registry")] -#[cfg_attr(docsrs, doc(cfg(feature = "registry"), notable_trait))] +#[cfg_attr(docsrs, doc(cfg(feature = "registry")))] +#[cfg_attr(docsrs, doc(notable_trait))] pub trait Filter { /// Returns `true` if this subscriber is interested in a span or event with the /// given [`Metadata`] in the current [`Context`], similarly to diff --git a/tracing-subscriber/tests/layer_filters/main.rs b/tracing-subscriber/tests/layer_filters/main.rs index a61b8ff97e..8a6ad991d2 100644 --- a/tracing-subscriber/tests/layer_filters/main.rs +++ b/tracing-subscriber/tests/layer_filters/main.rs @@ -3,6 +3,7 @@ mod support; use self::support::*; mod filter_scopes; +mod targets; mod trees; use tracing::{level_filters::LevelFilter, Level};