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        #[allow(clippy::result_large_err)]
173        let value = parts
174            .next()
175            .map(|part| match regex {
176                true => ValueMatch::parse_regex(part),
177                false => Ok(ValueMatch::parse_non_regex(part)),
178            })
179            .transpose()?;
180        Ok(Match { name, value })
181    }
182}
183
184impl fmt::Display for Match {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        fmt::Display::fmt(&self.name, f)?;
187        if let Some(ref value) = self.value {
188            write!(f, "={}", value)?;
189        }
190        Ok(())
191    }
192}
193
194impl Ord for Match {
195    fn cmp(&self, other: &Self) -> Ordering {
196        // Ordering for `Match` directives is based first on _whether_ a value
197        // is matched or not. This is semantically meaningful --- we would
198        // prefer to check directives that match values first as they are more
199        // specific.
200        let has_value = match (self.value.as_ref(), other.value.as_ref()) {
201            (Some(_), None) => Ordering::Greater,
202            (None, Some(_)) => Ordering::Less,
203            _ => Ordering::Equal,
204        };
205        // If both directives match a value, we fall back to the field names in
206        // length + lexicographic ordering, and if these are equal as well, we
207        // compare the match directives.
208        //
209        // This ordering is no longer semantically meaningful but is necessary
210        // so that the directives can be stored in the `BTreeMap` in a defined
211        // order.
212        has_value
213            .then_with(|| self.name.cmp(&other.name))
214            .then_with(|| self.value.cmp(&other.value))
215    }
216}
217
218impl PartialOrd for Match {
219    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
220        Some(self.cmp(other))
221    }
222}
223
224// === impl ValueMatch ===
225
226fn value_match_f64(v: f64) -> ValueMatch {
227    if v.is_nan() {
228        ValueMatch::NaN
229    } else {
230        ValueMatch::F64(v)
231    }
232}
233
234impl ValueMatch {
235    /// Parse a `ValueMatch` that will match `fmt::Debug` fields using regular
236    /// expressions.
237    ///
238    /// This returns an error if the string didn't contain a valid `bool`,
239    /// `u64`, `i64`, or `f64` literal, and couldn't be parsed as a regular
240    /// expression.
241    #[allow(clippy::result_large_err)]
242    fn parse_regex(s: &str) -> Result<Self, matchers::BuildError> {
243        s.parse::<bool>()
244            .map(ValueMatch::Bool)
245            .or_else(|_| s.parse::<u64>().map(ValueMatch::U64))
246            .or_else(|_| s.parse::<i64>().map(ValueMatch::I64))
247            .or_else(|_| s.parse::<f64>().map(value_match_f64))
248            .or_else(|_| {
249                s.parse::<MatchPattern>()
250                    .map(|p| ValueMatch::Pat(Box::new(p)))
251            })
252    }
253
254    /// Parse a `ValueMatch` that will match `fmt::Debug` against a fixed
255    /// string.
256    ///
257    /// This does *not* return an error, because any string that isn't a valid
258    /// `bool`, `u64`, `i64`, or `f64` literal is treated as expected
259    /// `fmt::Debug` output.
260    fn parse_non_regex(s: &str) -> Self {
261        s.parse::<bool>()
262            .map(ValueMatch::Bool)
263            .or_else(|_| s.parse::<u64>().map(ValueMatch::U64))
264            .or_else(|_| s.parse::<i64>().map(ValueMatch::I64))
265            .or_else(|_| s.parse::<f64>().map(value_match_f64))
266            .unwrap_or_else(|_| ValueMatch::Debug(MatchDebug::new(s)))
267    }
268}
269
270impl fmt::Display for ValueMatch {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        match self {
273            ValueMatch::Bool(ref inner) => fmt::Display::fmt(inner, f),
274            ValueMatch::F64(ref inner) => fmt::Display::fmt(inner, f),
275            ValueMatch::NaN => fmt::Display::fmt(&f64::NAN, f),
276            ValueMatch::I64(ref inner) => fmt::Display::fmt(inner, f),
277            ValueMatch::U64(ref inner) => fmt::Display::fmt(inner, f),
278            ValueMatch::Debug(ref inner) => fmt::Display::fmt(inner, f),
279            ValueMatch::Pat(ref inner) => fmt::Display::fmt(inner, f),
280        }
281    }
282}
283
284// === impl MatchPattern ===
285
286impl FromStr for MatchPattern {
287    type Err = matchers::BuildError;
288    fn from_str(s: &str) -> Result<Self, Self::Err> {
289        let matcher = s.parse::<Pattern>()?;
290        Ok(Self {
291            matcher,
292            pattern: s.to_owned().into(),
293        })
294    }
295}
296
297impl fmt::Display for MatchPattern {
298    #[inline]
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        fmt::Display::fmt(&*self.pattern, f)
301    }
302}
303
304impl AsRef<str> for MatchPattern {
305    #[inline]
306    fn as_ref(&self) -> &str {
307        self.pattern.as_ref()
308    }
309}
310
311impl MatchPattern {
312    #[inline]
313    fn str_matches(&self, s: &impl AsRef<str>) -> bool {
314        self.matcher.matches(s)
315    }
316
317    #[inline]
318    fn debug_matches(&self, d: &impl fmt::Debug) -> bool {
319        self.matcher.debug_matches(d)
320    }
321
322    pub(super) fn into_debug_match(self) -> MatchDebug {
323        MatchDebug {
324            pattern: self.pattern,
325        }
326    }
327}
328
329impl PartialEq for MatchPattern {
330    #[inline]
331    fn eq(&self, other: &Self) -> bool {
332        self.pattern == other.pattern
333    }
334}
335
336impl Eq for MatchPattern {}
337
338impl PartialOrd for MatchPattern {
339    #[inline]
340    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
341        Some(self.cmp(other))
342    }
343}
344
345impl Ord for MatchPattern {
346    #[inline]
347    fn cmp(&self, other: &Self) -> Ordering {
348        self.pattern.cmp(&other.pattern)
349    }
350}
351
352// === impl MatchDebug ===
353
354impl MatchDebug {
355    fn new(s: &str) -> Self {
356        Self {
357            pattern: s.to_owned().into(),
358        }
359    }
360
361    #[inline]
362    fn debug_matches(&self, d: &impl fmt::Debug) -> bool {
363        // Naively, we would probably match a value's `fmt::Debug` output by
364        // formatting it to a string, and then checking if the string is equal
365        // to the expected pattern. However, this would require allocating every
366        // time we want to match a field value against a `Debug` matcher, which
367        // can be avoided.
368        //
369        // Instead, we implement `fmt::Write` for a type that, rather than
370        // actually _writing_ the strings to something, matches them against the
371        // expected pattern, and returns an error if the pattern does not match.
372        struct Matcher<'a> {
373            pattern: &'a str,
374        }
375
376        impl fmt::Write for Matcher<'_> {
377            fn write_str(&mut self, s: &str) -> fmt::Result {
378                // If the string is longer than the remaining expected string,
379                // we know it won't match, so bail.
380                if s.len() > self.pattern.len() {
381                    return Err(fmt::Error);
382                }
383
384                // If the expected string begins with the string that was
385                // written, we are still potentially a match. Advance the
386                // position in the expected pattern to chop off the matched
387                // output, and continue.
388                if self.pattern.starts_with(s) {
389                    self.pattern = &self.pattern[s.len()..];
390                    return Ok(());
391                }
392
393                // Otherwise, the expected string doesn't include the string
394                // that was written at the current position, so the `fmt::Debug`
395                // output doesn't match! Return an error signalling that this
396                // doesn't match.
397                Err(fmt::Error)
398            }
399        }
400        let mut matcher = Matcher {
401            pattern: &self.pattern,
402        };
403
404        // Try to "write" the value's `fmt::Debug` output to a `Matcher`. This
405        // returns an error if the `fmt::Debug` implementation wrote any
406        // characters that did not match the expected pattern.
407        write!(matcher, "{:?}", d).is_ok()
408    }
409}
410
411impl fmt::Display for MatchDebug {
412    #[inline]
413    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
414        fmt::Display::fmt(&*self.pattern, f)
415    }
416}
417
418impl AsRef<str> for MatchDebug {
419    #[inline]
420    fn as_ref(&self) -> &str {
421        self.pattern.as_ref()
422    }
423}
424
425impl PartialEq for MatchDebug {
426    #[inline]
427    fn eq(&self, other: &Self) -> bool {
428        self.pattern == other.pattern
429    }
430}
431
432impl Eq for MatchDebug {}
433
434impl PartialOrd for MatchDebug {
435    #[inline]
436    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
437        Some(self.cmp(other))
438    }
439}
440
441impl Ord for MatchDebug {
442    #[inline]
443    fn cmp(&self, other: &Self) -> Ordering {
444        self.pattern.cmp(&other.pattern)
445    }
446}
447
448// === impl BadName ===
449
450impl Error for BadName {}
451
452impl fmt::Display for BadName {
453    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454        write!(f, "invalid field name `{}`", self.name)
455    }
456}
457
458impl CallsiteMatch {
459    pub(crate) fn to_span_match(&self) -> SpanMatch {
460        let fields = self
461            .fields
462            .iter()
463            .map(|(k, v)| (k.clone(), (v.clone(), AtomicBool::new(false))))
464            .collect();
465        SpanMatch {
466            fields,
467            level: self.level,
468            has_matched: AtomicBool::new(false),
469        }
470    }
471}
472
473impl SpanMatch {
474    pub(crate) fn visitor(&self) -> MatchVisitor<'_> {
475        MatchVisitor { inner: self }
476    }
477
478    #[inline]
479    pub(crate) fn is_matched(&self) -> bool {
480        if self.has_matched.load(Acquire) {
481            return true;
482        }
483        self.is_matched_slow()
484    }
485
486    #[inline(never)]
487    fn is_matched_slow(&self) -> bool {
488        let matched = self
489            .fields
490            .values()
491            .all(|(_, matched)| matched.load(Acquire));
492        if matched {
493            self.has_matched.store(true, Release);
494        }
495        matched
496    }
497
498    #[inline]
499    pub(crate) fn filter(&self) -> Option<LevelFilter> {
500        if self.is_matched() {
501            Some(self.level)
502        } else {
503            None
504        }
505    }
506}
507
508impl Visit for MatchVisitor<'_> {
509    fn record_f64(&mut self, field: &Field, value: f64) {
510        match self.inner.fields.get(field) {
511            Some((ValueMatch::NaN, ref matched)) if value.is_nan() => {
512                matched.store(true, Release);
513            }
514            Some((ValueMatch::F64(ref e), ref matched)) if (value - *e).abs() < f64::EPSILON => {
515                matched.store(true, Release);
516            }
517            _ => {}
518        }
519    }
520
521    fn record_i64(&mut self, field: &Field, value: i64) {
522        use std::convert::TryInto;
523
524        match self.inner.fields.get(field) {
525            Some((ValueMatch::I64(ref e), ref matched)) if value == *e => {
526                matched.store(true, Release);
527            }
528            Some((ValueMatch::U64(ref e), ref matched)) if Ok(value) == (*e).try_into() => {
529                matched.store(true, Release);
530            }
531            _ => {}
532        }
533    }
534
535    fn record_u64(&mut self, field: &Field, value: u64) {
536        match self.inner.fields.get(field) {
537            Some((ValueMatch::U64(ref e), ref matched)) if value == *e => {
538                matched.store(true, Release);
539            }
540            _ => {}
541        }
542    }
543
544    fn record_bool(&mut self, field: &Field, value: bool) {
545        match self.inner.fields.get(field) {
546            Some((ValueMatch::Bool(ref e), ref matched)) if value == *e => {
547                matched.store(true, Release);
548            }
549            _ => {}
550        }
551    }
552
553    fn record_str(&mut self, field: &Field, value: &str) {
554        match self.inner.fields.get(field) {
555            Some((ValueMatch::Pat(ref e), ref matched)) if e.str_matches(&value) => {
556                matched.store(true, Release);
557            }
558            Some((ValueMatch::Debug(ref e), ref matched)) if e.debug_matches(&value) => {
559                matched.store(true, Release)
560            }
561            _ => {}
562        }
563    }
564
565    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
566        match self.inner.fields.get(field) {
567            Some((ValueMatch::Pat(ref e), ref matched)) if e.debug_matches(&value) => {
568                matched.store(true, Release);
569            }
570            Some((ValueMatch::Debug(ref e), ref matched)) if e.debug_matches(&value) => {
571                matched.store(true, Release)
572            }
573            _ => {}
574        }
575    }
576}
577
578#[cfg(test)]
579mod tests {
580    use super::*;
581    use alloc::format;
582
583    #[derive(Debug)]
584    #[allow(dead_code)]
585    struct MyStruct {
586        answer: usize,
587        question: &'static str,
588    }
589
590    #[test]
591    fn debug_struct_match() {
592        let my_struct = MyStruct {
593            answer: 42,
594            question: "life, the universe, and everything",
595        };
596
597        let pattern = "MyStruct { answer: 42, question: \"life, the universe, and everything\" }";
598
599        assert_eq!(
600            format!("{:?}", my_struct),
601            pattern,
602            "`MyStruct`'s `Debug` impl doesn't output the expected string"
603        );
604
605        let matcher = MatchDebug {
606            pattern: pattern.into(),
607        };
608        assert!(matcher.debug_matches(&my_struct))
609    }
610
611    #[test]
612    fn debug_struct_not_match() {
613        let my_struct = MyStruct {
614            answer: 42,
615            question: "what shall we have for lunch?",
616        };
617
618        let pattern = "MyStruct { answer: 42, question: \"life, the universe, and everything\" }";
619
620        assert_eq!(
621            format!("{:?}", my_struct),
622            "MyStruct { answer: 42, question: \"what shall we have for lunch?\" }",
623            "`MyStruct`'s `Debug` impl doesn't output the expected string"
624        );
625
626        let matcher = MatchDebug {
627            pattern: pattern.into(),
628        };
629        assert!(!matcher.debug_matches(&my_struct))
630    }
631}