| 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 | |