tracing_filter/simple/
mod.rs

1//! Support for simple filters, which match `env_logger`'s filter format.
2
3use {
4    crate::{Diagnostics, DEFAULT_ENV},
5    compact_str::CompactString,
6    std::{borrow::Cow, cmp, env, ffi::OsStr, fmt},
7    tracing_core::{Interest, LevelFilter, Metadata},
8    tracing_subscriber::layer::Context,
9};
10
11mod parse;
12
13/// A filter matching the semantics of the [`env_logger`] crate's filter format.
14///
15/// [`env_logger`]: https://docs.rs/env_logger/0.9.0/env_logger/
16///
17/// # Example
18///
19/// Use the `RUST_LOG` filter if it is set, but provide a fallback filter if the
20/// environment variable is not set.
21///
22/// ```rust
23/// # use tracing_filter::{DiagnosticsTheme, legacy::Filter};
24/// # use {tracing::{error, warn}, tracing_subscriber::prelude::*};
25/// let (filter, diagnostics) = Filter::from_default_env()
26///     .unwrap_or_else(|| Filter::parse("noisy_crate=warn,info"));
27///
28/// tracing_subscriber::registry()
29///     .with(filter.layer())
30///     .with(tracing_subscriber::fmt::layer())
31///     .init();
32///
33/// if let Some(diagnostics) = diagnostics {
34///     if let Some(error) = diagnostics.error(DiagnosticsTheme::default()) {
35///         error!("{error}");
36///     }
37///     if let Some(warn) = diagnostics.warn(DiagnosticsTheme::default()) {
38///         warn!("{warn}");
39///     }
40/// }
41/// ```
42#[derive(Debug, Default)]
43pub struct Filter {
44    directives: Vec<Directive>,
45    regex: Option<regex::Regex>,
46}
47
48#[derive(Debug, PartialEq, Eq)]
49struct Directive {
50    target: Option<CompactString>,
51    level: LevelFilter,
52}
53
54impl fmt::Display for Filter {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        for directive in &*self.directives {
57            if let Some(target) = &directive.target {
58                write!(f, "{}=", target)?;
59            }
60            write!(f, "{},", directive.level)?;
61        }
62
63        if let Some(regex) = &self.regex {
64            write!(f, "/{}", regex)?;
65        }
66
67        Ok(())
68    }
69}
70
71impl Filter {
72    /// Create a new filter, ignoring any invalid directives. It is highly
73    /// recommended you use [`Self::parse`] instead, and display the warnings for
74    /// ignored directives.
75    pub fn new(spec: &str) -> Self {
76        spec.parse().unwrap_or_else(|_| Self::empty())
77    }
78
79    /// Create an empty filter (i.e. one that filters nothing out).
80    pub const fn empty() -> Self {
81        Self {
82            directives: Vec::new(),
83            regex: None,
84        }
85    }
86
87    /// Create a filter from the default `RUST_LOG` environment.
88    pub fn from_default_env() -> Option<(Self, Option<Diagnostics<'static>>)> {
89        Self::from_env(DEFAULT_ENV)
90    }
91
92    /// Create a filter from the environment.
93    pub fn from_env(key: impl AsRef<OsStr>) -> Option<(Self, Option<Diagnostics<'static>>)> {
94        let s = env::var(key).ok()?;
95        let (filter, err) = Self::parse(&s);
96        Some((
97            filter.unwrap_or_default(),
98            match err {
99                None => None,
100                Some(x) => Some({
101                    Diagnostics {
102                        error: x.error,
103                        ignored: x.ignored,
104                        disabled: x.disabled,
105                        source: s.into(),
106                    }
107                }),
108            },
109        ))
110    }
111
112    /// Lift this filter to a filter layer.
113    pub fn layer(self) -> crate::FilterLayer<Self> {
114        crate::FilterLayer::new(self)
115    }
116}
117
118impl Filter {
119    // TODO: parse[_default]_env; configure default then override by environment
120
121    /// Add a new filter directive.
122    pub fn add_directive<'a>(
123        &mut self,
124        target: Option<impl Into<Cow<'a, str>>>,
125        level: impl Into<LevelFilter>,
126    ) {
127        let target = target.map(Into::into).map(Into::into);
128        let level = level.into();
129        let directive = Directive { target, level };
130        let ix = self.directives.binary_search_by(|x: &Directive| {
131            let a = x.target.as_ref().map(|x| x.len()).unwrap_or(0);
132            let b = directive.target.as_ref().map(|x| x.len()).unwrap_or(0);
133            match a.cmp(&b) {
134                cmp::Ordering::Equal => x.target.cmp(&directive.target),
135                ordering => ordering,
136            }
137        });
138        match ix {
139            Ok(ix) => self.directives[ix] = directive,
140            Err(ix) => self.directives.insert(ix, directive),
141        }
142    }
143
144    /// Builder-API version of [`Self::add_directive`].
145    pub fn with_directive<'a>(
146        mut self,
147        target: Option<impl Into<Cow<'a, str>>>,
148        level: impl Into<LevelFilter>,
149    ) -> Self {
150        self.add_directive(target, level);
151        self
152    }
153
154    /// Add a new filter directive at the given level.
155    pub fn add_level(&mut self, level: impl Into<LevelFilter>) {
156        self.add_directive(None::<&str>, level);
157    }
158
159    /// Builder-API version of [`Self::add_level`].
160    pub fn with_level(mut self, level: impl Into<LevelFilter>) -> Self {
161        self.add_level(level);
162        self
163    }
164
165    /// Add a new filter directive for a given target at a given level.
166    pub fn add_target<'a>(
167        &mut self,
168        target: impl Into<Cow<'a, str>>,
169        level: impl Into<LevelFilter>,
170    ) {
171        self.add_directive(Some(target), level);
172    }
173
174    /// Builder-API version of [`Self::add_target`].
175    pub fn with_target<'a>(
176        mut self,
177        target: impl Into<Cow<'a, str>>,
178        level: impl Into<LevelFilter>,
179    ) -> Self {
180        self.add_directive(Some(target), level);
181        self
182    }
183
184    /// Add a regex filter to this filter.
185    ///
186    /// # Panics
187    ///
188    /// Panics if a regex filter has already been set.
189    pub fn add_regex(&mut self, regex: regex::Regex) {
190        match &self.regex {
191            Some(_) => panic!("set `tracing_filter::simple::Filter` regex that was already set"),
192            None => self.regex = Some(regex),
193        }
194    }
195
196    /// Builder-API version of [`Self::add_regex`].
197    pub fn with_regex(mut self, regex: regex::Regex) -> Self {
198        self.add_regex(regex);
199        self
200    }
201
202    fn is_enabled(&self, metadata: &Metadata<'_>) -> bool {
203        // this code is adapted directly from env_logger 0.9.0
204        // env_logger is licensed under MIT OR Apache-2.0
205
206        let level = *metadata.level();
207        let target = metadata.target();
208
209        if self.directives.is_empty() {
210            return level <= LevelFilter::ERROR;
211        }
212
213        for directive in self.directives.iter().rev() {
214            match &directive.target {
215                Some(name) if !target.starts_with(&**name) => {},
216                Some(..) | None => return level <= directive.level,
217            }
218        }
219
220        false
221    }
222}
223
224impl<S> crate::Filter<S> for Filter {
225    fn callsite_enabled(&self, metadata: &Metadata<'_>) -> Interest {
226        if self.is_enabled(metadata) {
227            Interest::always()
228        } else {
229            Interest::never()
230        }
231    }
232
233    fn enabled(&self, metadata: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
234        self.is_enabled(metadata)
235    }
236}