summaryrefslogtreecommitdiffstats
path: root/vendor/tracing-subscriber/src/filter/env/field.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/tracing-subscriber/src/filter/env/field.rs')
-rw-r--r--vendor/tracing-subscriber/src/filter/env/field.rs626
1 files changed, 626 insertions, 0 deletions
diff --git a/vendor/tracing-subscriber/src/filter/env/field.rs b/vendor/tracing-subscriber/src/filter/env/field.rs
new file mode 100644
index 000000000..1394fd04a
--- /dev/null
+++ b/vendor/tracing-subscriber/src/filter/env/field.rs
@@ -0,0 +1,626 @@
+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))
+ }
+}