tracing_subscriber/filter/env/
directive.rs

1pub(crate) use crate::filter::directive::{FilterVec, ParseError, StaticDirective};
2use crate::filter::{
3    directive::{DirectiveSet, Match},
4    env::{field, FieldMap},
5    level::LevelFilter,
6};
7use once_cell::sync::Lazy;
8use regex::Regex;
9use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr};
10use tracing_core::{span, Level, Metadata};
11
12/// A single filtering directive.
13// TODO(eliza): add a builder for programmatically constructing directives?
14#[derive(Clone, Debug, Eq, PartialEq)]
15#[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))]
16pub struct Directive {
17    in_span: Option<String>,
18    fields: Vec<field::Match>,
19    pub(crate) target: Option<String>,
20    pub(crate) level: LevelFilter,
21}
22
23/// A set of dynamic filtering directives.
24pub(super) type Dynamics = DirectiveSet<Directive>;
25
26/// A set of static filtering directives.
27pub(super) type Statics = DirectiveSet<StaticDirective>;
28
29pub(crate) type CallsiteMatcher = MatchSet<field::CallsiteMatch>;
30pub(crate) type SpanMatcher = MatchSet<field::SpanMatch>;
31
32#[derive(Debug, PartialEq, Eq)]
33pub(crate) struct MatchSet<T> {
34    field_matches: FilterVec<T>,
35    base_level: LevelFilter,
36}
37
38impl Directive {
39    pub(super) fn has_name(&self) -> bool {
40        self.in_span.is_some()
41    }
42
43    pub(super) fn has_fields(&self) -> bool {
44        !self.fields.is_empty()
45    }
46
47    pub(super) fn to_static(&self) -> Option<StaticDirective> {
48        if !self.is_static() {
49            return None;
50        }
51
52        // TODO(eliza): these strings are all immutable; we should consider
53        // `Arc`ing them to make this more efficient...
54        let field_names = self.fields.iter().map(field::Match::name).collect();
55
56        Some(StaticDirective::new(
57            self.target.clone(),
58            field_names,
59            self.level,
60        ))
61    }
62
63    fn is_static(&self) -> bool {
64        !self.has_name() && !self.fields.iter().any(field::Match::has_value)
65    }
66
67    pub(super) fn is_dynamic(&self) -> bool {
68        self.has_name() || self.has_fields()
69    }
70
71    pub(crate) fn field_matcher(&self, meta: &Metadata<'_>) -> Option<field::CallsiteMatch> {
72        let fieldset = meta.fields();
73        let fields = self
74            .fields
75            .iter()
76            .filter_map(
77                |field::Match {
78                     ref name,
79                     ref value,
80                 }| {
81                    if let Some(field) = fieldset.field(name) {
82                        let value = value.as_ref().cloned()?;
83                        Some(Ok((field, value)))
84                    } else {
85                        Some(Err(()))
86                    }
87                },
88            )
89            .collect::<Result<FieldMap<_>, ()>>()
90            .ok()?;
91        Some(field::CallsiteMatch {
92            fields,
93            level: self.level,
94        })
95    }
96
97    pub(super) fn make_tables(
98        directives: impl IntoIterator<Item = Directive>,
99    ) -> (Dynamics, Statics) {
100        // TODO(eliza): this could be made more efficient...
101        let (dyns, stats): (Vec<Directive>, Vec<Directive>) =
102            directives.into_iter().partition(Directive::is_dynamic);
103        let statics = stats
104            .into_iter()
105            .filter_map(|d| d.to_static())
106            .chain(dyns.iter().filter_map(Directive::to_static))
107            .collect();
108        (Dynamics::from_iter(dyns), statics)
109    }
110
111    pub(super) fn deregexify(&mut self) {
112        for field in &mut self.fields {
113            field.value = match field.value.take() {
114                Some(field::ValueMatch::Pat(pat)) => {
115                    Some(field::ValueMatch::Debug(pat.into_debug_match()))
116                }
117                x => x,
118            }
119        }
120    }
121
122    pub(super) fn parse(from: &str, regex: bool) -> Result<Self, ParseError> {
123        static DIRECTIVE_RE: Lazy<Regex> = Lazy::new(|| {
124            Regex::new(
125                r"(?x)
126            ^(?P<global_level>(?i:trace|debug|info|warn|error|off|[0-5]))$ |
127                #                 ^^^.
128                #                     `note: we match log level names case-insensitively
129            ^
130            (?: # target name or span name
131                (?P<target>[\w:-]+)|(?P<span>\[[^\]]*\])
132            ){1,2}
133            (?: # level or nothing
134                =(?P<level>(?i:trace|debug|info|warn|error|off|[0-5]))?
135                    #          ^^^.
136                    #              `note: we match log level names case-insensitively
137            )?
138            $
139            ",
140            )
141            .unwrap()
142        });
143        static SPAN_PART_RE: Lazy<Regex> =
144            Lazy::new(|| Regex::new(r"(?P<name>[^\]\{]+)?(?:\{(?P<fields>[^\}]*)\})?").unwrap());
145        static FIELD_FILTER_RE: Lazy<Regex> =
146            // TODO(eliza): this doesn't _currently_ handle value matchers that include comma
147            // characters. We should fix that.
148            Lazy::new(|| {
149                Regex::new(
150                    r"(?x)
151                (
152                    # field name
153                    [[:word:]][[[:word:]]\.]*
154                    # value part (optional)
155                    (?:=[^,]+)?
156                )
157                # trailing comma or EOS
158                (?:,\s?|$)
159            ",
160                )
161                .unwrap()
162            });
163
164        let caps = DIRECTIVE_RE.captures(from).ok_or_else(ParseError::new)?;
165
166        if let Some(level) = caps
167            .name("global_level")
168            .and_then(|s| s.as_str().parse().ok())
169        {
170            return Ok(Directive {
171                level,
172                ..Default::default()
173            });
174        }
175
176        let target = caps.name("target").and_then(|c| {
177            let s = c.as_str();
178            if s.parse::<LevelFilter>().is_ok() {
179                None
180            } else {
181                Some(s.to_owned())
182            }
183        });
184
185        let (in_span, fields) = caps
186            .name("span")
187            .and_then(|cap| {
188                let cap = cap.as_str().trim_matches(|c| c == '[' || c == ']');
189                let caps = SPAN_PART_RE.captures(cap)?;
190                let span = caps.name("name").map(|c| c.as_str().to_owned());
191                let fields = caps
192                    .name("fields")
193                    .map(|c| {
194                        FIELD_FILTER_RE
195                            .find_iter(c.as_str())
196                            .map(|c| field::Match::parse(c.as_str(), regex))
197                            .collect::<Result<Vec<_>, _>>()
198                    })
199                    .unwrap_or_else(|| Ok(Vec::new()));
200                Some((span, fields))
201            })
202            .unwrap_or_else(|| (None, Ok(Vec::new())));
203
204        let level = caps
205            .name("level")
206            .and_then(|l| l.as_str().parse().ok())
207            // Setting the target without the level enables every level for that target
208            .unwrap_or(LevelFilter::TRACE);
209
210        Ok(Self {
211            level,
212            target,
213            in_span,
214            fields: fields?,
215        })
216    }
217}
218
219impl Match for Directive {
220    fn cares_about(&self, meta: &Metadata<'_>) -> bool {
221        // Does this directive have a target filter, and does it match the
222        // metadata's target?
223        if let Some(ref target) = self.target {
224            if !meta.target().starts_with(&target[..]) {
225                return false;
226            }
227        }
228
229        // Do we have a name filter, and does it match the metadata's name?
230        // TODO(eliza): put name globbing here?
231        if let Some(ref name) = self.in_span {
232            if name != meta.name() {
233                return false;
234            }
235        }
236
237        // Does the metadata define all the fields that this directive cares about?
238        let actual_fields = meta.fields();
239        for expected_field in &self.fields {
240            // Does the actual field set (from the metadata) contain this field?
241            if actual_fields.field(&expected_field.name).is_none() {
242                return false;
243            }
244        }
245
246        true
247    }
248
249    fn level(&self) -> &LevelFilter {
250        &self.level
251    }
252}
253
254impl FromStr for Directive {
255    type Err = ParseError;
256    fn from_str(from: &str) -> Result<Self, Self::Err> {
257        Directive::parse(from, true)
258    }
259}
260
261impl Default for Directive {
262    fn default() -> Self {
263        Directive {
264            level: LevelFilter::OFF,
265            target: None,
266            in_span: None,
267            fields: Vec::new(),
268        }
269    }
270}
271
272impl PartialOrd for Directive {
273    fn partial_cmp(&self, other: &Directive) -> Option<Ordering> {
274        Some(self.cmp(other))
275    }
276}
277
278impl Ord for Directive {
279    fn cmp(&self, other: &Directive) -> Ordering {
280        // We attempt to order directives by how "specific" they are. This
281        // ensures that we try the most specific directives first when
282        // attempting to match a piece of metadata.
283
284        // First, we compare based on whether a target is specified, and the
285        // lengths of those targets if both have targets.
286        let ordering = self
287            .target
288            .as_ref()
289            .map(String::len)
290            .cmp(&other.target.as_ref().map(String::len))
291            // Next compare based on the presence of span names.
292            .then_with(|| self.in_span.is_some().cmp(&other.in_span.is_some()))
293            // Then we compare how many fields are defined by each
294            // directive.
295            .then_with(|| self.fields.len().cmp(&other.fields.len()))
296            // Finally, we fall back to lexicographical ordering if the directives are
297            // equally specific. Although this is no longer semantically important,
298            // we need to define a total ordering to determine the directive's place
299            // in the BTreeMap.
300            .then_with(|| {
301                self.target
302                    .cmp(&other.target)
303                    .then_with(|| self.in_span.cmp(&other.in_span))
304                    .then_with(|| self.fields[..].cmp(&other.fields[..]))
305            })
306            .reverse();
307
308        #[cfg(debug_assertions)]
309        {
310            if ordering == Ordering::Equal {
311                debug_assert_eq!(
312                    self.target, other.target,
313                    "invariant violated: Ordering::Equal must imply a.target == b.target"
314                );
315                debug_assert_eq!(
316                    self.in_span, other.in_span,
317                    "invariant violated: Ordering::Equal must imply a.in_span == b.in_span"
318                );
319                debug_assert_eq!(
320                    self.fields, other.fields,
321                    "invariant violated: Ordering::Equal must imply a.fields == b.fields"
322                );
323            }
324        }
325
326        ordering
327    }
328}
329
330impl fmt::Display for Directive {
331    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332        let mut wrote_any = false;
333        if let Some(ref target) = self.target {
334            fmt::Display::fmt(target, f)?;
335            wrote_any = true;
336        }
337
338        if self.in_span.is_some() || !self.fields.is_empty() {
339            f.write_str("[")?;
340
341            if let Some(ref span) = self.in_span {
342                fmt::Display::fmt(span, f)?;
343            }
344
345            let mut fields = self.fields.iter();
346            if let Some(field) = fields.next() {
347                write!(f, "{{{}", field)?;
348                for field in fields {
349                    write!(f, ",{}", field)?;
350                }
351                f.write_str("}")?;
352            }
353
354            f.write_str("]")?;
355            wrote_any = true;
356        }
357
358        if wrote_any {
359            f.write_str("=")?;
360        }
361
362        fmt::Display::fmt(&self.level, f)
363    }
364}
365
366impl From<LevelFilter> for Directive {
367    fn from(level: LevelFilter) -> Self {
368        Self {
369            level,
370            ..Self::default()
371        }
372    }
373}
374
375impl From<Level> for Directive {
376    fn from(level: Level) -> Self {
377        LevelFilter::from_level(level).into()
378    }
379}
380
381// === impl Dynamics ===
382
383impl Dynamics {
384    pub(crate) fn matcher(&self, metadata: &Metadata<'_>) -> Option<CallsiteMatcher> {
385        let mut base_level = None;
386        let field_matches = self
387            .directives_for(metadata)
388            .filter_map(|d| {
389                if let Some(f) = d.field_matcher(metadata) {
390                    return Some(f);
391                }
392                match base_level {
393                    Some(ref b) if d.level > *b => base_level = Some(d.level),
394                    None => base_level = Some(d.level),
395                    _ => {}
396                }
397                None
398            })
399            .collect();
400
401        if let Some(base_level) = base_level {
402            Some(CallsiteMatcher {
403                field_matches,
404                base_level,
405            })
406        } else if !field_matches.is_empty() {
407            Some(CallsiteMatcher {
408                field_matches,
409                base_level: base_level.unwrap_or(LevelFilter::OFF),
410            })
411        } else {
412            None
413        }
414    }
415
416    pub(crate) fn has_value_filters(&self) -> bool {
417        self.directives()
418            .any(|d| d.fields.iter().any(|f| f.value.is_some()))
419    }
420}
421
422// ===== impl DynamicMatch =====
423
424impl CallsiteMatcher {
425    /// Create a new `SpanMatch` for a given instance of the matched callsite.
426    pub(crate) fn to_span_match(&self, attrs: &span::Attributes<'_>) -> SpanMatcher {
427        let field_matches = self
428            .field_matches
429            .iter()
430            .map(|m| {
431                let m = m.to_span_match();
432                attrs.record(&mut m.visitor());
433                m
434            })
435            .collect();
436        SpanMatcher {
437            field_matches,
438            base_level: self.base_level,
439        }
440    }
441}
442
443impl SpanMatcher {
444    /// Returns the level currently enabled for this callsite.
445    pub(crate) fn level(&self) -> LevelFilter {
446        self.field_matches
447            .iter()
448            .filter_map(field::SpanMatch::filter)
449            .max()
450            .unwrap_or(self.base_level)
451    }
452
453    pub(crate) fn record_update(&self, record: &span::Record<'_>) {
454        for m in &self.field_matches {
455            record.record(&mut m.visitor())
456        }
457    }
458}
459
460#[cfg(test)]
461mod test {
462    use super::*;
463
464    fn parse_directives(dirs: impl AsRef<str>) -> Vec<Directive> {
465        dirs.as_ref()
466            .split(',')
467            .filter_map(|s| s.parse().ok())
468            .collect()
469    }
470
471    fn expect_parse(dirs: impl AsRef<str>) -> Vec<Directive> {
472        dirs.as_ref()
473            .split(',')
474            .map(|s| {
475                s.parse()
476                    .unwrap_or_else(|err| panic!("directive '{:?}' should parse: {}", s, err))
477            })
478            .collect()
479    }
480
481    #[test]
482    fn directive_ordering_by_target_len() {
483        // TODO(eliza): it would be nice to have a property-based test for this
484        // instead.
485        let mut dirs = expect_parse(
486            "foo::bar=debug,foo::bar::baz=trace,foo=info,a_really_long_name_with_no_colons=warn",
487        );
488        dirs.sort_unstable();
489
490        let expected = vec![
491            "a_really_long_name_with_no_colons",
492            "foo::bar::baz",
493            "foo::bar",
494            "foo",
495        ];
496        let sorted = dirs
497            .iter()
498            .map(|d| d.target.as_ref().unwrap())
499            .collect::<Vec<_>>();
500
501        assert_eq!(expected, sorted);
502    }
503    #[test]
504    fn directive_ordering_by_span() {
505        // TODO(eliza): it would be nice to have a property-based test for this
506        // instead.
507        let mut dirs = expect_parse("bar[span]=trace,foo=debug,baz::quux=info,a[span]=warn");
508        dirs.sort_unstable();
509
510        let expected = vec!["baz::quux", "bar", "foo", "a"];
511        let sorted = dirs
512            .iter()
513            .map(|d| d.target.as_ref().unwrap())
514            .collect::<Vec<_>>();
515
516        assert_eq!(expected, sorted);
517    }
518
519    #[test]
520    fn directive_ordering_uses_lexicographic_when_equal() {
521        // TODO(eliza): it would be nice to have a property-based test for this
522        // instead.
523        let mut dirs = expect_parse("span[b]=debug,b=debug,a=trace,c=info,span[a]=info");
524        dirs.sort_unstable();
525
526        let expected = vec![
527            ("span", Some("b")),
528            ("span", Some("a")),
529            ("c", None),
530            ("b", None),
531            ("a", None),
532        ];
533        let sorted = dirs
534            .iter()
535            .map(|d| {
536                (
537                    d.target.as_ref().unwrap().as_ref(),
538                    d.in_span.as_ref().map(String::as_ref),
539                )
540            })
541            .collect::<Vec<_>>();
542
543        assert_eq!(expected, sorted);
544    }
545
546    // TODO: this test requires the parser to support directives with multiple
547    // fields, which it currently can't handle. We should enable this test when
548    // that's implemented.
549    #[test]
550    #[ignore]
551    fn directive_ordering_by_field_num() {
552        // TODO(eliza): it would be nice to have a property-based test for this
553        // instead.
554        let mut dirs = expect_parse(
555            "b[{foo,bar}]=info,c[{baz,quuux,quuux}]=debug,a[{foo}]=warn,bar[{field}]=trace,foo=debug,baz::quux=info"
556        );
557        dirs.sort_unstable();
558
559        let expected = vec!["baz::quux", "bar", "foo", "c", "b", "a"];
560        let sorted = dirs
561            .iter()
562            .map(|d| d.target.as_ref().unwrap())
563            .collect::<Vec<_>>();
564
565        assert_eq!(expected, sorted);
566    }
567
568    #[test]
569    fn parse_directives_ralith() {
570        let dirs = parse_directives("common=trace,server=trace");
571        assert_eq!(dirs.len(), 2, "\nparsed: {:#?}", dirs);
572        assert_eq!(dirs[0].target, Some("common".to_string()));
573        assert_eq!(dirs[0].level, LevelFilter::TRACE);
574        assert_eq!(dirs[0].in_span, None);
575
576        assert_eq!(dirs[1].target, Some("server".to_string()));
577        assert_eq!(dirs[1].level, LevelFilter::TRACE);
578        assert_eq!(dirs[1].in_span, None);
579    }
580
581    #[test]
582    fn parse_directives_ralith_uc() {
583        let dirs = parse_directives("common=INFO,server=DEBUG");
584        assert_eq!(dirs.len(), 2, "\nparsed: {:#?}", dirs);
585        assert_eq!(dirs[0].target, Some("common".to_string()));
586        assert_eq!(dirs[0].level, LevelFilter::INFO);
587        assert_eq!(dirs[0].in_span, None);
588
589        assert_eq!(dirs[1].target, Some("server".to_string()));
590        assert_eq!(dirs[1].level, LevelFilter::DEBUG);
591        assert_eq!(dirs[1].in_span, None);
592    }
593
594    #[test]
595    fn parse_directives_ralith_mixed() {
596        let dirs = parse_directives("common=iNfo,server=dEbUg");
597        assert_eq!(dirs.len(), 2, "\nparsed: {:#?}", dirs);
598        assert_eq!(dirs[0].target, Some("common".to_string()));
599        assert_eq!(dirs[0].level, LevelFilter::INFO);
600        assert_eq!(dirs[0].in_span, None);
601
602        assert_eq!(dirs[1].target, Some("server".to_string()));
603        assert_eq!(dirs[1].level, LevelFilter::DEBUG);
604        assert_eq!(dirs[1].in_span, None);
605    }
606
607    #[test]
608    fn parse_directives_valid() {
609        let dirs = parse_directives("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off");
610        assert_eq!(dirs.len(), 4, "\nparsed: {:#?}", dirs);
611        assert_eq!(dirs[0].target, Some("crate1::mod1".to_string()));
612        assert_eq!(dirs[0].level, LevelFilter::ERROR);
613        assert_eq!(dirs[0].in_span, None);
614
615        assert_eq!(dirs[1].target, Some("crate1::mod2".to_string()));
616        assert_eq!(dirs[1].level, LevelFilter::TRACE);
617        assert_eq!(dirs[1].in_span, None);
618
619        assert_eq!(dirs[2].target, Some("crate2".to_string()));
620        assert_eq!(dirs[2].level, LevelFilter::DEBUG);
621        assert_eq!(dirs[2].in_span, None);
622
623        assert_eq!(dirs[3].target, Some("crate3".to_string()));
624        assert_eq!(dirs[3].level, LevelFilter::OFF);
625        assert_eq!(dirs[3].in_span, None);
626    }
627
628    #[test]
629
630    fn parse_level_directives() {
631        let dirs = parse_directives(
632            "crate1::mod1=error,crate1::mod2=warn,crate1::mod2::mod3=info,\
633             crate2=debug,crate3=trace,crate3::mod2::mod1=off",
634        );
635        assert_eq!(dirs.len(), 6, "\nparsed: {:#?}", dirs);
636        assert_eq!(dirs[0].target, Some("crate1::mod1".to_string()));
637        assert_eq!(dirs[0].level, LevelFilter::ERROR);
638        assert_eq!(dirs[0].in_span, None);
639
640        assert_eq!(dirs[1].target, Some("crate1::mod2".to_string()));
641        assert_eq!(dirs[1].level, LevelFilter::WARN);
642        assert_eq!(dirs[1].in_span, None);
643
644        assert_eq!(dirs[2].target, Some("crate1::mod2::mod3".to_string()));
645        assert_eq!(dirs[2].level, LevelFilter::INFO);
646        assert_eq!(dirs[2].in_span, None);
647
648        assert_eq!(dirs[3].target, Some("crate2".to_string()));
649        assert_eq!(dirs[3].level, LevelFilter::DEBUG);
650        assert_eq!(dirs[3].in_span, None);
651
652        assert_eq!(dirs[4].target, Some("crate3".to_string()));
653        assert_eq!(dirs[4].level, LevelFilter::TRACE);
654        assert_eq!(dirs[4].in_span, None);
655
656        assert_eq!(dirs[5].target, Some("crate3::mod2::mod1".to_string()));
657        assert_eq!(dirs[5].level, LevelFilter::OFF);
658        assert_eq!(dirs[5].in_span, None);
659    }
660
661    #[test]
662    fn parse_uppercase_level_directives() {
663        let dirs = parse_directives(
664            "crate1::mod1=ERROR,crate1::mod2=WARN,crate1::mod2::mod3=INFO,\
665             crate2=DEBUG,crate3=TRACE,crate3::mod2::mod1=OFF",
666        );
667        assert_eq!(dirs.len(), 6, "\nparsed: {:#?}", dirs);
668        assert_eq!(dirs[0].target, Some("crate1::mod1".to_string()));
669        assert_eq!(dirs[0].level, LevelFilter::ERROR);
670        assert_eq!(dirs[0].in_span, None);
671
672        assert_eq!(dirs[1].target, Some("crate1::mod2".to_string()));
673        assert_eq!(dirs[1].level, LevelFilter::WARN);
674        assert_eq!(dirs[1].in_span, None);
675
676        assert_eq!(dirs[2].target, Some("crate1::mod2::mod3".to_string()));
677        assert_eq!(dirs[2].level, LevelFilter::INFO);
678        assert_eq!(dirs[2].in_span, None);
679
680        assert_eq!(dirs[3].target, Some("crate2".to_string()));
681        assert_eq!(dirs[3].level, LevelFilter::DEBUG);
682        assert_eq!(dirs[3].in_span, None);
683
684        assert_eq!(dirs[4].target, Some("crate3".to_string()));
685        assert_eq!(dirs[4].level, LevelFilter::TRACE);
686        assert_eq!(dirs[4].in_span, None);
687
688        assert_eq!(dirs[5].target, Some("crate3::mod2::mod1".to_string()));
689        assert_eq!(dirs[5].level, LevelFilter::OFF);
690        assert_eq!(dirs[5].in_span, None);
691    }
692
693    #[test]
694    fn parse_numeric_level_directives() {
695        let dirs = parse_directives(
696            "crate1::mod1=1,crate1::mod2=2,crate1::mod2::mod3=3,crate2=4,\
697             crate3=5,crate3::mod2::mod1=0",
698        );
699        assert_eq!(dirs.len(), 6, "\nparsed: {:#?}", dirs);
700        assert_eq!(dirs[0].target, Some("crate1::mod1".to_string()));
701        assert_eq!(dirs[0].level, LevelFilter::ERROR);
702        assert_eq!(dirs[0].in_span, None);
703
704        assert_eq!(dirs[1].target, Some("crate1::mod2".to_string()));
705        assert_eq!(dirs[1].level, LevelFilter::WARN);
706        assert_eq!(dirs[1].in_span, None);
707
708        assert_eq!(dirs[2].target, Some("crate1::mod2::mod3".to_string()));
709        assert_eq!(dirs[2].level, LevelFilter::INFO);
710        assert_eq!(dirs[2].in_span, None);
711
712        assert_eq!(dirs[3].target, Some("crate2".to_string()));
713        assert_eq!(dirs[3].level, LevelFilter::DEBUG);
714        assert_eq!(dirs[3].in_span, None);
715
716        assert_eq!(dirs[4].target, Some("crate3".to_string()));
717        assert_eq!(dirs[4].level, LevelFilter::TRACE);
718        assert_eq!(dirs[4].in_span, None);
719
720        assert_eq!(dirs[5].target, Some("crate3::mod2::mod1".to_string()));
721        assert_eq!(dirs[5].level, LevelFilter::OFF);
722        assert_eq!(dirs[5].in_span, None);
723    }
724
725    #[test]
726    fn parse_directives_invalid_crate() {
727        // test parse_directives with multiple = in specification
728        let dirs = parse_directives("crate1::mod1=warn=info,crate2=debug");
729        assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
730        assert_eq!(dirs[0].target, Some("crate2".to_string()));
731        assert_eq!(dirs[0].level, LevelFilter::DEBUG);
732        assert_eq!(dirs[0].in_span, None);
733    }
734
735    #[test]
736    fn parse_directives_invalid_level() {
737        // test parse_directives with 'noNumber' as log level
738        let dirs = parse_directives("crate1::mod1=noNumber,crate2=debug");
739        assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
740        assert_eq!(dirs[0].target, Some("crate2".to_string()));
741        assert_eq!(dirs[0].level, LevelFilter::DEBUG);
742        assert_eq!(dirs[0].in_span, None);
743    }
744
745    #[test]
746    fn parse_directives_string_level() {
747        // test parse_directives with 'warn' as log level
748        let dirs = parse_directives("crate1::mod1=wrong,crate2=warn");
749        assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
750        assert_eq!(dirs[0].target, Some("crate2".to_string()));
751        assert_eq!(dirs[0].level, LevelFilter::WARN);
752        assert_eq!(dirs[0].in_span, None);
753    }
754
755    #[test]
756    fn parse_directives_empty_level() {
757        // test parse_directives with '' as log level
758        let dirs = parse_directives("crate1::mod1=wrong,crate2=");
759        assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
760        assert_eq!(dirs[0].target, Some("crate2".to_string()));
761        assert_eq!(dirs[0].level, LevelFilter::TRACE);
762        assert_eq!(dirs[0].in_span, None);
763    }
764
765    #[test]
766    fn parse_directives_global() {
767        // test parse_directives with no crate
768        let dirs = parse_directives("warn,crate2=debug");
769        assert_eq!(dirs.len(), 2, "\nparsed: {:#?}", dirs);
770        assert_eq!(dirs[0].target, None);
771        assert_eq!(dirs[0].level, LevelFilter::WARN);
772        assert_eq!(dirs[1].in_span, None);
773
774        assert_eq!(dirs[1].target, Some("crate2".to_string()));
775        assert_eq!(dirs[1].level, LevelFilter::DEBUG);
776        assert_eq!(dirs[1].in_span, None);
777    }
778
779    // helper function for tests below
780    fn test_parse_bare_level(directive_to_test: &str, level_expected: LevelFilter) {
781        let dirs = parse_directives(directive_to_test);
782        assert_eq!(
783            dirs.len(),
784            1,
785            "\ninput: \"{}\"; parsed: {:#?}",
786            directive_to_test,
787            dirs
788        );
789        assert_eq!(dirs[0].target, None);
790        assert_eq!(dirs[0].level, level_expected);
791        assert_eq!(dirs[0].in_span, None);
792    }
793
794    #[test]
795    fn parse_directives_global_bare_warn_lc() {
796        // test parse_directives with no crate, in isolation, all lowercase
797        test_parse_bare_level("warn", LevelFilter::WARN);
798    }
799
800    #[test]
801    fn parse_directives_global_bare_warn_uc() {
802        // test parse_directives with no crate, in isolation, all uppercase
803        test_parse_bare_level("WARN", LevelFilter::WARN);
804    }
805
806    #[test]
807    fn parse_directives_global_bare_warn_mixed() {
808        // test parse_directives with no crate, in isolation, mixed case
809        test_parse_bare_level("wArN", LevelFilter::WARN);
810    }
811
812    #[test]
813    fn parse_directives_valid_with_spans() {
814        let dirs = parse_directives("crate1::mod1[foo]=error,crate1::mod2[bar],crate2[baz]=debug");
815        assert_eq!(dirs.len(), 3, "\nparsed: {:#?}", dirs);
816        assert_eq!(dirs[0].target, Some("crate1::mod1".to_string()));
817        assert_eq!(dirs[0].level, LevelFilter::ERROR);
818        assert_eq!(dirs[0].in_span, Some("foo".to_string()));
819
820        assert_eq!(dirs[1].target, Some("crate1::mod2".to_string()));
821        assert_eq!(dirs[1].level, LevelFilter::TRACE);
822        assert_eq!(dirs[1].in_span, Some("bar".to_string()));
823
824        assert_eq!(dirs[2].target, Some("crate2".to_string()));
825        assert_eq!(dirs[2].level, LevelFilter::DEBUG);
826        assert_eq!(dirs[2].in_span, Some("baz".to_string()));
827    }
828
829    #[test]
830    fn parse_directives_with_dash_in_target_name() {
831        let dirs = parse_directives("target-name=info");
832        assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
833        assert_eq!(dirs[0].target, Some("target-name".to_string()));
834        assert_eq!(dirs[0].level, LevelFilter::INFO);
835        assert_eq!(dirs[0].in_span, None);
836    }
837
838    #[test]
839    fn parse_directives_with_dash_in_span_name() {
840        // Reproduces https://github.com/tokio-rs/tracing/issues/1367
841
842        let dirs = parse_directives("target[span-name]=info");
843        assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
844        assert_eq!(dirs[0].target, Some("target".to_string()));
845        assert_eq!(dirs[0].level, LevelFilter::INFO);
846        assert_eq!(dirs[0].in_span, Some("span-name".to_string()));
847    }
848
849    #[test]
850    fn parse_directives_with_special_characters_in_span_name() {
851        let span_name = "!\"#$%&'()*+-./:;<=>?@^_`|~[}";
852
853        let dirs = parse_directives(format!("target[{}]=info", span_name));
854        assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
855        assert_eq!(dirs[0].target, Some("target".to_string()));
856        assert_eq!(dirs[0].level, LevelFilter::INFO);
857        assert_eq!(dirs[0].in_span, Some(span_name.to_string()));
858    }
859
860    #[test]
861    fn parse_directives_with_invalid_span_chars() {
862        let invalid_span_name = "]{";
863
864        let dirs = parse_directives(format!("target[{}]=info", invalid_span_name));
865        assert_eq!(dirs.len(), 0, "\nparsed: {:#?}", dirs);
866    }
867}