tracing_subscriber/filter/env/
field.rs

1use alloc::{
2    borrow::ToOwned,
3    boxed::Box,
4    string::{String, ToString},
5    sync::Arc,
6};
7use core::{
8    cmp::Ordering,
9    fmt::{self, Write},
10    str::FromStr,
11    sync::atomic::{AtomicBool, Ordering::*},
12};
13use matchers::Pattern;
14use std::error::Error;
15
16use super::{FieldMap, LevelFilter};
17use tracing_core::field::{Field, Visit};
18
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub(crate) struct Match {
21    pub(crate) name: String, // TODO: allow match patterns for names?
22    pub(crate) value: Option<ValueMatch>,
23}
24
25#[derive(Debug, Eq, PartialEq)]
26pub(crate) struct CallsiteMatch {
27    pub(crate) fields: FieldMap<ValueMatch>,
28    pub(crate) level: LevelFilter,
29}
30
31#[derive(Debug)]
32pub(crate) struct SpanMatch {
33    fields: FieldMap<(ValueMatch, AtomicBool)>,
34    level: LevelFilter,
35    has_matched: AtomicBool,
36}
37
38pub(crate) struct MatchVisitor<'a> {
39    inner: &'a SpanMatch,
40}
41
42#[derive(Debug, Clone)]
43pub(crate) enum ValueMatch {
44    /// Matches a specific `bool` value.
45    Bool(bool),
46    /// Matches a specific `f64` value.
47    F64(f64),
48    /// Matches a specific `u64` value.
49    U64(u64),
50    /// Matches a specific `i64` value.
51    I64(i64),
52    /// Matches any `NaN` `f64` value.
53    NaN,
54    /// Matches any field whose `fmt::Debug` output is equal to a fixed string.
55    Debug(MatchDebug),
56    /// Matches any field whose `fmt::Debug` output matches a regular expression
57    /// pattern.
58    Pat(Box<MatchPattern>),
59}
60
61impl Eq for ValueMatch {}
62
63impl PartialEq for ValueMatch {
64    fn eq(&self, other: &Self) -> bool {
65        use ValueMatch::*;
66        match (self, other) {
67            (Bool(a), Bool(b)) => a.eq(b),
68            (F64(a), F64(b)) => {
69                debug_assert!(!a.is_nan());
70                debug_assert!(!b.is_nan());
71
72                a.eq(b)
73            }
74            (U64(a), U64(b)) => a.eq(b),
75            (I64(a), I64(b)) => a.eq(b),
76            (NaN, NaN) => true,
77            (Pat(a), Pat(b)) => a.eq(b),
78            _ => false,
79        }
80    }
81}
82
83impl Ord for ValueMatch {
84    fn cmp(&self, other: &Self) -> Ordering {
85        use ValueMatch::*;
86        match (self, other) {
87            (Bool(this), Bool(that)) => this.cmp(that),
88            (Bool(_), _) => Ordering::Less,
89
90            (F64(this), F64(that)) => this
91                .partial_cmp(that)
92                .expect("`ValueMatch::F64` may not contain `NaN` values"),
93            (F64(_), Bool(_)) => Ordering::Greater,
94            (F64(_), _) => Ordering::Less,
95
96            (NaN, NaN) => Ordering::Equal,
97            (NaN, Bool(_)) | (NaN, F64(_)) => Ordering::Greater,
98            (NaN, _) => Ordering::Less,
99
100            (U64(this), U64(that)) => this.cmp(that),
101            (U64(_), Bool(_)) | (U64(_), F64(_)) | (U64(_), NaN) => Ordering::Greater,
102            (U64(_), _) => Ordering::Less,
103
104            (I64(this), I64(that)) => this.cmp(that),
105            (I64(_), Bool(_)) | (I64(_), F64(_)) | (I64(_), NaN) | (I64(_), U64(_)) => {
106                Ordering::Greater
107            }
108            (I64(_), _) => Ordering::Less,
109
110            (Pat(this), Pat(that)) => this.cmp(that),
111            (Pat(_), _) => Ordering::Greater,
112
113            (Debug(this), Debug(that)) => this.cmp(that),
114            (Debug(_), _) => Ordering::Greater,
115        }
116    }
117}
118
119impl PartialOrd for ValueMatch {
120    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
121        Some(self.cmp(other))
122    }
123}
124
125/// Matches a field's `fmt::Debug` output against a regular expression pattern.
126///
127/// This is used for matching all non-literal field value filters when regular
128/// expressions are enabled.
129#[derive(Debug, Clone)]
130pub(crate) struct MatchPattern {
131    pub(crate) matcher: Pattern,
132    pattern: Arc<str>,
133}
134
135/// Matches a field's `fmt::Debug` output against a fixed string pattern.
136///
137/// This is used for matching all non-literal field value filters when regular
138/// expressions are disabled.
139#[derive(Debug, Clone)]
140pub(crate) struct MatchDebug {
141    pattern: Arc<str>,
142}
143
144/// Indicates that a field name specified in a filter directive was invalid.
145#[derive(Clone, Debug)]
146#[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))]
147pub struct BadName {
148    name: String,
149}
150
151// === impl Match ===
152
153impl Match {
154    pub(crate) fn has_value(&self) -> bool {
155        self.value.is_some()
156    }
157
158    // TODO: reference count these strings?
159    pub(crate) fn name(&self) -> String {
160        self.name.clone()
161    }
162
163    pub(crate) fn parse(s: &str, regex: bool) -> Result<Self, Box<dyn Error + Send + Sync>> {
164        let mut parts = s.split('=');
165        let name = parts
166            .next()
167            .ok_or_else(|| BadName {
168                name: "".to_string(),
169            })?
170            // TODO: validate field name
171            .to_string();
172        let value = parts
173            .next()
174            .map(|part| match regex {
175                true => ValueMatch::parse_regex(part),
176                false => Ok(ValueMatch::parse_non_regex(part)),
177            })
178            .transpose()?;
179        Ok(Match { name, value })
180    }
181}
182
183impl fmt::Display for Match {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        fmt::Display::fmt(&self.name, f)?;
186        if let Some(ref value) = self.value {
187            write!(f, "={}", value)?;
188        }
189        Ok(())
190    }
191}
192
193impl Ord for Match {
194    fn cmp(&self, other: &Self) -> Ordering {
195        // Ordering for `Match` directives is based first on _whether_ a value
196        // is matched or not. This is semantically meaningful --- we would
197        // prefer to check directives that match values first as they are more
198        // specific.
199        let has_value = match (self.value.as_ref(), other.value.as_ref()) {
200            (Some(_), None) => Ordering::Greater,
201            (None, Some(_)) => Ordering::Less,
202            _ => Ordering::Equal,
203        };
204        // If both directives match a value, we fall back to the field names in
205        // length + lexicographic ordering, and if these are equal as well, we
206        // compare the match directives.
207        //
208        // This ordering is no longer semantically meaningful but is necessary
209        // so that the directives can be stored in the `BTreeMap` in a defined
210        // order.
211        has_value
212            .then_with(|| self.name.cmp(&other.name))
213            .then_with(|| self.value.cmp(&other.value))
214    }
215}
216
217impl PartialOrd for Match {
218    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
219        Some(self.cmp(other))
220    }
221}
222
223// === impl ValueMatch ===
224
225fn value_match_f64(v: f64) -> ValueMatch {
226    if v.is_nan() {
227        ValueMatch::NaN
228    } else {
229        ValueMatch::F64(v)
230    }
231}
232
233impl ValueMatch {
234    /// Parse a `ValueMatch` that will match `fmt::Debug` fields using regular
235    /// expressions.
236    ///
237    /// This returns an error if the string didn't contain a valid `bool`,
238    /// `u64`, `i64`, or `f64` literal, and couldn't be parsed as a regular
239    /// expression.
240    #[allow(clippy::result_large_err)]
241    fn parse_regex(s: &str) -> Result<Self, matchers::BuildError> {
242        s.parse::<bool>()
243            .map(ValueMatch::Bool)
244            .or_else(|_| s.parse::<u64>().map(ValueMatch::U64))
245            .or_else(|_| s.parse::<i64>().map(ValueMatch::I64))
246            .or_else(|_| s.parse::<f64>().map(value_match_f64))
247            .or_else(|_| {
248                s.parse::<MatchPattern>()
249                    .map(|p| ValueMatch::Pat(Box::new(p)))
250            })
251    }
252
253    /// Parse a `ValueMatch` that will match `fmt::Debug` against a fixed
254    /// string.
255    ///
256    /// This does *not* return an error, because any string that isn't a valid
257    /// `bool`, `u64`, `i64`, or `f64` literal is treated as expected
258    /// `fmt::Debug` output.
259    fn parse_non_regex(s: &str) -> Self {
260        s.parse::<bool>()
261            .map(ValueMatch::Bool)
262            .or_else(|_| s.parse::<u64>().map(ValueMatch::U64))
263            .or_else(|_| s.parse::<i64>().map(ValueMatch::I64))
264            .or_else(|_| s.parse::<f64>().map(value_match_f64))
265            .unwrap_or_else(|_| ValueMatch::Debug(MatchDebug::new(s)))
266    }
267}
268
269impl fmt::Display for ValueMatch {
270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271        match self {
272            ValueMatch::Bool(ref inner) => fmt::Display::fmt(inner, f),
273            ValueMatch::F64(ref inner) => fmt::Display::fmt(inner, f),
274            ValueMatch::NaN => fmt::Display::fmt(&f64::NAN, f),
275            ValueMatch::I64(ref inner) => fmt::Display::fmt(inner, f),
276            ValueMatch::U64(ref inner) => fmt::Display::fmt(inner, f),
277            ValueMatch::Debug(ref inner) => fmt::Display::fmt(inner, f),
278            ValueMatch::Pat(ref inner) => fmt::Display::fmt(inner, f),
279        }
280    }
281}
282
283// === impl MatchPattern ===
284
285impl FromStr for MatchPattern {
286    type Err = matchers::BuildError;
287    fn from_str(s: &str) -> Result<Self, Self::Err> {
288        let matcher = s.parse::<Pattern>()?;
289        Ok(Self {
290            matcher,
291            pattern: s.to_owned().into(),
292        })
293    }
294}
295
296impl fmt::Display for MatchPattern {
297    #[inline]
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        fmt::Display::fmt(&*self.pattern, f)
300    }
301}
302
303impl AsRef<str> for MatchPattern {
304    #[inline]
305    fn as_ref(&self) -> &str {
306        self.pattern.as_ref()
307    }
308}
309
310impl MatchPattern {
311    #[inline]
312    fn str_matches(&self, s: &impl AsRef<str>) -> bool {
313        self.matcher.matches(s)
314    }
315
316    #[inline]
317    fn debug_matches(&self, d: &impl fmt::Debug) -> bool {
318        self.matcher.debug_matches(d)
319    }
320
321    pub(super) fn into_debug_match(self) -> MatchDebug {
322        MatchDebug {
323            pattern: self.pattern,
324        }
325    }
326}
327
328impl PartialEq for MatchPattern {
329    #[inline]
330    fn eq(&self, other: &Self) -> bool {
331        self.pattern == other.pattern
332    }
333}
334
335impl Eq for MatchPattern {}
336
337impl PartialOrd for MatchPattern {
338    #[inline]
339    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
340        Some(self.cmp(other))
341    }
342}
343
344impl Ord for MatchPattern {
345    #[inline]
346    fn cmp(&self, other: &Self) -> Ordering {
347        self.pattern.cmp(&other.pattern)
348    }
349}
350
351// === impl MatchDebug ===
352
353impl MatchDebug {
354    fn new(s: &str) -> Self {
355        Self {
356            pattern: s.to_owned().into(),
357        }
358    }
359
360    #[inline]
361    fn debug_matches(&self, d: &impl fmt::Debug) -> bool {
362        // Naively, we would probably match a value's `fmt::Debug` output by
363        // formatting it to a string, and then checking if the string is equal
364        // to the expected pattern. However, this would require allocating every
365        // time we want to match a field value against a `Debug` matcher, which
366        // can be avoided.
367        //
368        // Instead, we implement `fmt::Write` for a type that, rather than
369        // actually _writing_ the strings to something, matches them against the
370        // expected pattern, and returns an error if the pattern does not match.
371        struct Matcher<'a> {
372            pattern: &'a str,
373        }
374
375        impl fmt::Write for Matcher<'_> {
376            fn write_str(&mut self, s: &str) -> fmt::Result {
377                // If the string is longer than the remaining expected string,
378                // we know it won't match, so bail.
379                if s.len() > self.pattern.len() {
380                    return Err(fmt::Error);
381                }
382
383                // If the expected string begins with the string that was
384                // written, we are still potentially a match. Advance the
385                // position in the expected pattern to chop off the matched
386                // output, and continue.
387                if self.pattern.starts_with(s) {
388                    self.pattern = &self.pattern[s.len()..];
389                    return Ok(());
390                }
391
392                // Otherwise, the expected string doesn't include the string
393                // that was written at the current position, so the `fmt::Debug`
394                // output doesn't match! Return an error signalling that this
395                // doesn't match.
396                Err(fmt::Error)
397            }
398        }
399        let mut matcher = Matcher {
400            pattern: &self.pattern,
401        };
402
403        // Try to "write" the value's `fmt::Debug` output to a `Matcher`. This
404        // returns an error if the `fmt::Debug` implementation wrote any
405        // characters that did not match the expected pattern.
406        write!(matcher, "{:?}", d).is_ok()
407    }
408}
409
410impl fmt::Display for MatchDebug {
411    #[inline]
412    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413        fmt::Display::fmt(&*self.pattern, f)
414    }
415}
416
417impl AsRef<str> for MatchDebug {
418    #[inline]
419    fn as_ref(&self) -> &str {
420        self.pattern.as_ref()
421    }
422}
423
424impl PartialEq for MatchDebug {
425    #[inline]
426    fn eq(&self, other: &Self) -> bool {
427        self.pattern == other.pattern
428    }
429}
430
431impl Eq for MatchDebug {}
432
433impl PartialOrd for MatchDebug {
434    #[inline]
435    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
436        Some(self.cmp(other))
437    }
438}
439
440impl Ord for MatchDebug {
441    #[inline]
442    fn cmp(&self, other: &Self) -> Ordering {
443        self.pattern.cmp(&other.pattern)
444    }
445}
446
447// === impl BadName ===
448
449impl Error for BadName {}
450
451impl fmt::Display for BadName {
452    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453        write!(f, "invalid field name `{}`", self.name)
454    }
455}
456
457impl CallsiteMatch {
458    pub(crate) fn to_span_match(&self) -> SpanMatch {
459        let fields = self
460            .fields
461            .iter()
462            .map(|(k, v)| (k.clone(), (v.clone(), AtomicBool::new(false))))
463            .collect();
464        SpanMatch {
465            fields,
466            level: self.level,
467            has_matched: AtomicBool::new(false),
468        }
469    }
470}
471
472impl SpanMatch {
473    pub(crate) fn visitor(&self) -> MatchVisitor<'_> {
474        MatchVisitor { inner: self }
475    }
476
477    #[inline]
478    pub(crate) fn is_matched(&self) -> bool {
479        if self.has_matched.load(Acquire) {
480            return true;
481        }
482        self.is_matched_slow()
483    }
484
485    #[inline(never)]
486    fn is_matched_slow(&self) -> bool {
487        let matched = self
488            .fields
489            .values()
490            .all(|(_, matched)| matched.load(Acquire));
491        if matched {
492            self.has_matched.store(true, Release);
493        }
494        matched
495    }
496
497    #[inline]
498    pub(crate) fn filter(&self) -> Option<LevelFilter> {
499        if self.is_matched() {
500            Some(self.level)
501        } else {
502            None
503        }
504    }
505}
506
507impl Visit for MatchVisitor<'_> {
508    fn record_f64(&mut self, field: &Field, value: f64) {
509        match self.inner.fields.get(field) {
510            Some((ValueMatch::NaN, ref matched)) if value.is_nan() => {
511                matched.store(true, Release);
512            }
513            Some((ValueMatch::F64(ref e), ref matched)) if (value - *e).abs() < f64::EPSILON => {
514                matched.store(true, Release);
515            }
516            _ => {}
517        }
518    }
519
520    fn record_i64(&mut self, field: &Field, value: i64) {
521        use std::convert::TryInto;
522
523        match self.inner.fields.get(field) {
524            Some((ValueMatch::I64(ref e), ref matched)) if value == *e => {
525                matched.store(true, Release);
526            }
527            Some((ValueMatch::U64(ref e), ref matched)) if Ok(value) == (*e).try_into() => {
528                matched.store(true, Release);
529            }
530            _ => {}
531        }
532    }
533
534    fn record_u64(&mut self, field: &Field, value: u64) {
535        match self.inner.fields.get(field) {
536            Some((ValueMatch::U64(ref e), ref matched)) if value == *e => {
537                matched.store(true, Release);
538            }
539            _ => {}
540        }
541    }
542
543    fn record_bool(&mut self, field: &Field, value: bool) {
544        match self.inner.fields.get(field) {
545            Some((ValueMatch::Bool(ref e), ref matched)) if value == *e => {
546                matched.store(true, Release);
547            }
548            _ => {}
549        }
550    }
551
552    fn record_str(&mut self, field: &Field, value: &str) {
553        match self.inner.fields.get(field) {
554            Some((ValueMatch::Pat(ref e), ref matched)) if e.str_matches(&value) => {
555                matched.store(true, Release);
556            }
557            Some((ValueMatch::Debug(ref e), ref matched)) if e.debug_matches(&value) => {
558                matched.store(true, Release)
559            }
560            _ => {}
561        }
562    }
563
564    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
565        match self.inner.fields.get(field) {
566            Some((ValueMatch::Pat(ref e), ref matched)) if e.debug_matches(&value) => {
567                matched.store(true, Release);
568            }
569            Some((ValueMatch::Debug(ref e), ref matched)) if e.debug_matches(&value) => {
570                matched.store(true, Release)
571            }
572            _ => {}
573        }
574    }
575}
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580    use alloc::format;
581
582    #[derive(Debug)]
583    #[allow(dead_code)]
584    struct MyStruct {
585        answer: usize,
586        question: &'static str,
587    }
588
589    #[test]
590    fn debug_struct_match() {
591        let my_struct = MyStruct {
592            answer: 42,
593            question: "life, the universe, and everything",
594        };
595
596        let pattern = "MyStruct { answer: 42, question: \"life, the universe, and everything\" }";
597
598        assert_eq!(
599            format!("{:?}", my_struct),
600            pattern,
601            "`MyStruct`'s `Debug` impl doesn't output the expected string"
602        );
603
604        let matcher = MatchDebug {
605            pattern: pattern.into(),
606        };
607        assert!(matcher.debug_matches(&my_struct))
608    }
609
610    #[test]
611    fn debug_struct_not_match() {
612        let my_struct = MyStruct {
613            answer: 42,
614            question: "what shall we have for lunch?",
615        };
616
617        let pattern = "MyStruct { answer: 42, question: \"life, the universe, and everything\" }";
618
619        assert_eq!(
620            format!("{:?}", my_struct),
621            "MyStruct { answer: 42, question: \"what shall we have for lunch?\" }",
622            "`MyStruct`'s `Debug` impl doesn't output the expected string"
623        );
624
625        let matcher = MatchDebug {
626            pattern: pattern.into(),
627        };
628        assert!(!matcher.debug_matches(&my_struct))
629    }
630}