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