use matchers::Pattern; use std::{ cmp::Ordering, error::Error, fmt::{self, Write}, str::FromStr, sync::{ atomic::{AtomicBool, Ordering::*}, Arc, }, }; use super::{FieldMap, LevelFilter}; use tracing_core::field::{Field, Visit}; #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct Match { pub(crate) name: String, // TODO: allow match patterns for names? pub(crate) value: Option, } #[derive(Debug, Eq, PartialEq)] pub(crate) struct CallsiteMatch { pub(crate) fields: FieldMap, pub(crate) level: LevelFilter, } #[derive(Debug)] pub(crate) struct SpanMatch { fields: FieldMap<(ValueMatch, AtomicBool)>, level: LevelFilter, has_matched: AtomicBool, } pub(crate) struct MatchVisitor<'a> { inner: &'a SpanMatch, } #[derive(Debug, Clone)] pub(crate) enum ValueMatch { /// Matches a specific `bool` value. Bool(bool), /// Matches a specific `f64` value. F64(f64), /// Matches a specific `u64` value. U64(u64), /// Matches a specific `i64` value. I64(i64), /// Matches any `NaN` `f64` value. NaN, /// Matches any field whose `fmt::Debug` output is equal to a fixed string. Debug(MatchDebug), /// Matches any field whose `fmt::Debug` output matches a regular expression /// pattern. Pat(Box), } impl Eq for ValueMatch {} impl PartialEq for ValueMatch { fn eq(&self, other: &Self) -> bool { use ValueMatch::*; match (self, other) { (Bool(a), Bool(b)) => a.eq(b), (F64(a), F64(b)) => { debug_assert!(!a.is_nan()); debug_assert!(!b.is_nan()); a.eq(b) } (U64(a), U64(b)) => a.eq(b), (I64(a), I64(b)) => a.eq(b), (NaN, NaN) => true, (Pat(a), Pat(b)) => a.eq(b), _ => false, } } } impl Ord for ValueMatch { fn cmp(&self, other: &Self) -> Ordering { use ValueMatch::*; match (self, other) { (Bool(this), Bool(that)) => this.cmp(that), (Bool(_), _) => Ordering::Less, (F64(this), F64(that)) => this .partial_cmp(that) .expect("`ValueMatch::F64` may not contain `NaN` values"), (F64(_), Bool(_)) => Ordering::Greater, (F64(_), _) => Ordering::Less, (NaN, NaN) => Ordering::Equal, (NaN, Bool(_)) | (NaN, F64(_)) => Ordering::Greater, (NaN, _) => Ordering::Less, (U64(this), U64(that)) => this.cmp(that), (U64(_), Bool(_)) | (U64(_), F64(_)) | (U64(_), NaN) => Ordering::Greater, (U64(_), _) => Ordering::Less, (I64(this), I64(that)) => this.cmp(that), (I64(_), Bool(_)) | (I64(_), F64(_)) | (I64(_), NaN) | (I64(_), U64(_)) => { Ordering::Greater } (I64(_), _) => Ordering::Less, (Pat(this), Pat(that)) => this.cmp(that), (Pat(_), _) => Ordering::Greater, (Debug(this), Debug(that)) => this.cmp(that), (Debug(_), _) => Ordering::Greater, } } } impl PartialOrd for ValueMatch { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } /// Matches a field's `fmt::Debug` output against a regular expression pattern. /// /// This is used for matching all non-literal field value filters when regular /// expressions are enabled. #[derive(Debug, Clone)] pub(crate) struct MatchPattern { pub(crate) matcher: Pattern, pattern: Arc, } /// Matches a field's `fmt::Debug` output against a fixed string pattern. /// /// This is used for matching all non-literal field value filters when regular /// expressions are disabled. #[derive(Debug, Clone)] pub(crate) struct MatchDebug { pattern: Arc, } /// Indicates that a field name specified in a filter directive was invalid. #[derive(Clone, Debug)] #[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))] pub struct BadName { name: String, } // === impl Match === impl Match { pub(crate) fn has_value(&self) -> bool { self.value.is_some() } // TODO: reference count these strings? pub(crate) fn name(&self) -> String { self.name.clone() } pub(crate) fn parse(s: &str, regex: bool) -> Result> { let mut parts = s.split('='); let name = parts .next() .ok_or_else(|| BadName { name: "".to_string(), })? // TODO: validate field name .to_string(); let value = parts .next() .map(|part| match regex { true => ValueMatch::parse_regex(part), false => Ok(ValueMatch::parse_non_regex(part)), }) .transpose()?; Ok(Match { name, value }) } } impl fmt::Display for Match { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.name, f)?; if let Some(ref value) = self.value { write!(f, "={}", value)?; } Ok(()) } } impl Ord for Match { fn cmp(&self, other: &Self) -> Ordering { // Ordering for `Match` directives is based first on _whether_ a value // is matched or not. This is semantically meaningful --- we would // prefer to check directives that match values first as they are more // specific. let has_value = match (self.value.as_ref(), other.value.as_ref()) { (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, _ => Ordering::Equal, }; // If both directives match a value, we fall back to the field names in // length + lexicographic ordering, and if these are equal as well, we // compare the match directives. // // This ordering is no longer semantically meaningful but is necessary // so that the directives can be stored in the `BTreeMap` in a defined // order. has_value .then_with(|| self.name.cmp(&other.name)) .then_with(|| self.value.cmp(&other.value)) } } impl PartialOrd for Match { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } // === impl ValueMatch === fn value_match_f64(v: f64) -> ValueMatch { if v.is_nan() { ValueMatch::NaN } else { ValueMatch::F64(v) } } impl ValueMatch { /// Parse a `ValueMatch` that will match `fmt::Debug` fields using regular /// expressions. /// /// This returns an error if the string didn't contain a valid `bool`, /// `u64`, `i64`, or `f64` literal, and couldn't be parsed as a regular /// expression. fn parse_regex(s: &str) -> Result { s.parse::() .map(ValueMatch::Bool) .or_else(|_| s.parse::().map(ValueMatch::U64)) .or_else(|_| s.parse::().map(ValueMatch::I64)) .or_else(|_| s.parse::().map(value_match_f64)) .or_else(|_| { s.parse::() .map(|p| ValueMatch::Pat(Box::new(p))) }) } /// Parse a `ValueMatch` that will match `fmt::Debug` against a fixed /// string. /// /// This does *not* return an error, because any string that isn't a valid /// `bool`, `u64`, `i64`, or `f64` literal is treated as expected /// `fmt::Debug` output. fn parse_non_regex(s: &str) -> Self { s.parse::() .map(ValueMatch::Bool) .or_else(|_| s.parse::().map(ValueMatch::U64)) .or_else(|_| s.parse::().map(ValueMatch::I64)) .or_else(|_| s.parse::().map(value_match_f64)) .unwrap_or_else(|_| ValueMatch::Debug(MatchDebug::new(s))) } } impl fmt::Display for ValueMatch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ValueMatch::Bool(ref inner) => fmt::Display::fmt(inner, f), ValueMatch::F64(ref inner) => fmt::Display::fmt(inner, f), ValueMatch::NaN => fmt::Display::fmt(&std::f64::NAN, f), ValueMatch::I64(ref inner) => fmt::Display::fmt(inner, f), ValueMatch::U64(ref inner) => fmt::Display::fmt(inner, f), ValueMatch::Debug(ref inner) => fmt::Display::fmt(inner, f), ValueMatch::Pat(ref inner) => fmt::Display::fmt(inner, f), } } } // === impl MatchPattern === impl FromStr for MatchPattern { type Err = matchers::Error; fn from_str(s: &str) -> Result { let matcher = s.parse::()?; Ok(Self { matcher, pattern: s.to_owned().into(), }) } } impl fmt::Display for MatchPattern { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&*self.pattern, f) } } impl AsRef for MatchPattern { #[inline] fn as_ref(&self) -> &str { self.pattern.as_ref() } } impl MatchPattern { #[inline] fn str_matches(&self, s: &impl AsRef) -> bool { self.matcher.matches(s) } #[inline] fn debug_matches(&self, d: &impl fmt::Debug) -> bool { self.matcher.debug_matches(d) } pub(super) fn into_debug_match(self) -> MatchDebug { MatchDebug { pattern: self.pattern, } } } impl PartialEq for MatchPattern { #[inline] fn eq(&self, other: &Self) -> bool { self.pattern == other.pattern } } impl Eq for MatchPattern {} impl PartialOrd for MatchPattern { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.pattern.cmp(&other.pattern)) } } impl Ord for MatchPattern { #[inline] fn cmp(&self, other: &Self) -> Ordering { self.pattern.cmp(&other.pattern) } } // === impl MatchDebug === impl MatchDebug { fn new(s: &str) -> Self { Self { pattern: s.to_owned().into(), } } #[inline] fn debug_matches(&self, d: &impl fmt::Debug) -> bool { // Naively, we would probably match a value's `fmt::Debug` output by // formatting it to a string, and then checking if the string is equal // to the expected pattern. However, this would require allocating every // time we want to match a field value against a `Debug` matcher, which // can be avoided. // // Instead, we implement `fmt::Write` for a type that, rather than // actually _writing_ the strings to something, matches them against the // expected pattern, and returns an error if the pattern does not match. struct Matcher<'a> { pattern: &'a str, } impl fmt::Write for Matcher<'_> { fn write_str(&mut self, s: &str) -> fmt::Result { // If the string is longer than the remaining expected string, // we know it won't match, so bail. if s.len() > self.pattern.len() { return Err(fmt::Error); } // If the expected string begins with the string that was // written, we are still potentially a match. Advance the // position in the expected pattern to chop off the matched // output, and continue. if self.pattern.starts_with(s) { self.pattern = &self.pattern[s.len()..]; return Ok(()); } // Otherwise, the expected string doesn't include the string // that was written at the current position, so the `fmt::Debug` // output doesn't match! Return an error signalling that this // doesn't match. Err(fmt::Error) } } let mut matcher = Matcher { pattern: &self.pattern, }; // Try to "write" the value's `fmt::Debug` output to a `Matcher`. This // returns an error if the `fmt::Debug` implementation wrote any // characters that did not match the expected pattern. write!(matcher, "{:?}", d).is_ok() } } impl fmt::Display for MatchDebug { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&*self.pattern, f) } } impl AsRef for MatchDebug { #[inline] fn as_ref(&self) -> &str { self.pattern.as_ref() } } impl PartialEq for MatchDebug { #[inline] fn eq(&self, other: &Self) -> bool { self.pattern == other.pattern } } impl Eq for MatchDebug {} impl PartialOrd for MatchDebug { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.pattern.cmp(&other.pattern)) } } impl Ord for MatchDebug { #[inline] fn cmp(&self, other: &Self) -> Ordering { self.pattern.cmp(&other.pattern) } } // === impl BadName === impl Error for BadName {} impl fmt::Display for BadName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "invalid field name `{}`", self.name) } } impl CallsiteMatch { pub(crate) fn to_span_match(&self) -> SpanMatch { let fields = self .fields .iter() .map(|(k, v)| (k.clone(), (v.clone(), AtomicBool::new(false)))) .collect(); SpanMatch { fields, level: self.level, has_matched: AtomicBool::new(false), } } } impl SpanMatch { pub(crate) fn visitor(&self) -> MatchVisitor<'_> { MatchVisitor { inner: self } } #[inline] pub(crate) fn is_matched(&self) -> bool { if self.has_matched.load(Acquire) { return true; } self.is_matched_slow() } #[inline(never)] fn is_matched_slow(&self) -> bool { let matched = self .fields .values() .all(|(_, matched)| matched.load(Acquire)); if matched { self.has_matched.store(true, Release); } matched } #[inline] pub(crate) fn filter(&self) -> Option { if self.is_matched() { Some(self.level) } else { None } } } impl<'a> Visit for MatchVisitor<'a> { fn record_f64(&mut self, field: &Field, value: f64) { match self.inner.fields.get(field) { Some((ValueMatch::NaN, ref matched)) if value.is_nan() => { matched.store(true, Release); } Some((ValueMatch::F64(ref e), ref matched)) if (value - *e).abs() < std::f64::EPSILON => { matched.store(true, Release); } _ => {} } } fn record_i64(&mut self, field: &Field, value: i64) { use std::convert::TryInto; match self.inner.fields.get(field) { Some((ValueMatch::I64(ref e), ref matched)) if value == *e => { matched.store(true, Release); } Some((ValueMatch::U64(ref e), ref matched)) if Ok(value) == (*e).try_into() => { matched.store(true, Release); } _ => {} } } fn record_u64(&mut self, field: &Field, value: u64) { match self.inner.fields.get(field) { Some((ValueMatch::U64(ref e), ref matched)) if value == *e => { matched.store(true, Release); } _ => {} } } fn record_bool(&mut self, field: &Field, value: bool) { match self.inner.fields.get(field) { Some((ValueMatch::Bool(ref e), ref matched)) if value == *e => { matched.store(true, Release); } _ => {} } } fn record_str(&mut self, field: &Field, value: &str) { match self.inner.fields.get(field) { Some((ValueMatch::Pat(ref e), ref matched)) if e.str_matches(&value) => { matched.store(true, Release); } Some((ValueMatch::Debug(ref e), ref matched)) if e.debug_matches(&value) => { matched.store(true, Release) } _ => {} } } fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { match self.inner.fields.get(field) { Some((ValueMatch::Pat(ref e), ref matched)) if e.debug_matches(&value) => { matched.store(true, Release); } Some((ValueMatch::Debug(ref e), ref matched)) if e.debug_matches(&value) => { matched.store(true, Release) } _ => {} } } } #[cfg(test)] mod tests { use super::*; #[derive(Debug)] #[allow(dead_code)] struct MyStruct { answer: usize, question: &'static str, } #[test] fn debug_struct_match() { let my_struct = MyStruct { answer: 42, question: "life, the universe, and everything", }; let pattern = "MyStruct { answer: 42, question: \"life, the universe, and everything\" }"; assert_eq!( format!("{:?}", my_struct), pattern, "`MyStruct`'s `Debug` impl doesn't output the expected string" ); let matcher = MatchDebug { pattern: pattern.into(), }; assert!(matcher.debug_matches(&my_struct)) } #[test] fn debug_struct_not_match() { let my_struct = MyStruct { answer: 42, question: "what shall we have for lunch?", }; let pattern = "MyStruct { answer: 42, question: \"life, the universe, and everything\" }"; assert_eq!( format!("{:?}", my_struct), "MyStruct { answer: 42, question: \"what shall we have for lunch?\" }", "`MyStruct`'s `Debug` impl doesn't output the expected string" ); let matcher = MatchDebug { pattern: pattern.into(), }; assert!(!matcher.debug_matches(&my_struct)) } }