1 | use std::env; |
2 | use std::fmt; |
3 | use std::mem; |
4 | |
5 | use log::{LevelFilter, Metadata, Record}; |
6 | |
7 | use crate::enabled; |
8 | use crate::parse_spec; |
9 | use crate::parser::ParseResult; |
10 | use crate::Directive; |
11 | use crate::FilterOp; |
12 | use crate::ParseError; |
13 | |
14 | /// A builder for a log filter. |
15 | /// |
16 | /// It can be used to parse a set of directives from a string before building |
17 | /// a [`Filter`] instance. |
18 | /// |
19 | /// ## Example |
20 | /// |
21 | /// ``` |
22 | /// # use std::env; |
23 | /// use env_filter::Builder; |
24 | /// |
25 | /// let mut builder = Builder::new(); |
26 | /// |
27 | /// // Parse a logging filter from an environment variable. |
28 | /// if let Ok(rust_log) = env::var("RUST_LOG" ) { |
29 | /// builder.parse(&rust_log); |
30 | /// } |
31 | /// |
32 | /// let filter = builder.build(); |
33 | /// ``` |
34 | pub struct Builder { |
35 | directives: Vec<Directive>, |
36 | filter: Option<FilterOp>, |
37 | built: bool, |
38 | } |
39 | |
40 | impl Builder { |
41 | /// Initializes the filter builder with defaults. |
42 | pub fn new() -> Builder { |
43 | Builder { |
44 | directives: Vec::new(), |
45 | filter: None, |
46 | built: false, |
47 | } |
48 | } |
49 | |
50 | /// Initializes the filter builder from an environment. |
51 | pub fn from_env(env: &str) -> Builder { |
52 | let mut builder = Builder::new(); |
53 | |
54 | if let Ok(s) = env::var(env) { |
55 | builder.parse(&s); |
56 | } |
57 | |
58 | builder |
59 | } |
60 | |
61 | /// Insert the directive replacing any directive with the same name. |
62 | fn insert_directive(&mut self, mut directive: Directive) { |
63 | if let Some(pos) = self |
64 | .directives |
65 | .iter() |
66 | .position(|d| d.name == directive.name) |
67 | { |
68 | mem::swap(&mut self.directives[pos], &mut directive); |
69 | } else { |
70 | self.directives.push(directive); |
71 | } |
72 | } |
73 | |
74 | /// Adds a directive to the filter for a specific module. |
75 | pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self { |
76 | self.filter(Some(module), level) |
77 | } |
78 | |
79 | /// Adds a directive to the filter for all modules. |
80 | pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self { |
81 | self.filter(None, level) |
82 | } |
83 | |
84 | /// Adds a directive to the filter. |
85 | /// |
86 | /// The given module (if any) will log at most the specified level provided. |
87 | /// If no module is provided then the filter will apply to all log messages. |
88 | pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self { |
89 | self.insert_directive(Directive { |
90 | name: module.map(|s| s.to_owned()), |
91 | level, |
92 | }); |
93 | self |
94 | } |
95 | |
96 | /// Parses the directives string. |
97 | /// |
98 | /// See the [Enabling Logging] section for more details. |
99 | /// |
100 | /// [Enabling Logging]: ../index.html#enabling-logging |
101 | pub fn parse(&mut self, filters: &str) -> &mut Self { |
102 | #![allow (clippy::print_stderr)] // compatibility |
103 | |
104 | let ParseResult { |
105 | directives, |
106 | filter, |
107 | errors, |
108 | } = parse_spec(filters); |
109 | |
110 | for error in errors { |
111 | eprintln!("warning: {error}, ignoring it" ); |
112 | } |
113 | |
114 | self.filter = filter; |
115 | |
116 | for directive in directives { |
117 | self.insert_directive(directive); |
118 | } |
119 | self |
120 | } |
121 | |
122 | /// Parses the directive string, returning an error if the given directive string is invalid. |
123 | /// |
124 | /// See the [Enabling Logging] section for more details. |
125 | /// |
126 | /// [Enabling Logging]: ../index.html#enabling-logging |
127 | pub fn try_parse(&mut self, filters: &str) -> Result<&mut Self, ParseError> { |
128 | let (directives, filter) = parse_spec(filters).ok()?; |
129 | |
130 | self.filter = filter; |
131 | |
132 | for directive in directives { |
133 | self.insert_directive(directive); |
134 | } |
135 | Ok(self) |
136 | } |
137 | |
138 | /// Build a log filter. |
139 | pub fn build(&mut self) -> Filter { |
140 | assert!(!self.built, "attempt to re-use consumed builder" ); |
141 | self.built = true; |
142 | |
143 | let mut directives = Vec::new(); |
144 | if self.directives.is_empty() { |
145 | // Adds the default filter if none exist |
146 | directives.push(Directive { |
147 | name: None, |
148 | level: LevelFilter::Error, |
149 | }); |
150 | } else { |
151 | // Consume directives. |
152 | directives = mem::take(&mut self.directives); |
153 | // Sort the directives by length of their name, this allows a |
154 | // little more efficient lookup at runtime. |
155 | directives.sort_by(|a, b| { |
156 | let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0); |
157 | let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0); |
158 | alen.cmp(&blen) |
159 | }); |
160 | } |
161 | |
162 | Filter { |
163 | directives: mem::take(&mut directives), |
164 | filter: mem::take(&mut self.filter), |
165 | } |
166 | } |
167 | } |
168 | |
169 | impl Default for Builder { |
170 | fn default() -> Self { |
171 | Builder::new() |
172 | } |
173 | } |
174 | |
175 | impl fmt::Debug for Builder { |
176 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
177 | if self.built { |
178 | f.debug_struct("Filter" ).field(name:"built" , &true).finish() |
179 | } else { |
180 | f&mut DebugStruct<'_, '_>.debug_struct("Filter" ) |
181 | .field("filter" , &self.filter) |
182 | .field(name:"directives" , &self.directives) |
183 | .finish() |
184 | } |
185 | } |
186 | } |
187 | |
188 | /// A log filter. |
189 | /// |
190 | /// This struct can be used to determine whether or not a log record |
191 | /// should be written to the output. |
192 | /// Use the [`Builder`] type to parse and construct a `Filter`. |
193 | /// |
194 | /// [`Builder`]: struct.Builder.html |
195 | pub struct Filter { |
196 | directives: Vec<Directive>, |
197 | filter: Option<FilterOp>, |
198 | } |
199 | |
200 | impl Filter { |
201 | /// Returns the maximum `LevelFilter` that this filter instance is |
202 | /// configured to output. |
203 | /// |
204 | /// # Example |
205 | /// |
206 | /// ```rust |
207 | /// use log::LevelFilter; |
208 | /// use env_filter::Builder; |
209 | /// |
210 | /// let mut builder = Builder::new(); |
211 | /// builder.filter(Some("module1" ), LevelFilter::Info); |
212 | /// builder.filter(Some("module2" ), LevelFilter::Error); |
213 | /// |
214 | /// let filter = builder.build(); |
215 | /// assert_eq!(filter.filter(), LevelFilter::Info); |
216 | /// ``` |
217 | pub fn filter(&self) -> LevelFilter { |
218 | self.directives |
219 | .iter() |
220 | .map(|d| d.level) |
221 | .max() |
222 | .unwrap_or(LevelFilter::Off) |
223 | } |
224 | |
225 | /// Checks if this record matches the configured filter. |
226 | pub fn matches(&self, record: &Record<'_>) -> bool { |
227 | if !self.enabled(record.metadata()) { |
228 | return false; |
229 | } |
230 | |
231 | if let Some(filter) = self.filter.as_ref() { |
232 | if !filter.is_match(&record.args().to_string()) { |
233 | return false; |
234 | } |
235 | } |
236 | |
237 | true |
238 | } |
239 | |
240 | /// Determines if a log message with the specified metadata would be logged. |
241 | pub fn enabled(&self, metadata: &Metadata<'_>) -> bool { |
242 | let level = metadata.level(); |
243 | let target = metadata.target(); |
244 | |
245 | enabled(&self.directives, level, target) |
246 | } |
247 | } |
248 | |
249 | impl fmt::Debug for Filter { |
250 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
251 | f&mut DebugStruct<'_, '_>.debug_struct("Filter" ) |
252 | .field("filter" , &self.filter) |
253 | .field(name:"directives" , &self.directives) |
254 | .finish() |
255 | } |
256 | } |
257 | |
258 | #[cfg (test)] |
259 | mod tests { |
260 | use log::{Level, LevelFilter}; |
261 | use snapbox::{assert_data_eq, str}; |
262 | |
263 | use super::{enabled, Builder, Directive, Filter}; |
264 | |
265 | fn make_logger_filter(dirs: Vec<Directive>) -> Filter { |
266 | let mut logger = Builder::new().build(); |
267 | logger.directives = dirs; |
268 | logger |
269 | } |
270 | |
271 | #[test ] |
272 | fn filter_info() { |
273 | let logger = Builder::new().filter(None, LevelFilter::Info).build(); |
274 | assert!(enabled(&logger.directives, Level::Info, "crate1" )); |
275 | assert!(!enabled(&logger.directives, Level::Debug, "crate1" )); |
276 | } |
277 | |
278 | #[test ] |
279 | fn filter_beginning_longest_match() { |
280 | let logger = Builder::new() |
281 | .filter(Some("crate2" ), LevelFilter::Info) |
282 | .filter(Some("crate2::mod" ), LevelFilter::Debug) |
283 | .filter(Some("crate1::mod1" ), LevelFilter::Warn) |
284 | .build(); |
285 | assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1" )); |
286 | assert!(!enabled(&logger.directives, Level::Debug, "crate2" )); |
287 | } |
288 | |
289 | // Some of our tests are only correct or complete when they cover the full |
290 | // universe of variants for log::Level. In the unlikely event that a new |
291 | // variant is added in the future, this test will detect the scenario and |
292 | // alert us to the need to review and update the tests. In such a |
293 | // situation, this test will fail to compile, and the error message will |
294 | // look something like this: |
295 | // |
296 | // error[E0004]: non-exhaustive patterns: `NewVariant` not covered |
297 | // --> src/filter/mod.rs:413:15 |
298 | // | |
299 | // 413 | match level_universe { |
300 | // | ^^^^^^^^^^^^^^ pattern `NewVariant` not covered |
301 | #[test ] |
302 | fn ensure_tests_cover_level_universe() { |
303 | let level_universe: Level = Level::Trace; // use of trace variant is arbitrary |
304 | match level_universe { |
305 | Level::Error | Level::Warn | Level::Info | Level::Debug | Level::Trace => (), |
306 | } |
307 | } |
308 | |
309 | #[test ] |
310 | fn parse_default() { |
311 | let logger = Builder::new().parse("info,crate1::mod1=warn" ).build(); |
312 | assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1" )); |
313 | assert!(enabled(&logger.directives, Level::Info, "crate2::mod2" )); |
314 | } |
315 | |
316 | #[test ] |
317 | fn parse_default_bare_level_off_lc() { |
318 | let logger = Builder::new().parse("off" ).build(); |
319 | assert!(!enabled(&logger.directives, Level::Error, "" )); |
320 | assert!(!enabled(&logger.directives, Level::Warn, "" )); |
321 | assert!(!enabled(&logger.directives, Level::Info, "" )); |
322 | assert!(!enabled(&logger.directives, Level::Debug, "" )); |
323 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
324 | } |
325 | |
326 | #[test ] |
327 | fn parse_default_bare_level_off_uc() { |
328 | let logger = Builder::new().parse("OFF" ).build(); |
329 | assert!(!enabled(&logger.directives, Level::Error, "" )); |
330 | assert!(!enabled(&logger.directives, Level::Warn, "" )); |
331 | assert!(!enabled(&logger.directives, Level::Info, "" )); |
332 | assert!(!enabled(&logger.directives, Level::Debug, "" )); |
333 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
334 | } |
335 | |
336 | #[test ] |
337 | fn parse_default_bare_level_error_lc() { |
338 | let logger = Builder::new().parse("error" ).build(); |
339 | assert!(enabled(&logger.directives, Level::Error, "" )); |
340 | assert!(!enabled(&logger.directives, Level::Warn, "" )); |
341 | assert!(!enabled(&logger.directives, Level::Info, "" )); |
342 | assert!(!enabled(&logger.directives, Level::Debug, "" )); |
343 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
344 | } |
345 | |
346 | #[test ] |
347 | fn parse_default_bare_level_error_uc() { |
348 | let logger = Builder::new().parse("ERROR" ).build(); |
349 | assert!(enabled(&logger.directives, Level::Error, "" )); |
350 | assert!(!enabled(&logger.directives, Level::Warn, "" )); |
351 | assert!(!enabled(&logger.directives, Level::Info, "" )); |
352 | assert!(!enabled(&logger.directives, Level::Debug, "" )); |
353 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
354 | } |
355 | |
356 | #[test ] |
357 | fn parse_default_bare_level_warn_lc() { |
358 | let logger = Builder::new().parse("warn" ).build(); |
359 | assert!(enabled(&logger.directives, Level::Error, "" )); |
360 | assert!(enabled(&logger.directives, Level::Warn, "" )); |
361 | assert!(!enabled(&logger.directives, Level::Info, "" )); |
362 | assert!(!enabled(&logger.directives, Level::Debug, "" )); |
363 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
364 | } |
365 | |
366 | #[test ] |
367 | fn parse_default_bare_level_warn_uc() { |
368 | let logger = Builder::new().parse("WARN" ).build(); |
369 | assert!(enabled(&logger.directives, Level::Error, "" )); |
370 | assert!(enabled(&logger.directives, Level::Warn, "" )); |
371 | assert!(!enabled(&logger.directives, Level::Info, "" )); |
372 | assert!(!enabled(&logger.directives, Level::Debug, "" )); |
373 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
374 | } |
375 | |
376 | #[test ] |
377 | fn parse_default_bare_level_info_lc() { |
378 | let logger = Builder::new().parse("info" ).build(); |
379 | assert!(enabled(&logger.directives, Level::Error, "" )); |
380 | assert!(enabled(&logger.directives, Level::Warn, "" )); |
381 | assert!(enabled(&logger.directives, Level::Info, "" )); |
382 | assert!(!enabled(&logger.directives, Level::Debug, "" )); |
383 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
384 | } |
385 | |
386 | #[test ] |
387 | fn parse_default_bare_level_info_uc() { |
388 | let logger = Builder::new().parse("INFO" ).build(); |
389 | assert!(enabled(&logger.directives, Level::Error, "" )); |
390 | assert!(enabled(&logger.directives, Level::Warn, "" )); |
391 | assert!(enabled(&logger.directives, Level::Info, "" )); |
392 | assert!(!enabled(&logger.directives, Level::Debug, "" )); |
393 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
394 | } |
395 | |
396 | #[test ] |
397 | fn parse_default_bare_level_debug_lc() { |
398 | let logger = Builder::new().parse("debug" ).build(); |
399 | assert!(enabled(&logger.directives, Level::Error, "" )); |
400 | assert!(enabled(&logger.directives, Level::Warn, "" )); |
401 | assert!(enabled(&logger.directives, Level::Info, "" )); |
402 | assert!(enabled(&logger.directives, Level::Debug, "" )); |
403 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
404 | } |
405 | |
406 | #[test ] |
407 | fn parse_default_bare_level_debug_uc() { |
408 | let logger = Builder::new().parse("DEBUG" ).build(); |
409 | assert!(enabled(&logger.directives, Level::Error, "" )); |
410 | assert!(enabled(&logger.directives, Level::Warn, "" )); |
411 | assert!(enabled(&logger.directives, Level::Info, "" )); |
412 | assert!(enabled(&logger.directives, Level::Debug, "" )); |
413 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
414 | } |
415 | |
416 | #[test ] |
417 | fn parse_default_bare_level_trace_lc() { |
418 | let logger = Builder::new().parse("trace" ).build(); |
419 | assert!(enabled(&logger.directives, Level::Error, "" )); |
420 | assert!(enabled(&logger.directives, Level::Warn, "" )); |
421 | assert!(enabled(&logger.directives, Level::Info, "" )); |
422 | assert!(enabled(&logger.directives, Level::Debug, "" )); |
423 | assert!(enabled(&logger.directives, Level::Trace, "" )); |
424 | } |
425 | |
426 | #[test ] |
427 | fn parse_default_bare_level_trace_uc() { |
428 | let logger = Builder::new().parse("TRACE" ).build(); |
429 | assert!(enabled(&logger.directives, Level::Error, "" )); |
430 | assert!(enabled(&logger.directives, Level::Warn, "" )); |
431 | assert!(enabled(&logger.directives, Level::Info, "" )); |
432 | assert!(enabled(&logger.directives, Level::Debug, "" )); |
433 | assert!(enabled(&logger.directives, Level::Trace, "" )); |
434 | } |
435 | |
436 | // In practice, the desired log level is typically specified by a token |
437 | // that is either all lowercase (e.g., 'trace') or all uppercase (.e.g, |
438 | // 'TRACE'), but this tests serves as a reminder that |
439 | // log::Level::from_str() ignores all case variants. |
440 | #[test ] |
441 | fn parse_default_bare_level_debug_mixed() { |
442 | { |
443 | let logger = Builder::new().parse("Debug" ).build(); |
444 | assert!(enabled(&logger.directives, Level::Error, "" )); |
445 | assert!(enabled(&logger.directives, Level::Warn, "" )); |
446 | assert!(enabled(&logger.directives, Level::Info, "" )); |
447 | assert!(enabled(&logger.directives, Level::Debug, "" )); |
448 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
449 | } |
450 | { |
451 | let logger = Builder::new().parse("debuG" ).build(); |
452 | assert!(enabled(&logger.directives, Level::Error, "" )); |
453 | assert!(enabled(&logger.directives, Level::Warn, "" )); |
454 | assert!(enabled(&logger.directives, Level::Info, "" )); |
455 | assert!(enabled(&logger.directives, Level::Debug, "" )); |
456 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
457 | } |
458 | { |
459 | let logger = Builder::new().parse("deBug" ).build(); |
460 | assert!(enabled(&logger.directives, Level::Error, "" )); |
461 | assert!(enabled(&logger.directives, Level::Warn, "" )); |
462 | assert!(enabled(&logger.directives, Level::Info, "" )); |
463 | assert!(enabled(&logger.directives, Level::Debug, "" )); |
464 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
465 | } |
466 | { |
467 | let logger = Builder::new().parse("DeBuG" ).build(); // LaTeX flavor! |
468 | assert!(enabled(&logger.directives, Level::Error, "" )); |
469 | assert!(enabled(&logger.directives, Level::Warn, "" )); |
470 | assert!(enabled(&logger.directives, Level::Info, "" )); |
471 | assert!(enabled(&logger.directives, Level::Debug, "" )); |
472 | assert!(!enabled(&logger.directives, Level::Trace, "" )); |
473 | } |
474 | } |
475 | |
476 | #[test ] |
477 | fn try_parse_valid_filter() { |
478 | let logger = Builder::new() |
479 | .try_parse("info,crate1::mod1=warn" ) |
480 | .expect("valid filter returned error" ) |
481 | .build(); |
482 | assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1" )); |
483 | assert!(enabled(&logger.directives, Level::Info, "crate2::mod2" )); |
484 | } |
485 | |
486 | #[test ] |
487 | fn try_parse_invalid_filter() { |
488 | let error = Builder::new().try_parse("info,crate1=invalid" ).unwrap_err(); |
489 | assert_data_eq!( |
490 | error, |
491 | str!["error parsing logger filter: invalid logging spec 'invalid'" ] |
492 | ); |
493 | } |
494 | |
495 | #[test ] |
496 | fn match_full_path() { |
497 | let logger = make_logger_filter(vec![ |
498 | Directive { |
499 | name: Some("crate2" .to_owned()), |
500 | level: LevelFilter::Info, |
501 | }, |
502 | Directive { |
503 | name: Some("crate1::mod1" .to_owned()), |
504 | level: LevelFilter::Warn, |
505 | }, |
506 | ]); |
507 | assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1" )); |
508 | assert!(!enabled(&logger.directives, Level::Info, "crate1::mod1" )); |
509 | assert!(enabled(&logger.directives, Level::Info, "crate2" )); |
510 | assert!(!enabled(&logger.directives, Level::Debug, "crate2" )); |
511 | } |
512 | |
513 | #[test ] |
514 | fn no_match() { |
515 | let logger = make_logger_filter(vec![ |
516 | Directive { |
517 | name: Some("crate2" .to_owned()), |
518 | level: LevelFilter::Info, |
519 | }, |
520 | Directive { |
521 | name: Some("crate1::mod1" .to_owned()), |
522 | level: LevelFilter::Warn, |
523 | }, |
524 | ]); |
525 | assert!(!enabled(&logger.directives, Level::Warn, "crate3" )); |
526 | } |
527 | |
528 | #[test ] |
529 | fn match_beginning() { |
530 | let logger = make_logger_filter(vec![ |
531 | Directive { |
532 | name: Some("crate2" .to_owned()), |
533 | level: LevelFilter::Info, |
534 | }, |
535 | Directive { |
536 | name: Some("crate1::mod1" .to_owned()), |
537 | level: LevelFilter::Warn, |
538 | }, |
539 | ]); |
540 | assert!(enabled(&logger.directives, Level::Info, "crate2::mod1" )); |
541 | } |
542 | |
543 | #[test ] |
544 | fn match_beginning_longest_match() { |
545 | let logger = make_logger_filter(vec![ |
546 | Directive { |
547 | name: Some("crate2" .to_owned()), |
548 | level: LevelFilter::Info, |
549 | }, |
550 | Directive { |
551 | name: Some("crate2::mod" .to_owned()), |
552 | level: LevelFilter::Debug, |
553 | }, |
554 | Directive { |
555 | name: Some("crate1::mod1" .to_owned()), |
556 | level: LevelFilter::Warn, |
557 | }, |
558 | ]); |
559 | assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1" )); |
560 | assert!(!enabled(&logger.directives, Level::Debug, "crate2" )); |
561 | } |
562 | |
563 | #[test ] |
564 | fn match_default() { |
565 | let logger = make_logger_filter(vec![ |
566 | Directive { |
567 | name: None, |
568 | level: LevelFilter::Info, |
569 | }, |
570 | Directive { |
571 | name: Some("crate1::mod1" .to_owned()), |
572 | level: LevelFilter::Warn, |
573 | }, |
574 | ]); |
575 | assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1" )); |
576 | assert!(enabled(&logger.directives, Level::Info, "crate2::mod2" )); |
577 | } |
578 | |
579 | #[test ] |
580 | fn zero_level() { |
581 | let logger = make_logger_filter(vec![ |
582 | Directive { |
583 | name: None, |
584 | level: LevelFilter::Info, |
585 | }, |
586 | Directive { |
587 | name: Some("crate1::mod1" .to_owned()), |
588 | level: LevelFilter::Off, |
589 | }, |
590 | ]); |
591 | assert!(!enabled(&logger.directives, Level::Error, "crate1::mod1" )); |
592 | assert!(enabled(&logger.directives, Level::Info, "crate2::mod2" )); |
593 | } |
594 | } |
595 | |