1 | pub(crate) use crate::filter::directive::{FilterVec, ParseError, StaticDirective}; |
2 | use crate::filter::{ |
3 | directive::{DirectiveSet, Match}, |
4 | env::{field, FieldMap}, |
5 | level::LevelFilter, |
6 | }; |
7 | use once_cell::sync::Lazy; |
8 | use regex::Regex; |
9 | use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr}; |
10 | use 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" )))] |
16 | pub 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. |
24 | pub(super) type Dynamics = DirectiveSet<Directive>; |
25 | |
26 | /// A set of static filtering directives. |
27 | pub(super) type Statics = DirectiveSet<StaticDirective>; |
28 | |
29 | pub(crate) type CallsiteMatcher = MatchSet<field::CallsiteMatch>; |
30 | pub(crate) type SpanMatcher = MatchSet<field::SpanMatch>; |
31 | |
32 | #[derive (Debug, PartialEq, Eq)] |
33 | pub(crate) struct MatchSet<T> { |
34 | field_matches: FilterVec<T>, |
35 | base_level: LevelFilter, |
36 | } |
37 | |
38 | impl 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 | |
212 | impl 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 | |
247 | impl 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 | |
254 | impl 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 | |
265 | impl PartialOrd for Directive { |
266 | fn partial_cmp(&self, other: &Directive) -> Option<Ordering> { |
267 | Some(self.cmp(other)) |
268 | } |
269 | } |
270 | |
271 | impl 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 | |
323 | impl 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 | |
359 | impl From<LevelFilter> for Directive { |
360 | fn from(level: LevelFilter) -> Self { |
361 | Self { |
362 | level, |
363 | ..Self::default() |
364 | } |
365 | } |
366 | } |
367 | |
368 | impl From<Level> for Directive { |
369 | fn from(level: Level) -> Self { |
370 | LevelFilter::from_level(level).into() |
371 | } |
372 | } |
373 | |
374 | // === impl Dynamics === |
375 | |
376 | impl 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 | |
417 | impl 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 | |
436 | impl 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)] |
454 | mod 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 | |