| 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<ValueMatch>, |
| } |
| |
| #[derive(Debug, Eq, PartialEq)] |
| pub(crate) struct CallsiteMatch { |
| pub(crate) fields: FieldMap<ValueMatch>, |
| 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<MatchPattern>), |
| } |
| |
| 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<Ordering> { |
| 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<str>, |
| } |
| |
| /// 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<str>, |
| } |
| |
| /// 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<Self, Box<dyn Error + Send + Sync>> { |
| 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<Ordering> { |
| 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<Self, matchers::Error> { |
| s.parse::<bool>() |
| .map(ValueMatch::Bool) |
| .or_else(|_| s.parse::<u64>().map(ValueMatch::U64)) |
| .or_else(|_| s.parse::<i64>().map(ValueMatch::I64)) |
| .or_else(|_| s.parse::<f64>().map(value_match_f64)) |
| .or_else(|_| { |
| s.parse::<MatchPattern>() |
| .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::<bool>() |
| .map(ValueMatch::Bool) |
| .or_else(|_| s.parse::<u64>().map(ValueMatch::U64)) |
| .or_else(|_| s.parse::<i64>().map(ValueMatch::I64)) |
| .or_else(|_| s.parse::<f64>().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<Self, Self::Err> { |
| let matcher = s.parse::<Pattern>()?; |
| 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<str> for MatchPattern { |
| #[inline] |
| fn as_ref(&self) -> &str { |
| self.pattern.as_ref() |
| } |
| } |
| |
| impl MatchPattern { |
| #[inline] |
| fn str_matches(&self, s: &impl AsRef<str>) -> 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<Ordering> { |
| 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<str> 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<Ordering> { |
| 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<LevelFilter> { |
| 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)) |
| } |
| } |