1use crate::level::Level;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct FilterRule {
30 pub target: String,
32 pub level: Level,
34}
35
36impl FilterRule {
37 pub fn new(target: impl Into<String>, level: Level) -> Self {
39 Self {
40 target: target.into(),
41 level,
42 }
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct Filter {
62 default_level: Level,
63 rules: Vec<FilterRule>,
66 min_level: Level,
69}
70
71impl Filter {
72 #[must_use]
74 pub fn new(default_level: Level) -> Self {
75 Self {
76 default_level,
77 rules: Vec::new(),
78 min_level: default_level,
79 }
80 }
81
82 #[must_use]
91 pub fn with_rule(mut self, target: impl Into<String>, level: Level) -> Self {
92 self.insert(FilterRule::new(target, level));
93 self
94 }
95
96 fn insert(&mut self, rule: FilterRule) {
97 if let Some(existing) = self.rules.iter_mut().find(|r| r.target == rule.target) {
98 existing.level = rule.level;
99 } else {
100 let at = self
104 .rules
105 .partition_point(|r| r.target.len() >= rule.target.len());
106 self.rules.insert(at, rule);
107 }
108 self.recompute_min();
109 }
110
111 fn recompute_min(&mut self) {
112 self.min_level = self
113 .rules
114 .iter()
115 .map(|r| r.level)
116 .fold(self.default_level, core::cmp::min);
117 }
118
119 #[must_use]
121 pub fn default_level(&self) -> Level {
122 self.default_level
123 }
124
125 #[must_use]
127 pub fn with_default_level(mut self, level: Level) -> Self {
128 self.default_level = level;
129 self.recompute_min();
130 self
131 }
132
133 #[must_use]
135 pub fn rules(&self) -> &[FilterRule] {
136 &self.rules
137 }
138
139 #[must_use]
142 pub fn min_level(&self) -> Level {
143 self.min_level
144 }
145
146 #[inline]
148 #[must_use]
149 pub fn is_enabled(&self, target: &str, level: Level) -> bool {
150 if level < self.min_level || level == Level::Off {
151 return false;
152 }
153 level.is_enabled_at(self.threshold_for(target))
154 }
155
156 #[must_use]
159 pub fn threshold_for(&self, target: &str) -> Level {
160 self.rules
161 .iter()
162 .find(|rule| target_matches(target, &rule.target))
163 .map_or(self.default_level, |rule| rule.level)
164 }
165
166 pub fn parse(directive: &str) -> Result<Self, ParseFilterError> {
205 let mut filter = Self::new(Level::Off);
206 let mut segments = 0_usize;
207 for raw in directive.split(',') {
208 let segment = raw.trim();
209 if segment.is_empty() {
210 continue;
211 }
212 segments += 1;
213 match segment.split_once('=') {
214 Some((target, level)) => {
215 let target = target.trim();
216 validate_target(target, segment)?;
217 let level: Level = level.trim().parse().map_err(|_| {
218 ParseFilterError::new(ParseFilterErrorKind::InvalidLevel, segment)
219 })?;
220 filter.insert(FilterRule::new(target, level));
221 }
222 None => {
223 let level: Level = segment.parse().map_err(|_| {
224 ParseFilterError::new(ParseFilterErrorKind::InvalidLevel, segment)
225 })?;
226 filter.default_level = level;
227 }
228 }
229 }
230 if segments == 0 {
231 return Err(ParseFilterError::new(
234 ParseFilterErrorKind::Empty,
235 directive,
236 ));
237 }
238 filter.recompute_min();
239 Ok(filter)
240 }
241}
242
243impl core::str::FromStr for Filter {
244 type Err = ParseFilterError;
245
246 fn from_str(s: &str) -> Result<Self, Self::Err> {
247 Self::parse(s)
248 }
249}
250
251fn validate_target(target: &str, segment: &str) -> Result<(), ParseFilterError> {
252 let kind = if target.is_empty() {
253 ParseFilterErrorKind::EmptyTarget
254 } else if target.ends_with(':') || target.ends_with('.') {
255 ParseFilterErrorKind::TrailingSeparator
256 } else if target.chars().any(char::is_whitespace) {
257 ParseFilterErrorKind::InvalidTarget
258 } else {
259 return Ok(());
260 };
261 Err(ParseFilterError::new(kind, segment))
262}
263
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266#[non_exhaustive]
267pub enum ParseFilterErrorKind {
268 Empty,
270 InvalidLevel,
272 EmptyTarget,
274 TrailingSeparator,
276 InvalidTarget,
278 NotUnicode,
281}
282
283#[derive(Debug, Clone, PartialEq, Eq)]
288pub struct ParseFilterError {
289 kind: ParseFilterErrorKind,
290 segment: String,
291}
292
293impl ParseFilterError {
294 fn new(kind: ParseFilterErrorKind, segment: &str) -> Self {
295 Self {
296 kind,
297 segment: segment.to_owned(),
298 }
299 }
300
301 pub(crate) fn not_unicode(raw: &std::ffi::OsStr) -> Self {
302 Self {
303 kind: ParseFilterErrorKind::NotUnicode,
304 segment: raw.to_string_lossy().into_owned(),
305 }
306 }
307
308 #[must_use]
310 pub fn kind(&self) -> ParseFilterErrorKind {
311 self.kind
312 }
313
314 #[must_use]
318 pub fn segment(&self) -> &str {
319 &self.segment
320 }
321}
322
323impl core::fmt::Display for ParseFilterError {
324 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
325 let what = match self.kind {
326 ParseFilterErrorKind::Empty => "directive is empty",
327 ParseFilterErrorKind::InvalidLevel => {
328 "unknown level (expected off, trace, debug, info, warn or error)"
329 }
330 ParseFilterErrorKind::EmptyTarget => "target is empty",
331 ParseFilterErrorKind::TrailingSeparator => "target ends with a separator",
332 ParseFilterErrorKind::InvalidTarget => "target contains whitespace",
333 ParseFilterErrorKind::NotUnicode => "value is not valid Unicode",
334 };
335 write!(f, "invalid filter directive {:?}: {what}", self.segment)
336 }
337}
338
339impl std::error::Error for ParseFilterError {}
340
341#[inline]
344fn target_matches(target: &str, prefix: &str) -> bool {
345 match target.strip_prefix(prefix) {
346 Some("") => true,
347 Some(rest) => !prefix.is_empty() && (rest.starts_with("::") || rest.starts_with('.')),
348 None => false,
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn test_filter_default_applies_when_no_rules_match() {
358 let f = Filter::new(Level::Warn);
359 assert!(f.is_enabled("anything", Level::Warn));
360 assert!(!f.is_enabled("anything", Level::Info));
361 }
362
363 #[test]
364 fn test_filter_rule_overrides_default_for_matching_prefix() {
365 let f = Filter::new(Level::Warn).with_rule("app::auth", Level::Debug);
366 assert!(f.is_enabled("app::auth", Level::Debug));
367 assert!(f.is_enabled("app::auth::login", Level::Debug));
368 assert!(!f.is_enabled("app::auth", Level::Trace));
369 assert!(!f.is_enabled("hyper", Level::Info));
370 }
371
372 #[test]
373 fn test_filter_prefix_match_respects_module_boundary() {
374 let f = Filter::new(Level::Off).with_rule("app", Level::Info);
375 assert!(f.is_enabled("app", Level::Info));
376 assert!(f.is_enabled("app::sub", Level::Info));
377 assert!(f.is_enabled("app.sub", Level::Info));
378 assert!(!f.is_enabled("application", Level::Info));
379 assert!(!f.is_enabled("ap", Level::Info));
380 assert!(!f.is_enabled("app:", Level::Info));
381 assert!(!f.is_enabled("app:x", Level::Info));
382 }
383
384 #[test]
385 fn test_filter_most_specific_rule_wins_regardless_of_order() {
386 let f = Filter::parse("info,app=warn,app::db=trace").unwrap();
388 assert!(f.is_enabled("app::db", Level::Trace));
389 assert!(f.is_enabled("app::db::pool", Level::Trace));
390 assert!(!f.is_enabled("app::http", Level::Info));
391 assert!(f.is_enabled("other", Level::Info));
392 let reversed = Filter::parse("app::db=trace,app=warn,info").unwrap();
393 assert_eq!(f, reversed);
394 let built = Filter::new(Level::Info)
395 .with_rule("app", Level::Warn)
396 .with_rule("app::db", Level::Trace);
397 assert_eq!(built.threshold_for("app::db::x"), Level::Trace);
398 }
399
400 #[test]
401 fn test_filter_same_target_last_wins() {
402 let f = Filter::parse("app=error,app=debug").unwrap();
403 assert_eq!(f.rules().len(), 1);
404 assert_eq!(f.threshold_for("app"), Level::Debug);
405 let f = Filter::new(Level::Info)
406 .with_rule("x", Level::Error)
407 .with_rule("x", Level::Trace);
408 assert_eq!(f.rules().len(), 1);
409 assert_eq!(f.threshold_for("x"), Level::Trace);
410 let f = Filter::parse("info,warn").unwrap();
411 assert_eq!(f.default_level(), Level::Warn);
412 }
413
414 #[test]
415 fn test_filter_rules_are_ordered_most_specific_first() {
416 let f = Filter::parse("a=info,a::b::c=debug,a::b=warn").unwrap();
417 let targets: Vec<&str> = f.rules().iter().map(|r| r.target.as_str()).collect();
418 assert_eq!(targets, ["a::b::c", "a::b", "a"]);
419 }
420
421 #[test]
422 fn test_filter_min_level_fast_path() {
423 let f = Filter::new(Level::Warn).with_rule("loud", Level::Trace);
424 assert_eq!(f.min_level(), Level::Trace);
425 assert!(f.is_enabled("loud", Level::Trace));
426 let quiet = Filter::new(Level::Error);
427 assert_eq!(quiet.min_level(), Level::Error);
428 assert!(!quiet.is_enabled("x", Level::Warn));
429 let raised = f.with_default_level(Level::Debug);
430 assert_eq!(raised.default_level(), Level::Debug);
431 assert_eq!(raised.min_level(), Level::Trace);
432 }
433
434 #[test]
435 fn test_filter_never_admits_off_level_records() {
436 let f = Filter::new(Level::Trace);
437 assert!(!f.is_enabled("x", Level::Off));
438 }
439
440 #[test]
441 fn test_filter_empty_target_rule_matches_only_empty_target() {
442 let f = Filter::new(Level::Off).with_rule("", Level::Info);
443 assert!(f.is_enabled("", Level::Info));
444 assert!(!f.is_enabled("app", Level::Info));
445 assert!(!f.is_enabled("::app", Level::Info));
446 }
447
448 #[test]
449 fn test_parse_simple_directive() {
450 let f = Filter::parse("info").unwrap();
451 assert_eq!(f.default_level(), Level::Info);
452 assert!(f.rules().is_empty());
453 let f: Filter = "DEBUG".parse().unwrap();
454 assert_eq!(f.default_level(), Level::Debug);
455 }
456
457 #[test]
458 fn test_parse_with_overrides_and_whitespace() {
459 let f = Filter::parse(" info, app::auth = debug ,hyper=warn,, ").unwrap();
460 assert_eq!(f.default_level(), Level::Info);
461 assert_eq!(f.rules().len(), 2);
462 assert!(f.is_enabled("hyper", Level::Warn));
463 assert!(!f.is_enabled("hyper", Level::Info));
464 assert!(f.is_enabled("app::auth", Level::Debug));
465 }
466
467 #[test]
468 fn test_parse_rules_without_default_disable_unmatched() {
469 let f = Filter::parse("hyper=warn").unwrap();
470 assert_eq!(f.default_level(), Level::Off);
471 assert!(f.is_enabled("hyper", Level::Warn));
472 assert!(!f.is_enabled("other", Level::Error));
473 }
474
475 #[test]
476 fn test_parse_rejects_degenerate_input_with_typed_errors() {
477 let cases = [
478 ("", ParseFilterErrorKind::Empty, ""),
479 (" ", ParseFilterErrorKind::Empty, " "),
480 ("nope", ParseFilterErrorKind::InvalidLevel, "nope"),
481 (
482 "info,hyper=verbose",
483 ParseFilterErrorKind::InvalidLevel,
484 "hyper=verbose",
485 ),
486 ("=debug", ParseFilterErrorKind::EmptyTarget, "=debug"),
487 (
488 "app::=debug",
489 ParseFilterErrorKind::TrailingSeparator,
490 "app::=debug",
491 ),
492 (
493 "app.=debug",
494 ParseFilterErrorKind::TrailingSeparator,
495 "app.=debug",
496 ),
497 (
498 "my app=debug",
499 ParseFilterErrorKind::InvalidTarget,
500 "my app=debug",
501 ),
502 ("a=b=c", ParseFilterErrorKind::InvalidLevel, "a=b=c"),
503 ];
504 for (input, kind, segment) in cases {
505 let err = Filter::parse(input).unwrap_err();
506 assert_eq!(err.kind(), kind, "{input:?}");
507 assert_eq!(err.segment(), segment, "{input:?}");
508 }
509 }
510
511 #[test]
512 fn test_parse_filter_error_display_names_segment_and_cause() {
513 let err = Filter::parse("x=loud").unwrap_err();
514 assert_eq!(
515 err.to_string(),
516 "invalid filter directive \"x=loud\": unknown level (expected off, trace, debug, info, warn or error)"
517 );
518 for (input, needle) in [
519 ("", "empty"),
520 ("=x", "target is empty"),
521 ("a::=info", "separator"),
522 ("a b=info", "whitespace"),
523 ] {
524 let msg = Filter::parse(input).unwrap_err().to_string();
525 assert!(msg.contains(needle), "{input:?}: {msg}");
526 }
527 }
528
529 #[test]
530 fn test_target_matches_boundaries() {
531 assert!(target_matches("a", "a"));
532 assert!(target_matches("a::b", "a"));
533 assert!(target_matches("a.b", "a"));
534 assert!(!target_matches("ab", "a"));
535 assert!(!target_matches("a:b", "a"));
536 assert!(!target_matches("b", "a"));
537 assert!(!target_matches("a", "a::b"));
538 assert!(target_matches("", ""));
539 assert!(!target_matches("::a", ""));
540 }
541}