1use crate::flexi_error::FlexiLoggerError;
2use crate::LevelFilter;
3
4#[cfg(feature = "textfilter")]
5use regex::Regex;
6use std::{collections::HashMap, env};
7
8#[derive(Clone, Debug, Default)]
56pub struct LogSpecification {
57 module_filters: Vec<ModuleFilter>,
58 #[cfg(feature = "textfilter")]
59 textfilter: Option<Box<Regex>>,
60}
61
62#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct ModuleFilter {
67 pub module_name: Option<String>,
69 pub level_filter: LevelFilter,
71}
72
73impl LogSpecification {
74 pub(crate) fn update_from(&mut self, other: Self) {
75 self.module_filters = other.module_filters;
76
77 #[cfg(feature = "textfilter")]
78 {
79 self.textfilter = other.textfilter;
80 }
81 }
82
83 pub(crate) fn max_level(&self) -> log::LevelFilter {
84 self.module_filters
85 .iter()
86 .map(|d| d.level_filter)
87 .max()
88 .unwrap_or(log::LevelFilter::Off)
89 }
90
91 #[must_use]
93 pub fn off() -> Self {
94 Self::default()
95 }
96
97 #[must_use]
99 pub fn error() -> Self {
100 Self::new_with(LevelFilter::Error)
101 }
102
103 #[must_use]
105 pub fn warn() -> Self {
106 Self::new_with(LevelFilter::Warn)
107 }
108
109 #[must_use]
111 pub fn info() -> Self {
112 Self::new_with(LevelFilter::Info)
113 }
114
115 #[must_use]
117 pub fn debug() -> Self {
118 Self::new_with(LevelFilter::Debug)
119 }
120
121 #[must_use]
123 pub fn trace() -> Self {
124 Self::new_with(LevelFilter::Trace)
125 }
126
127 #[must_use]
128 fn new_with(level_filter: LevelFilter) -> Self {
129 Self {
130 module_filters: vec![ModuleFilter {
131 module_name: None,
132 level_filter,
133 }],
134 #[cfg(feature = "textfilter")]
135 textfilter: None,
136 }
137 }
138
139 pub fn parse<S: AsRef<str>>(spec: S) -> Result<Self, FlexiLoggerError> {
145 let mut parse_errs = String::new();
146 let mut dirs = Vec::<ModuleFilter>::new();
147 let spec = spec.as_ref();
148 let mut parts = spec.split('/');
149 let mods = parts.next();
150 #[cfg(feature = "textfilter")]
151 let filter = parts.next();
152 if parts.next().is_some() {
153 push_err(
154 &format!("invalid log spec '{spec}' (too many '/'s), ignoring it"),
155 &mut parse_errs,
156 );
157 return parse_err(parse_errs, Self::off());
158 }
159 if let Some(m) = mods {
160 for s in m.split(',') {
161 let s = s.trim();
162 if s.is_empty() {
163 continue;
164 }
165 let mut parts = s.split('=');
166 let (log_level, name) = match (
167 parts.next().map(str::trim),
168 parts.next().map(str::trim),
169 parts.next(),
170 ) {
171 (Some(part_0), None, None) => {
172 if contains_whitespace(part_0, &mut parse_errs) {
173 continue;
174 }
175 match parse_level_filter(part_0.trim()) {
178 Ok(num) => (num, None),
179 Err(_) => (LevelFilter::max(), Some(part_0)),
180 }
181 }
182
183 (Some(part_0), Some(""), None) => {
184 if contains_whitespace(part_0, &mut parse_errs) {
185 continue;
186 }
187 (LevelFilter::max(), Some(part_0))
188 }
189
190 (Some(part_0), Some(part_1), None) => {
191 if contains_whitespace(part_0, &mut parse_errs) {
192 continue;
193 }
194 match parse_level_filter(part_1.trim()) {
195 Ok(num) => (num, Some(part_0.trim())),
196 Err(e) => {
197 push_err(&e.to_string(), &mut parse_errs);
198 continue;
199 }
200 }
201 }
202 _ => {
203 push_err(
204 &format!("invalid part in log spec '{s}', ignoring it"),
205 &mut parse_errs,
206 );
207 continue;
208 }
209 };
210 dirs.push(ModuleFilter {
211 module_name: name.map(ToString::to_string),
212 level_filter: log_level,
213 });
214 }
215 }
216
217 #[cfg(feature = "textfilter")]
218 let textfilter = filter.and_then(|filter| match Regex::new(filter) {
219 Ok(re) => Some(Box::new(re)),
220 Err(e) => {
221 push_err(&format!("invalid regex filter - {e}"), &mut parse_errs);
222 None
223 }
224 });
225
226 let logspec = Self {
227 module_filters: dirs.level_sort(),
228 #[cfg(feature = "textfilter")]
229 textfilter,
230 };
231
232 if parse_errs.is_empty() {
233 Ok(logspec)
234 } else {
235 parse_err(parse_errs, logspec)
236 }
237 }
238
239 pub fn env() -> Result<Self, FlexiLoggerError> {
246 match env::var("RUST_LOG") {
247 Ok(spec) => Self::parse(spec),
248 Err(..) => Ok(Self::off()),
249 }
250 }
251
252 pub fn env_or_parse<S: AsRef<str>>(given_spec: S) -> Result<Self, FlexiLoggerError> {
259 env::var("RUST_LOG")
260 .map_err(|_e| FlexiLoggerError::Poison )
261 .and_then(Self::parse)
262 .or_else(|_| Self::parse(given_spec.as_ref()))
263 }
264
265 #[must_use]
267 pub fn builder() -> LogSpecBuilder {
268 LogSpecBuilder::new()
269 }
270
271 #[cfg(feature = "specfile_without_notification")]
279 #[cfg_attr(docsrs, doc(cfg(feature = "specfile")))]
280 pub fn from_toml<S: AsRef<str>>(s: S) -> Result<Self, FlexiLoggerError> {
281 #[derive(Clone, Debug, serde_derive::Deserialize)]
282 struct LogSpecFileFormat {
283 pub global_level: Option<String>,
284 pub global_pattern: Option<String>,
285 pub modules: Option<std::collections::BTreeMap<String, String>>,
286 }
287 let s = s.as_ref();
288 let logspec_ff: LogSpecFileFormat = toml::from_str(s)?;
289 let mut parse_errs = String::new();
290 let mut module_filters = Vec::<ModuleFilter>::new();
291
292 if let Some(s) = logspec_ff.global_level {
293 module_filters.push(ModuleFilter {
294 module_name: None,
295 level_filter: parse_level_filter(s)?,
296 });
297 }
298
299 for (k, v) in logspec_ff.modules.unwrap_or_default() {
300 module_filters.push(ModuleFilter {
301 module_name: Some(k),
302 level_filter: parse_level_filter(v)?,
303 });
304 }
305
306 #[cfg(feature = "textfilter")]
307 let textfilter = match logspec_ff.global_pattern {
308 None => None,
309 Some(s) => match Regex::new(&s) {
310 Ok(re) => Some(Box::new(re)),
311 Err(e) => {
312 push_err(&format!("invalid regex filter - {e}"), &mut parse_errs);
313 None
314 }
315 },
316 };
317
318 let logspec = Self {
319 module_filters: module_filters.level_sort(),
320 #[cfg(feature = "textfilter")]
321 textfilter,
322 };
323 if parse_errs.is_empty() {
324 Ok(logspec)
325 } else {
326 parse_err(parse_errs, logspec)
327 }
328 }
329
330 #[cfg(feature = "specfile_without_notification")]
338 #[cfg_attr(docsrs, doc(cfg(feature = "specfile")))]
339 pub fn to_toml(&self, w: &mut dyn std::io::Write) -> Result<(), FlexiLoggerError> {
340 self.to_toml_impl(w).map_err(FlexiLoggerError::SpecfileIo)
341 }
342
343 #[cfg(feature = "specfile_without_notification")]
344 fn to_toml_impl(&self, w: &mut dyn std::io::Write) -> Result<(), std::io::Error> {
345 w.write_all(b"### Optional: Default log level\n")?;
346 let last = self.module_filters.last();
347 match last {
348 Some(last_v) if last_v.module_name.is_none() => {
349 w.write_all(
350 format!(
351 "global_level = '{}'\n",
352 last_v.level_filter.to_string().to_lowercase()
353 )
354 .as_bytes(),
355 )?;
356 }
357 _ => {
358 w.write_all(b"#global_level = 'info'\n")?;
359 }
360 }
361
362 w.write_all(
363 b"\n### Optional: specify a regular expression to suppress all messages that don't match\n",
364 )?;
365 w.write_all(b"#global_pattern = 'foo'\n")?;
366
367 w.write_all(
368 b"\n### Specific log levels per module are optionally defined in this section\n",
369 )?;
370 w.write_all(b"[modules]\n")?;
371 if self.module_filters.is_empty() || self.module_filters[0].module_name.is_none() {
372 w.write_all(b"#'mod1' = 'warn'\n")?;
373 w.write_all(b"#'mod2' = 'debug'\n")?;
374 w.write_all(b"#'mod2::mod3' = 'trace'\n")?;
375 }
376 for mf in &self.module_filters {
377 if let Some(ref name) = mf.module_name {
378 w.write_all(
379 format!(
380 "'{}' = '{}'\n",
381 name,
382 mf.level_filter.to_string().to_lowercase()
383 )
384 .as_bytes(),
385 )?;
386 }
387 }
388 Ok(())
389 }
390
391 #[must_use]
393 pub fn enabled(&self, level: log::Level, writing_module: &str) -> bool {
394 for module_filter in &self.module_filters {
396 match module_filter.module_name {
397 Some(ref module_name) => {
398 if writing_module.starts_with(module_name) {
399 return level <= module_filter.level_filter;
400 }
401 }
402 None => return level <= module_filter.level_filter,
403 }
404 }
405 false
406 }
407
408 #[must_use]
410 pub fn module_filters(&self) -> &Vec<ModuleFilter> {
411 &self.module_filters
412 }
413
414 #[cfg(feature = "textfilter")]
418 #[must_use]
419 pub fn text_filter(&self) -> Option<&Regex> {
420 self.textfilter.as_deref()
421 }
422
423 #[must_use]
426 pub fn level_for_module(&self, module: Option<&str>) -> Option<LevelFilter> {
427 self.module_filters()
428 .iter()
429 .find(|mod_filter| mod_filter.module_name.as_deref() == module)
430 .map(|found_filter| found_filter.level_filter)
431 }
432}
433
434impl std::fmt::Display for LogSpecification {
435 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436 let mut write_comma = false;
437 if let Some(last) = self.module_filters.last() {
439 if last.module_name.is_none() {
440 write!(f, "{}", last.level_filter.to_string().to_lowercase())?;
441 write_comma = true;
442 }
443 }
444
445 for mf in &self.module_filters {
451 if let Some(ref name) = mf.module_name {
452 if write_comma {
453 write!(f, ", ")?;
454 }
455 write!(f, "{name} = {}", mf.level_filter.to_string().to_lowercase())?;
456 write_comma = true;
457 }
458 }
459 Ok(())
460 }
461}
462
463impl std::convert::TryFrom<&str> for LogSpecification {
464 type Error = FlexiLoggerError;
465 fn try_from(value: &str) -> Result<Self, Self::Error> {
466 LogSpecification::parse(value)
467 }
468}
469
470impl std::convert::TryFrom<&String> for LogSpecification {
471 type Error = FlexiLoggerError;
472 fn try_from(value: &String) -> Result<Self, Self::Error> {
473 LogSpecification::parse(value)
474 }
475}
476
477impl From<LevelFilter> for LogSpecification {
478 fn from(value: LevelFilter) -> Self {
479 match value {
480 LevelFilter::Error => LogSpecification::error(),
481 LevelFilter::Warn => LogSpecification::warn(),
482 LevelFilter::Info => LogSpecification::info(),
483 LevelFilter::Debug => LogSpecification::debug(),
484 LevelFilter::Trace => LogSpecification::trace(),
485 LevelFilter::Off => LogSpecification::off(),
486 }
487 }
488}
489
490fn push_err(s: &str, parse_errs: &mut String) {
491 if !parse_errs.is_empty() {
492 parse_errs.push_str("; ");
493 }
494 parse_errs.push_str(s);
495}
496
497fn parse_err(
498 errors: String,
499 logspec: LogSpecification,
500) -> Result<LogSpecification, FlexiLoggerError> {
501 Err(FlexiLoggerError::Parse(errors, logspec))
502}
503
504fn parse_level_filter<S: AsRef<str>>(s: S) -> Result<LevelFilter, FlexiLoggerError> {
505 match s.as_ref().to_lowercase().as_ref() {
506 "off" => Ok(LevelFilter::Off),
507 "error" => Ok(LevelFilter::Error),
508 "warn" => Ok(LevelFilter::Warn),
509 "info" => Ok(LevelFilter::Info),
510 "debug" => Ok(LevelFilter::Debug),
511 "trace" => Ok(LevelFilter::Trace),
512 _ => Err(FlexiLoggerError::LevelFilter(format!(
513 "unknown level filter: {}",
514 s.as_ref()
515 ))),
516 }
517}
518
519fn contains_whitespace(s: &str, parse_errs: &mut String) -> bool {
520 let result = s.chars().any(char::is_whitespace);
521 if result {
522 push_err(
523 &format!("ignoring invalid part in log spec '{s}' (contains a whitespace)"),
524 parse_errs,
525 );
526 }
527 result
528}
529
530#[allow(clippy::needless_doctest_main)]
531#[derive(Clone, Debug, Default)]
570pub struct LogSpecBuilder {
571 module_filters: HashMap<Option<String>, LevelFilter>,
572}
573
574impl LogSpecBuilder {
575 #[must_use]
577 pub fn new() -> Self {
578 let mut modfilmap = HashMap::new();
579 modfilmap.insert(None, LevelFilter::Off);
580 Self {
581 module_filters: modfilmap,
582 }
583 }
584
585 #[must_use]
587 pub fn from_module_filters(module_filters: &[ModuleFilter]) -> Self {
588 let mut modfilmap = HashMap::new();
589 for mf in module_filters {
590 modfilmap.insert(mf.module_name.clone(), mf.level_filter);
591 }
592 Self {
593 module_filters: modfilmap,
594 }
595 }
596
597 pub fn default(&mut self, lf: LevelFilter) -> &mut Self {
599 self.module_filters.insert(None, lf);
600 self
601 }
602
603 pub fn module<M: AsRef<str>>(&mut self, module_name: M, lf: LevelFilter) -> &mut Self {
605 self.module_filters
606 .insert(Some(module_name.as_ref().to_owned()), lf);
607 self
608 }
609
610 pub fn remove<M: AsRef<str>>(&mut self, module_name: M) -> &mut Self {
612 self.module_filters
613 .remove(&Some(module_name.as_ref().to_owned()));
614 self
615 }
616
617 pub fn insert_modules_from(&mut self, other: LogSpecification) -> &mut Self {
619 for module_filter in other.module_filters {
620 self.module_filters
621 .insert(module_filter.module_name, module_filter.level_filter);
622 }
623 self
624 }
625
626 #[must_use]
628 pub fn finalize(self) -> LogSpecification {
629 LogSpecification {
630 module_filters: self.module_filters.into_vec_module_filter(),
631 #[cfg(feature = "textfilter")]
632 textfilter: None,
633 }
634 }
635
636 #[cfg(feature = "textfilter")]
640 #[must_use]
641 pub fn finalize_with_textfilter(self, tf: Regex) -> LogSpecification {
642 LogSpecification {
643 module_filters: self.module_filters.into_vec_module_filter(),
644 textfilter: Some(Box::new(tf)),
645 }
646 }
647
648 #[must_use]
650 pub fn build(&self) -> LogSpecification {
651 LogSpecification {
652 module_filters: self.module_filters.clone().into_vec_module_filter(),
653 #[cfg(feature = "textfilter")]
654 textfilter: None,
655 }
656 }
657
658 #[cfg(feature = "textfilter")]
662 #[cfg_attr(docsrs, doc(cfg(feature = "textfilter")))]
663 #[must_use]
664 pub fn build_with_textfilter(&self, tf: Option<Regex>) -> LogSpecification {
665 LogSpecification {
666 module_filters: self.module_filters.clone().into_vec_module_filter(),
667 textfilter: tf.map(Box::new),
668 }
669 }
670}
671
672trait IntoVecModuleFilter {
673 fn into_vec_module_filter(self) -> Vec<ModuleFilter>;
674}
675impl IntoVecModuleFilter for HashMap<Option<String>, LevelFilter> {
676 fn into_vec_module_filter(self) -> Vec<ModuleFilter> {
677 let mf: Vec<ModuleFilter> = self
678 .into_iter()
679 .map(|(k, v)| ModuleFilter {
680 module_name: k,
681 level_filter: v,
682 })
683 .collect();
684 mf.level_sort()
685 }
686}
687
688trait LevelSort {
689 fn level_sort(self) -> Vec<ModuleFilter>;
690}
691impl LevelSort for Vec<ModuleFilter> {
692 fn level_sort(mut self) -> Vec<ModuleFilter> {
695 self.sort_by(|a, b| {
696 let a_len = a.module_name.as_ref().map_or(0, String::len);
697 let b_len = b.module_name.as_ref().map_or(0, String::len);
698 b_len.cmp(&a_len)
699 });
700 self
701 }
702}
703
704#[cfg(test)]
705mod tests {
706 use crate::LogSpecification;
707 use log::{Level, LevelFilter};
708
709 #[test]
710 fn parse_roundtrip() {
711 let ss = [
712 "crate1::mod1 = error, crate1::mod2 = trace, crate2 = debug",
713 "debug, crate1::mod2 = trace, crate2 = error",
714 ];
715 for s in &ss {
716 let spec = LogSpecification::parse(s).unwrap();
717 assert_eq!(*s, spec.to_string().as_str());
718 }
719 assert_eq!("", LogSpecification::default().to_string().as_str());
720 }
721
722 #[test]
723 fn parse_logging_spec_valid() {
724 let spec =
725 LogSpecification::parse("crate1::mod1 = error, crate1::mod2, crate2 = debug").unwrap();
726 assert_eq!(spec.module_filters().len(), 3);
727 assert_eq!(
728 spec.module_filters()[0].module_name,
729 Some("crate1::mod1".to_string())
730 );
731 assert_eq!(spec.module_filters()[0].level_filter, LevelFilter::Error);
732
733 assert_eq!(
734 spec.module_filters()[1].module_name,
735 Some("crate1::mod2".to_string())
736 );
737 assert_eq!(spec.module_filters()[1].level_filter, LevelFilter::max());
738
739 assert_eq!(
740 spec.module_filters()[2].module_name,
741 Some("crate2".to_string())
742 );
743 assert_eq!(spec.module_filters()[2].level_filter, LevelFilter::Debug);
744
745 #[cfg(feature = "textfilter")]
746 assert!(spec.text_filter().is_none());
747 }
748
749 #[test]
750 fn parse_logging_spec_invalid_crate() {
751 assert!(LogSpecification::parse("crate1::mod1=warn=info,crate2=debug").is_err());
753 }
754
755 #[test]
756 fn parse_logging_spec_wrong_log_level() {
757 assert!(LogSpecification::parse("crate1::mod1=wrong, crate2=warn").is_err());
758 }
759
760 #[test]
761 fn parse_logging_spec_empty_log_level() {
762 assert!(LogSpecification::parse("crate1::mod1=wrong, crate2=").is_err());
763 }
764
765 #[test]
766 fn parse_logging_spec_global() {
767 let spec = LogSpecification::parse("warn,crate2=debug").unwrap();
768 assert_eq!(spec.module_filters().len(), 2);
769
770 assert_eq!(spec.module_filters()[1].module_name, None);
771 assert_eq!(spec.module_filters()[1].level_filter, LevelFilter::Warn);
772
773 assert_eq!(
774 spec.module_filters()[0].module_name,
775 Some("crate2".to_string())
776 );
777 assert_eq!(spec.module_filters()[0].level_filter, LevelFilter::Debug);
778
779 #[cfg(feature = "textfilter")]
780 assert!(spec.text_filter().is_none());
781 }
782
783 #[test]
784 #[cfg(feature = "textfilter")]
785 fn parse_logging_spec_valid_filter() {
786 let spec = LogSpecification::parse(" crate1::mod1 = error , crate1::mod2,crate2=debug/abc")
787 .unwrap();
788 assert_eq!(spec.module_filters().len(), 3);
789
790 assert_eq!(
791 spec.module_filters()[0].module_name,
792 Some("crate1::mod1".to_string())
793 );
794 assert_eq!(spec.module_filters()[0].level_filter, LevelFilter::Error);
795
796 assert_eq!(
797 spec.module_filters()[1].module_name,
798 Some("crate1::mod2".to_string())
799 );
800 assert_eq!(spec.module_filters()[1].level_filter, LevelFilter::max());
801
802 assert_eq!(
803 spec.module_filters()[2].module_name,
804 Some("crate2".to_string())
805 );
806 assert_eq!(spec.module_filters()[2].level_filter, LevelFilter::Debug);
807 assert!(
808 spec.text_filter().is_some()
809 && spec.text_filter().as_ref().unwrap().to_string() == "abc"
810 );
811 }
812
813 #[test]
814 fn parse_logging_spec_invalid_crate_filter() {
815 assert!(LogSpecification::parse("crate1::mod1=error=warn,crate2=debug/a.c").is_err());
816 }
817
818 #[test]
819 #[cfg(feature = "textfilter")]
820 fn parse_logging_spec_empty_with_filter() {
821 let spec = LogSpecification::parse("crate1/a*c").unwrap();
822 assert_eq!(spec.module_filters().len(), 1);
823 assert_eq!(
824 spec.module_filters()[0].module_name,
825 Some("crate1".to_string())
826 );
827 assert_eq!(spec.module_filters()[0].level_filter, LevelFilter::max());
828 assert!(
829 spec.text_filter().is_some()
830 && spec.text_filter().as_ref().unwrap().to_string() == "a*c"
831 );
832 }
833
834 #[test]
835 fn reuse_logspec_builder() {
836 let mut builder = crate::LogSpecBuilder::new();
837
838 builder.default(LevelFilter::Info);
839 builder.module("carlo", LevelFilter::Debug);
840 builder.module("toni", LevelFilter::Warn);
841 let spec1 = builder.build();
842
843 assert_eq!(
844 spec1.module_filters()[0].module_name,
845 Some("carlo".to_string())
846 );
847 assert_eq!(spec1.module_filters()[0].level_filter, LevelFilter::Debug);
848
849 assert_eq!(
850 spec1.module_filters()[1].module_name,
851 Some("toni".to_string())
852 );
853 assert_eq!(spec1.module_filters()[1].level_filter, LevelFilter::Warn);
854
855 assert_eq!(spec1.module_filters().len(), 3);
856 assert_eq!(spec1.module_filters()[2].module_name, None);
857 assert_eq!(spec1.module_filters()[2].level_filter, LevelFilter::Info);
858
859 builder.default(LevelFilter::Error);
860 builder.remove("carlo");
861 builder.module("greta", LevelFilter::Trace);
862 let spec2 = builder.build();
863
864 assert_eq!(spec2.module_filters().len(), 3);
865 assert_eq!(spec2.module_filters()[2].module_name, None);
866 assert_eq!(spec2.module_filters()[2].level_filter, LevelFilter::Error);
867
868 assert_eq!(
869 spec2.module_filters()[0].module_name,
870 Some("greta".to_string())
871 );
872 assert_eq!(spec2.module_filters()[0].level_filter, LevelFilter::Trace);
873
874 assert_eq!(
875 spec2.module_filters()[1].module_name,
876 Some("toni".to_string())
877 );
878 assert_eq!(spec2.module_filters()[1].level_filter, LevelFilter::Warn);
879 }
880
881 #[test]
884 fn match_full_path() {
885 let spec = LogSpecification::parse("crate2=info,crate1::mod1=warn").unwrap();
886 assert!(spec.enabled(Level::Warn, "crate1::mod1"));
887 assert!(!spec.enabled(Level::Info, "crate1::mod1"));
888 assert!(spec.enabled(Level::Info, "crate2"));
889 assert!(!spec.enabled(Level::Debug, "crate2"));
890 }
891
892 #[test]
893 fn no_match() {
894 let spec = LogSpecification::parse("crate2=info,crate1::mod1=warn").unwrap();
895 assert!(!spec.enabled(Level::Warn, "crate3"));
896 }
897
898 #[test]
899 fn match_beginning() {
900 let spec = LogSpecification::parse("crate2=info,crate1::mod1=warn").unwrap();
901 assert!(spec.enabled(Level::Info, "crate2::mod1"));
902 }
903
904 #[test]
905 fn match_beginning_longest_match() {
906 let spec = LogSpecification::parse(
907 "abcd = info, abcd::mod1 = error, klmn::mod = debug, klmn = info",
908 )
909 .unwrap();
910 assert!(spec.enabled(Level::Error, "abcd::mod1::foo"));
911 assert!(!spec.enabled(Level::Warn, "abcd::mod1::foo"));
912 assert!(spec.enabled(Level::Warn, "abcd::mod2::foo"));
913 assert!(!spec.enabled(Level::Debug, "abcd::mod2::foo"));
914
915 assert!(!spec.enabled(Level::Debug, "klmn"));
916 assert!(!spec.enabled(Level::Debug, "klmn::foo::bar"));
917 assert!(spec.enabled(Level::Info, "klmn::foo::bar"));
918 }
919
920 #[test]
921 fn match_default1() {
922 let spec = LogSpecification::parse("info,abcd::mod1=warn").unwrap();
923 assert!(spec.enabled(Level::Warn, "abcd::mod1"));
924 assert!(spec.enabled(Level::Info, "crate2::mod2"));
925 }
926
927 #[test]
928 fn match_default2() {
929 let spec = LogSpecification::parse("modxyz=error, info, abcd::mod1=warn").unwrap();
930 assert!(spec.enabled(Level::Warn, "abcd::mod1"));
931 assert!(spec.enabled(Level::Info, "crate2::mod2"));
932 }
933
934 #[test]
935 fn rocket() {
936 let spec = LogSpecification::parse("info, rocket=off, serenity=off").unwrap();
937 assert!(spec.enabled(Level::Info, "itsme"));
938 assert!(spec.enabled(Level::Warn, "abcd::mod1"));
939 assert!(!spec.enabled(Level::Debug, "abcd::mod1"));
940 assert!(!spec.enabled(Level::Error, "rocket::rocket"));
941 assert!(!spec.enabled(Level::Warn, "rocket::rocket"));
942 assert!(!spec.enabled(Level::Info, "rocket::rocket"));
943 }
944
945 #[test]
946 fn add_filters() {
947 let mut builder = crate::LogSpecBuilder::new();
948
949 builder.default(LevelFilter::Debug);
950 builder.module("carlo", LevelFilter::Debug);
951 builder.module("toni", LevelFilter::Warn);
952
953 builder.insert_modules_from(
954 LogSpecification::parse("info, may=error, toni::heart = trace").unwrap(),
955 );
956 let spec = builder.build();
957
958 assert_eq!(spec.module_filters().len(), 5);
959
960 assert_eq!(
961 spec.module_filters()[0].module_name,
962 Some("toni::heart".to_string())
963 );
964 assert_eq!(spec.module_filters()[0].level_filter, LevelFilter::Trace);
965
966 assert_eq!(
967 spec.module_filters()[1].module_name,
968 Some("carlo".to_string())
969 );
970 assert_eq!(spec.module_filters()[1].level_filter, LevelFilter::Debug);
971
972 assert_eq!(
973 spec.module_filters()[2].module_name,
974 Some("toni".to_string())
975 );
976 assert_eq!(spec.module_filters()[2].level_filter, LevelFilter::Warn);
977
978 assert_eq!(
979 spec.module_filters()[3].module_name,
980 Some("may".to_string())
981 );
982 assert_eq!(spec.module_filters()[3].level_filter, LevelFilter::Error);
983
984 assert_eq!(spec.module_filters()[4].module_name, None);
985 assert_eq!(spec.module_filters()[4].level_filter, LevelFilter::Info);
986 }
987
988 #[test]
989 fn zero_level() {
990 let spec = LogSpecification::parse("info,crate1::mod1=off").unwrap();
991 assert!(!spec.enabled(Level::Error, "crate1::mod1"));
992 assert!(spec.enabled(Level::Info, "crate2::mod2"));
993 }
994}
995
996#[cfg(test)]
997#[cfg(feature = "specfile_without_notification")]
998mod test_with_specfile {
999 use log::LevelFilter;
1000
1001 #[cfg(feature = "specfile_without_notification")]
1002 use crate::LogSpecification;
1003
1004 #[test]
1005 fn specfile() {
1006 compare_specs("", "");
1007
1008 compare_specs(
1009 "[modules]\n\
1010 ",
1011 "",
1012 );
1013
1014 compare_specs(
1015 "global_level = 'info'\n\
1016 \n\
1017 [modules]\n\
1018 ",
1019 "info",
1020 );
1021
1022 compare_specs(
1023 "global_level = 'info'\n\
1024 \n\
1025 [modules]\n\
1026 'mod1::mod2' = 'debug'\n\
1027 'mod3' = 'trace'\n\
1028 ",
1029 "info, mod1::mod2 = debug, mod3 = trace",
1030 );
1031
1032 compare_specs(
1033 "global_level = 'info'\n\
1034 global_pattern = 'Foo'\n\
1035 \n\
1036 [modules]\n\
1037 'mod1::mod2' = 'debug'\n\
1038 'mod3' = 'trace'\n\
1039 ",
1040 "info, mod1::mod2 = debug, mod3 = trace /Foo",
1041 );
1042 }
1043
1044 #[cfg(feature = "specfile_without_notification")]
1045 fn compare_specs(toml: &str, spec_string: &str) {
1046 let ls_toml = LogSpecification::from_toml(toml).unwrap();
1047 let ls_spec = LogSpecification::parse(spec_string).unwrap();
1048
1049 assert_eq!(ls_toml.module_filters, ls_spec.module_filters);
1050 assert_eq!(ls_toml.textfilter.is_none(), ls_spec.textfilter.is_none());
1051 if ls_toml.textfilter.is_some() && ls_spec.textfilter.is_some() {
1052 assert_eq!(
1053 ls_toml.textfilter.unwrap().to_string(),
1054 ls_spec.textfilter.unwrap().to_string()
1055 );
1056 }
1057 }
1058
1059 #[test]
1060 fn test_level_for_module() {
1061 let spec = LogSpecification::parse("info,crate1::mod1=warn").unwrap();
1062 assert_eq!(spec.level_for_module(None), Some(LevelFilter::Info));
1063 assert_eq!(
1064 spec.level_for_module(Some("crate1::mod1")),
1065 Some(LevelFilter::Warn)
1066 );
1067 assert_eq!(spec.level_for_module(Some("crate2::mod2")), None,);
1068 assert_eq!(spec.level_for_module(Some("crate3")), None);
1069 }
1070}