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