-
Notifications
You must be signed in to change notification settings - Fork 734
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]>
- Loading branch information
1 parent
e12090c
commit ed52841
Showing
6 changed files
with
903 additions
and
312 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<String>, | ||
pub(in crate::filter) field_names: FilterVec<String>, | ||
pub(in crate::filter) level: LevelFilter, | ||
} | ||
|
||
#[cfg(feature = "smallvec")] | ||
pub(in crate::filter) type FilterVec<T> = smallvec::SmallVec<[T; 8]>; | ||
#[cfg(not(feature = "smallvec"))] | ||
pub(in crate::filter) type FilterVec<T> = Vec<T>; | ||
|
||
#[derive(Debug, PartialEq, Clone)] | ||
pub(in crate::filter) struct DirectiveSet<T> { | ||
directives: FilterVec<T>, | ||
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<dyn Error + Send + Sync>), | ||
Level(level::ParseError), | ||
Other(Option<&'static str>), | ||
} | ||
|
||
// === impl DirectiveSet === | ||
|
||
impl<T> DirectiveSet<T> { | ||
pub(crate) fn is_empty(&self) -> bool { | ||
self.directives.is_empty() | ||
} | ||
|
||
pub(crate) fn iter(&self) -> std::slice::Iter<'_, T> { | ||
self.directives.iter() | ||
} | ||
} | ||
|
||
impl<T: Ord> Default for DirectiveSet<T> { | ||
fn default() -> Self { | ||
Self { | ||
directives: FilterVec::new(), | ||
max_level: LevelFilter::OFF, | ||
} | ||
} | ||
} | ||
|
||
impl<T: Match + Ord> DirectiveSet<T> { | ||
pub(crate) fn directives(&self) -> impl Iterator<Item = &T> { | ||
self.directives.iter() | ||
} | ||
|
||
pub(crate) fn directives_for<'a>( | ||
&'a self, | ||
metadata: &'a Metadata<'a>, | ||
) -> impl Iterator<Item = &'a T> + '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<T> { | ||
self.directives | ||
} | ||
} | ||
|
||
impl<T: Match + Ord> FromIterator<T> for DirectiveSet<T> { | ||
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { | ||
let mut this = Self::default(); | ||
this.extend(iter); | ||
this | ||
} | ||
} | ||
|
||
impl<T: Match + Ord> Extend<T> for DirectiveSet<T> { | ||
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) { | ||
for directive in iter.into_iter() { | ||
self.add(directive); | ||
} | ||
} | ||
} | ||
|
||
// === impl Statics === | ||
|
||
impl DirectiveSet<StaticDirective> { | ||
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<String>, | ||
field_names: FilterVec<String>, | ||
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<Ordering> { | ||
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<Self, Self::Err> { | ||
// 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::<LevelFilter>() { | ||
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<Box<dyn Error + Send + Sync>> for DirectiveParseError { | ||
fn from(e: Box<dyn Error + Send + Sync>) -> Self { | ||
Self { | ||
kind: ParseErrorKind::Field(e), | ||
} | ||
} | ||
} | ||
|
||
impl From<level::ParseError> for DirectiveParseError { | ||
fn from(l: level::ParseError) -> Self { | ||
Self { | ||
kind: ParseErrorKind::Level(l), | ||
} | ||
} | ||
} |
Oops, something went wrong.