tracing_filter/simple/
parse.rs1use {
2 super::{Directive, Filter},
3 crate::Diagnostics,
4 miette::{Diagnostic, SourceSpan},
5 std::{cmp, str::FromStr},
6 thiserror::Error,
7 tracing_core::LevelFilter,
8};
9
10impl Filter {
11 pub fn parse(spec: &str) -> (Option<Filter>, Option<Diagnostics<'_>>) {
16 let recover_span = |substr: &str| {
20 let offset = substr.as_ptr() as usize - spec.as_ptr() as usize;
21 offset..offset + substr.len()
22 };
23
24 let mut directives = Vec::new();
25 let mut parts = spec.split('/');
26 let dirs = parts.next();
27 let regex = parts.next();
28
29 if let Some(after) = parts.next() {
30 let regex = recover_span(regex.unwrap());
31 let after = recover_span(after);
32 let error = Error::MultipleSlash {
33 slash: (after.start - 1..after.start).into(),
34 regex: (regex.start..spec.len()).into(),
35 };
36 return (
37 None,
38 Some(Diagnostics {
39 error: Some(Box::new(error)),
40 ignored: Vec::new(),
41 disabled: None,
42 source: spec.into(),
43 }),
44 );
45 }
46
47 let mut warnings = Vec::new();
48
49 if let Some(dirs) = dirs {
50 for dir in dirs.split(',').map(|dir| dir.trim()) {
51 if dir.is_empty() {
52 continue;
53 }
54 let mut parts = dir.split('=');
55 let (log_level, name) =
56 match (parts.next(), parts.next().map(str::trim), parts.next()) {
57 (Some(part0), None, None) => {
58 match part0.parse() {
61 Ok(num) => (num, None),
62 Err(_) => (LevelFilter::TRACE, Some(part0)),
63 }
64 },
65 (Some(part0), Some(""), None) => (LevelFilter::TRACE, Some(part0)),
66 (Some(part0), Some(part1), None) => match part1.parse() {
67 Ok(num) => (num, Some(part0)),
68 _ => {
69 warnings.push(Warning::InvalidLevel {
70 span: recover_span(part1).into(),
71 });
72 continue;
73 },
74 },
75 (Some(_part0), Some(part1), Some(_part2)) => {
76 let part1 = recover_span(part1);
77 let dir = recover_span(dir);
78 warnings.push(Warning::InvalidLevel {
79 span: (part1.start..dir.end).into(),
80 });
81 continue;
82 },
83 _ => unreachable!(),
84 };
85 let directive = Directive {
86 target: name.map(Into::into),
87 level: log_level,
88 };
89 let ix = directives.binary_search_by(|x: &Directive| {
90 let a = x.target.as_ref().map(|x| x.len()).unwrap_or(0);
91 let b = directive.target.as_ref().map(|x| x.len()).unwrap_or(0);
92 match a.cmp(&b) {
93 cmp::Ordering::Equal => x.target.cmp(&directive.target),
94 ordering => ordering,
95 }
96 });
97 match ix {
98 Ok(ix) => directives[ix] = directive,
99 Err(ix) => directives.insert(ix, directive),
100 }
101 }
102 }
103
104 let regex = regex.and_then(|regex| match regex::Regex::new(regex) {
105 Ok(regex) => Some(regex),
106 Err(error) => {
107 warnings.push(Warning::InvalidRegex {
108 error,
109 span: recover_span(regex).into(),
110 });
111 None
112 },
113 });
114
115 let _ = regex; let filter = Some(Filter { directives, regex });
117 let report = if warnings.is_empty() {
118 None
119 } else {
120 Some(Diagnostics {
121 error: None,
122 ignored: warnings
123 .into_iter()
124 .map(|x| Box::new(x) as Box<dyn Diagnostic + Send + Sync + 'static>)
125 .collect(),
126 disabled: None,
127 source: spec.into(),
128 })
129 };
130
131 (filter, report)
132 }
133}
134
135impl FromStr for Filter {
136 type Err = Diagnostics<'static>;
137
138 fn from_str(spec: &str) -> Result<Self, Diagnostics<'static>> {
140 let (filter, errs) = Self::parse(spec);
141 filter.ok_or_else(|| {
142 errs.expect("filter compilation failed without any diagnostics")
143 .into_owned()
144 })
145 }
146}
147
148#[derive(Debug, Error, Diagnostic)]
149#[diagnostic(severity(warning))]
150enum Warning {
151 #[error("invalid level filter specified")]
152 #[diagnostic(help("valid level filters are OFF, ERROR, WARN, INFO, DEBUG, or TRACE"))]
153 InvalidLevel {
154 #[label]
155 span: SourceSpan,
156 },
157 #[error("invalid regex specified")]
158 #[diagnostic(code(tracing_filter::simple::InvalidRegex))]
159 InvalidRegex {
160 error: regex::Error,
164 #[label("{}", .error)]
165 span: SourceSpan,
166 },
167}
168
169#[derive(Debug, Error, Diagnostic)]
170#[diagnostic(severity(error))]
171enum Error {
172 #[error("logging spec has too many `/`s")]
173 #[diagnostic(help("regex filters may not contain `/`"))]
174 MultipleSlash {
175 #[label("this `/` is not allowed ...")]
176 slash: SourceSpan,
177 #[label("... in this regex filter")]
178 regex: SourceSpan,
179 },
180}