1use {
2 super::{
3 directive::{DynamicDirective, StaticDirective},
4 matcher::{FieldMatch, PatternMatch, ValueMatch},
5 Filter,
6 },
7 crate::{Diagnostics, SmallVec},
8 miette::{Diagnostic, SourceSpan},
9 once_cell::sync::Lazy,
10 regex::Regex,
11 std::{ops::Range, str::FromStr},
12 thiserror::Error,
13 tracing::level_filters::STATIC_MAX_LEVEL,
14 tracing_core::{Level, LevelFilter},
15};
16
17impl FromStr for Filter {
18 type Err = Diagnostics<'static>;
19
20 fn from_str(s: &str) -> Result<Self, Diagnostics<'static>> {
22 let (filter, errs) = Self::parse(s);
23 if let Some(errs) = errs {
24 Err(errs.into_owned())
25 } else {
26 Ok(filter)
27 }
28 }
29}
30
31impl Filter {
32 pub fn parse(spec: &str) -> (Filter, Option<Diagnostics<'_>>) {
37 let recover_span = |substr: &str| {
38 let offset = substr.as_ptr() as usize - spec.as_ptr() as usize;
39 offset..offset + substr.len()
40 };
41
42 let mut directives = Vec::new();
43 let mut ignored = Vec::new();
44
45 for directive_spec in spec.split(',') {
46 match DynamicDirective::parse(directive_spec, recover_span) {
47 Ok(directive) => directives.push(directive),
48 Err(directive) => ignored.push(directive),
49 }
50 }
51
52 let ignored: Vec<_> = ignored
53 .into_iter()
54 .map(|x| Box::new(x) as Box<dyn Diagnostic + Send + Sync + 'static>)
55 .collect();
56 let (filter, disabled) = Self::from_directives(directives);
57 match (&*ignored, disabled) {
58 (&[], None) => (filter, None),
59 (_, disabled) => (
60 filter,
61 Some(Diagnostics {
62 error: None,
63 ignored,
64 disabled: disabled
65 .map(|x| Box::new(x) as Box<dyn Diagnostic + Send + Sync + 'static>),
66 source: spec.into(),
67 }),
68 ),
69 }
70 }
71
72 fn from_directives(directives: Vec<DynamicDirective>) -> (Filter, Option<DisabledDirectives>) {
73 let disabled: Vec<_> = directives
74 .iter()
75 .filter(|directive| directive.level > STATIC_MAX_LEVEL)
76 .collect();
77
78 let advice = if !disabled.is_empty() {
79 let mut disabled_advice = Vec::new();
80
81 for directive in disabled {
82 disabled_advice.push(DisabledDirective {
83 directive: format!("{}", directive),
84 level: directive.level.into_level().unwrap(),
85 target: directive
86 .target
87 .as_deref()
88 .map(|t| format!("the `{}` target", t))
89 .unwrap_or_else(|| "all targets".into()),
90 });
91 }
92
93 let (feature, earlier_level) = match STATIC_MAX_LEVEL.into_level() {
94 Some(Level::TRACE) => unreachable!(),
95 Some(Level::DEBUG) => ("max_level_debug", Some(Level::TRACE)),
96 Some(Level::INFO) => ("max_level_info", Some(Level::DEBUG)),
97 Some(Level::WARN) => ("max_level_warn", Some(Level::INFO)),
98 Some(Level::ERROR) => ("max_level_error", Some(Level::WARN)),
99 None => ("max_level_off", None),
100 };
101 let static_max = StaticMaxAdvice {
102 static_level: STATIC_MAX_LEVEL,
103 earlier_level,
104 feature,
105 };
106 Some(DisabledDirectives {
107 directives: disabled_advice,
108 static_max: Some(static_max),
109 })
110 } else {
111 None
112 };
113
114 let (dynamics, mut statics) = DynamicDirective::make_tables(directives);
115
116 if statics.directives.is_empty() && dynamics.directives.is_empty() {
117 statics.add(StaticDirective::default());
118 }
119
120 let filter = Filter {
121 scope: Default::default(),
122 statics,
123 dynamics,
124 by_cs: Default::default(),
125 };
126 (filter, advice)
127 }
128}
129
130impl FromStr for DynamicDirective {
131 type Err = Diagnostics<'static>;
132 fn from_str(s: &str) -> Result<Self, Self::Err> {
133 Self::parse(s, |substr: &str| {
134 let offset = substr.as_ptr() as usize - s.as_ptr() as usize;
135 offset..offset + substr.len()
136 })
137 .map_err(|ignored| {
138 Diagnostics {
139 error: None,
140 ignored: vec![Box::new(ignored)],
141 disabled: None,
142 source: s.into(),
143 }
144 .into_owned()
145 })
146 }
147}
148
149impl DynamicDirective {
150 fn parse(
151 mut spec: &str,
152 recover_span: impl Fn(&str) -> Range<usize>,
153 ) -> Result<Self, IgnoredDirective> {
154 if let Ok(level) = spec.parse() {
157 return Ok(DynamicDirective {
158 level,
159 span: None,
160 fields: SmallVec::new(),
161 target: None,
162 });
163 }
164
165 let mut span = None;
168 let mut fields = SmallVec::new();
169 let mut target = None;
170
171 static TARGET_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[\w:-]+").unwrap());
175
176 static SPAN_RE: Lazy<Regex> =
180 Lazy::new(|| Regex::new(r"(?P<name>[^\]\{]+)?(?:\{(?P<fields>[^\}]*)\})?").unwrap());
181 static FIELDS_RE: Lazy<Regex> =
182 Lazy::new(|| Regex::new(r"[[:word:]][[[:word:]]\.]*(?:=[^,]+)?(?:,|$)").unwrap());
183
184 let mut first_time = true;
185 let mut parse_target_span = |spec: &mut &str| -> Result<(), IgnoredDirective> {
186 if let Some(m) = TARGET_RE.find(spec) {
187 debug_assert_eq!(m.start(), 0);
189 target = Some(m.as_str().into());
190 *spec = &spec[m.end()..];
191 } else if spec.starts_with('[') {
192 *spec = spec.trim_start_matches('['); match spec.split_once(']') {
195 Some((span_spec, rest)) => {
196 let m = SPAN_RE.captures(span_spec).unwrap();
197 span = m.name("name").map(|m| m.as_str().into());
198 fields = m
199 .name("fields")
200 .map(|m| {
201 FIELDS_RE
202 .find_iter(m.as_str())
203 .map(|m| {
204 FieldMatch::parse(m.as_str()).map_err(|error| {
205 IgnoredDirective::InvalidRegex {
206 error,
207 span: recover_span(
208 m.as_str().split('=').nth(1).unwrap(),
210 )
211 .into(),
212 }
213 })
214 })
215 .collect::<Result<SmallVec<_>, _>>()
216 })
217 .transpose()?
218 .unwrap_or_default();
219 *spec = rest;
220 },
221 None => {
222 let spec = recover_span(spec);
223 return Err(IgnoredDirective::UnclosedSpan {
224 open: (spec.start - 1..spec.start).into(),
225 close: (spec.end..spec.end).into(),
226 });
227 },
228 }
229 } else if first_time {
230 return Err(IgnoredDirective::InvalidTarget {
231 span: recover_span(spec).into(),
232 });
233 }
234
235 first_time = false;
236 Ok(())
237 };
238
239 parse_target_span(&mut spec)?;
240 if !spec.starts_with('=') {
241 parse_target_span(&mut spec)?;
242 }
243
244 match spec {
247 "" | "=" => Ok(DynamicDirective {
248 span,
249 fields,
250 target,
251 level: LevelFilter::TRACE,
252 }),
253 _ if spec.starts_with('=') => {
254 let spec = &spec[1..];
255 match spec.parse() {
256 Ok(level) => Ok(DynamicDirective {
257 span,
258 fields,
259 target,
260 level,
261 }),
262 Err(_) => Err(IgnoredDirective::InvalidLevel {
263 span: recover_span(spec).into(),
264 }),
265 }
266 },
267 _ => Err(IgnoredDirective::InvalidTrailing {
268 span: recover_span(spec).into(),
269 }),
270 }
271 }
272}
273
274impl FieldMatch {
275 fn parse(s: &str) -> Result<Self, matchers::Error> {
276 let mut split = s.split('=');
277 Ok(FieldMatch {
278 name: split.next().unwrap_or_default().into(),
279 value: split.next().map(ValueMatch::parse).transpose()?,
280 })
281 }
282}
283
284impl ValueMatch {
285 fn parse(s: &str) -> Result<Self, matchers::Error> {
286 fn value_match_f64(v: f64) -> ValueMatch {
287 if v.is_nan() {
288 ValueMatch::NaN
289 } else {
290 ValueMatch::F64(v)
291 }
292 }
293
294 Err(())
295 .or_else(|_| s.parse().map(ValueMatch::Bool))
296 .or_else(|_| s.parse().map(ValueMatch::U64))
297 .or_else(|_| s.parse().map(ValueMatch::I64))
298 .or_else(|_| s.parse().map(value_match_f64))
299 .or_else(|_| {
300 s.parse()
301 .map(|matcher| PatternMatch {
302 matcher,
303 pattern: s.into(),
304 })
305 .map(Box::new)
306 .map(ValueMatch::Pat)
307 })
308 }
309}
310
311#[derive(Debug, Error, Diagnostic)]
312#[error("{} directives were ignored as invalid", .0.len())]
313#[diagnostic(severity(warning))]
314struct IgnoredDirectives(#[related] Vec<IgnoredDirective>);
315
316#[derive(Debug, Error, Diagnostic)]
317#[diagnostic(severity(warning))]
318enum IgnoredDirective {
319 #[error("invalid target specified")]
320 InvalidTarget {
321 #[label]
322 span: SourceSpan,
323 },
324 #[error("invalid level filter specified")]
325 #[diagnostic(help("valid level filters are OFF, ERROR, WARN, INFO, DEBUG, or TRACE"))]
326 InvalidLevel {
327 #[label]
328 span: SourceSpan,
329 },
330 #[error("invalid regex specified")]
331 InvalidRegex {
332 error: matchers::Error,
336 #[label("{}", .error)]
337 span: SourceSpan,
338 },
339 #[error("invalid trailing characters")]
340 InvalidTrailing {
341 #[label]
342 span: SourceSpan,
343 },
344 #[error("unclosed span directive")]
345 UnclosedSpan {
346 #[label("opened here")]
347 open: SourceSpan,
348 #[label("stopped looking here")]
349 close: SourceSpan,
350 },
351}
352
353#[derive(Debug, Error, Diagnostic)]
354#[diagnostic(severity(warning))]
355#[error("{} directives would enable traces that are disabled statically", .directives.len())]
356struct DisabledDirectives {
357 #[related]
358 directives: Vec<DisabledDirective>,
359 #[related]
360 static_max: Option<StaticMaxAdvice>,
361}
362
363#[derive(Debug, Error, Diagnostic)]
364#[diagnostic(severity(warning))]
365#[error("`{}` would enabled the {} level for {}", .directive, .level, .target)]
366struct DisabledDirective {
367 directive: String,
368 level: Level,
369 target: String,
370}
371
372#[derive(Debug, Error, Diagnostic)]
373#[diagnostic(
374 severity(advice),
375 help(
376 "to enable {}logging, remove the `{}` feature",
377 .earlier_level.map(|l| format!("{} ", l)).unwrap_or_default(),
378 .feature
379 ),
380)]
381#[error("the static max level is `{}`", .static_level)]
382struct StaticMaxAdvice {
383 static_level: LevelFilter,
384 earlier_level: Option<Level>,
385 feature: &'static str,
386}