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(|| { |
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 | |
219 | impl 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 | |
254 | impl FromStr for Directive { |
255 | type Err = ParseError; |
256 | fn from_str(from: &str) -> Result<Self, Self::Err> { |
257 | Directive::parse(from, regex:true) |
258 | } |
259 | } |
260 | |
261 | impl 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 | |
272 | impl PartialOrd for Directive { |
273 | fn partial_cmp(&self, other: &Directive) -> Option<Ordering> { |
274 | Some(self.cmp(other)) |
275 | } |
276 | } |
277 | |
278 | impl 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 | |
330 | impl 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 | |
366 | impl From<LevelFilter> for Directive { |
367 | fn from(level: LevelFilter) -> Self { |
368 | Self { |
369 | level, |
370 | ..Self::default() |
371 | } |
372 | } |
373 | } |
374 | |
375 | impl From<Level> for Directive { |
376 | fn from(level: Level) -> Self { |
377 | LevelFilter::from_level(level).into() |
378 | } |
379 | } |
380 | |
381 | // === impl Dynamics === |
382 | |
383 | impl 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 | |
424 | impl 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: SmallVec<[SpanMatch; 8]> = self |
428 | .field_matches |
429 | .iter() |
430 | .map(|m: &CallsiteMatch| { |
431 | let m: SpanMatch = 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 | |
443 | impl 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: &SpanMatch in &self.field_matches { |
455 | record.record(&mut m.visitor()) |
456 | } |
457 | } |
458 | } |
459 | |
460 | #[cfg (test)] |
461 | mod 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 | } |
868 | |