tracing_filter/legacy/
parse.rs

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    /// Parse a filter from its string representation, discarding warnings.
21    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    /// Parse a filter from its string representation.
33    ///
34    /// Filter compilation can produce warnings even when it succeeds, thus
35    /// the nonstandard return type to provide [`Diagnostics`] on success.
36    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 it can be a global level filter, it is
155        // ^(?P<global_level>(?i:trace|debug|info|warn|error|off|[0-5]))$
156        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        // target and span parts are order insignificant
166        // ^(?:(?P<target>[\w:-]+)|(?P<span>\[[^\]]*\])){1,2}
167        let mut span = None;
168        let mut fields = SmallVec::new();
169        let mut target = None;
170
171        // This is the simplest way to check the \w character class; otherwise
172        // \p{Alphabetic} + \p{Nd} + \p{M} + \p{Pc} + \p{Join_Control}
173        // char::is_alphanumeric() + ????? + ?????? + ????????????????
174        static TARGET_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[\w:-]+").unwrap());
175
176        // The semantics of these are way too crazy to implement by hand :(
177        // Notably, the matches aren't anchored, meaning they can be partial.
178        // Just using the regex engine is the best way to maintain compatibilty.
179        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                // target
188                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                // span
193                *spec = spec.trim_start_matches('['); // yes, this is upstream behavior
194                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                                                    // ugly but correct
209                                                    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        // level or nothing
245        // (?:=(?P<level>(?i:trace|debug|info|warn|error|off|[0-5]))?)?$
246        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        // no, we are not going to parse the formatted regex error
333        // in order to translate it into miette span/labels
334        // it'd be nice, but it's not worth the brittle hacks
335        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}