tracing_subscriber/filter/env/
builder.rs

1use super::{
2    directive::{self, Directive},
3    EnvFilter, FromEnvError,
4};
5use crate::sync::RwLock;
6use alloc::{
7    format,
8    string::{String, ToString},
9    vec::Vec,
10};
11use std::{env, eprintln};
12use thread_local::ThreadLocal;
13use tracing::level_filters::STATIC_MAX_LEVEL;
14
15/// A [builder] for constructing new [`EnvFilter`]s.
16///
17/// [builder]: https://rust-unofficial.github.io/patterns/patterns/creational/builder.html
18#[derive(Debug, Clone)]
19#[must_use]
20pub struct Builder {
21    regex: bool,
22    env: Option<String>,
23    default_directive: Option<Directive>,
24}
25
26impl Builder {
27    /// Sets whether span field values can be matched with regular expressions.
28    ///
29    /// If this is `true`, field filter directives will be interpreted as
30    /// regular expressions if they are not able to be interpreted as a `bool`,
31    /// `i64`, `u64`, or `f64` literal. If this is `false,` those field values
32    /// will be interpreted as literal [`std::fmt::Debug`] output instead.
33    ///
34    /// By default, regular expressions are enabled.
35    ///
36    /// **Note**: when [`EnvFilter`]s are constructed from untrusted inputs,
37    /// disabling regular expressions is strongly encouraged.
38    pub fn with_regex(self, regex: bool) -> Self {
39        Self { regex, ..self }
40    }
41
42    /// Sets a default [filtering directive] that will be added to the filter if
43    /// the parsed string or environment variable contains no filter directives.
44    ///
45    /// By default, there is no default directive.
46    ///
47    /// # Examples
48    ///
49    /// If [`parse`], [`parse_lossy`], [`from_env`], or [`from_env_lossy`] are
50    /// called with an empty string or environment variable, the default
51    /// directive is used instead:
52    ///
53    /// ```rust
54    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
55    /// use tracing_subscriber::filter::{EnvFilter, LevelFilter};
56    ///
57    /// let filter = EnvFilter::builder()
58    ///     .with_default_directive(LevelFilter::INFO.into())
59    ///     .parse("")?;
60    ///
61    /// assert_eq!(format!("{}", filter), "info");
62    /// # Ok(()) }
63    /// ```
64    ///
65    /// Note that the `lossy` variants ([`parse_lossy`] and [`from_env_lossy`])
66    /// will ignore any invalid directives. If all directives in a filter
67    /// string or environment variable are invalid, those methods will also use
68    /// the default directive:
69    ///
70    /// ```rust
71    /// use tracing_subscriber::filter::{EnvFilter, LevelFilter};
72    ///
73    /// let filter = EnvFilter::builder()
74    ///     .with_default_directive(LevelFilter::INFO.into())
75    ///     .parse_lossy("some_target=fake level,foo::bar=lolwut");
76    ///
77    /// assert_eq!(format!("{}", filter), "info");
78    /// ```
79    ///
80    ///
81    /// If the string or environment variable contains valid filtering
82    /// directives, the default directive is not used:
83    ///
84    /// ```rust
85    /// use tracing_subscriber::filter::{EnvFilter, LevelFilter};
86    ///
87    /// let filter = EnvFilter::builder()
88    ///     .with_default_directive(LevelFilter::INFO.into())
89    ///     .parse_lossy("foo=trace");
90    ///
91    /// // The default directive is *not* used:
92    /// assert_eq!(format!("{}", filter), "foo=trace");
93    /// ```
94    ///
95    /// Parsing a more complex default directive from a string:
96    ///
97    /// ```rust
98    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
99    /// use tracing_subscriber::filter::{EnvFilter, LevelFilter};
100    ///
101    /// let default = "myapp=debug".parse()
102    ///     .expect("hard-coded default directive should be valid");
103    ///
104    /// let filter = EnvFilter::builder()
105    ///     .with_default_directive(default)
106    ///     .parse("")?;
107    ///
108    /// assert_eq!(format!("{}", filter), "myapp=debug");
109    /// # Ok(()) }
110    /// ```
111    ///
112    /// [`parse_lossy`]: Self::parse_lossy
113    /// [`from_env_lossy`]: Self::from_env_lossy
114    /// [`parse`]: Self::parse
115    /// [`from_env`]: Self::from_env
116    pub fn with_default_directive(self, default_directive: Directive) -> Self {
117        Self {
118            default_directive: Some(default_directive),
119            ..self
120        }
121    }
122
123    /// Sets the name of the environment variable used by the [`from_env`],
124    /// [`from_env_lossy`], and [`try_from_env`] methods.
125    ///
126    /// By default, this is the value of [`EnvFilter::DEFAULT_ENV`]
127    /// (`RUST_LOG`).
128    ///
129    /// [`from_env`]: Self::from_env
130    /// [`from_env_lossy`]: Self::from_env_lossy
131    /// [`try_from_env`]: Self::try_from_env
132    pub fn with_env_var(self, var: impl ToString) -> Self {
133        Self {
134            env: Some(var.to_string()),
135            ..self
136        }
137    }
138
139    /// Returns a new [`EnvFilter`] from the directives in the given string,
140    /// *ignoring* any that are invalid.
141    ///
142    /// If `parse_lossy` is called with an empty string, then the
143    /// [default directive] is used instead.
144    ///
145    /// [default directive]: Self::with_default_directive
146    pub fn parse_lossy<S: AsRef<str>>(&self, dirs: S) -> EnvFilter {
147        let directives = dirs
148            .as_ref()
149            .split(',')
150            .filter(|s| !s.is_empty())
151            .filter_map(|s| match Directive::parse(s, self.regex) {
152                Ok(d) => Some(d),
153                Err(err) => {
154                    eprintln!("ignoring `{}`: {}", s, err);
155                    None
156                }
157            });
158        self.from_directives(directives)
159    }
160
161    /// Returns a new [`EnvFilter`] from the directives in the given string,
162    /// or an error if any are invalid.
163    ///
164    /// If `parse` is called with an empty string, then the [default directive]
165    /// is used instead.
166    ///
167    /// [default directive]: Self::with_default_directive
168    pub fn parse<S: AsRef<str>>(&self, dirs: S) -> Result<EnvFilter, directive::ParseError> {
169        let dirs = dirs.as_ref();
170        if dirs.is_empty() {
171            return Ok(self.from_directives(std::iter::empty()));
172        }
173        let directives = dirs
174            .split(',')
175            .filter(|s| !s.is_empty())
176            .map(|s| Directive::parse(s, self.regex))
177            .collect::<Result<Vec<_>, _>>()?;
178        Ok(self.from_directives(directives))
179    }
180
181    /// Returns a new [`EnvFilter`] from the directives in the configured
182    /// environment variable, ignoring any directives that are invalid.
183    ///
184    /// If the environment variable is empty, then the [default directive]
185    /// is used instead.
186    ///
187    /// [default directive]: Self::with_default_directive
188    pub fn from_env_lossy(&self) -> EnvFilter {
189        let var = env::var(self.env_var_name()).unwrap_or_default();
190        self.parse_lossy(var)
191    }
192
193    /// Returns a new [`EnvFilter`] from the directives in the configured
194    /// environment variable. If the environment variable is unset, no directive is added.
195    ///
196    /// An error is returned if the environment contains invalid directives.
197    ///
198    /// If the environment variable is empty, then the [default directive]
199    /// is used instead.
200    ///
201    /// [default directive]: Self::with_default_directive
202    pub fn from_env(&self) -> Result<EnvFilter, FromEnvError> {
203        let var = env::var(self.env_var_name()).unwrap_or_default();
204        self.parse(var).map_err(Into::into)
205    }
206
207    /// Returns a new [`EnvFilter`] from the directives in the configured
208    /// environment variable, or an error if the environment variable is not set
209    /// or contains invalid directives.
210    ///
211    /// If the environment variable is empty, then the [default directive]
212    /// is used instead.
213    ///
214    /// [default directive]: Self::with_default_directive
215    pub fn try_from_env(&self) -> Result<EnvFilter, FromEnvError> {
216        let var = env::var(self.env_var_name())?;
217        self.parse(var).map_err(Into::into)
218    }
219
220    // TODO(eliza): consider making this a public API?
221    // Clippy doesn't love this naming, because it suggests that `from_` methods
222    // should not take a `Self`...but in this case, it's the `EnvFilter` that is
223    // being constructed "from" the directives, rather than the builder itself.
224    #[allow(clippy::wrong_self_convention)]
225    pub(super) fn from_directives(
226        &self,
227        directives: impl IntoIterator<Item = Directive>,
228    ) -> EnvFilter {
229        use tracing::Level;
230
231        let mut directives: Vec<_> = directives.into_iter().collect();
232        let mut disabled = Vec::new();
233        for directive in &mut directives {
234            if directive.level > STATIC_MAX_LEVEL {
235                disabled.push(directive.clone());
236            }
237            if !self.regex {
238                directive.deregexify();
239            }
240        }
241
242        if !disabled.is_empty() {
243            #[cfg(feature = "nu-ansi-term")]
244            use nu_ansi_term::{Color, Style};
245            // NOTE: We can't use a configured `MakeWriter` because the EnvFilter
246            // has no knowledge of any underlying subscriber or subscriber, which
247            // may or may not use a `MakeWriter`.
248            let warn = |msg: &str| {
249                #[cfg(not(feature = "nu-ansi-term"))]
250                let msg = format!("warning: {}", msg);
251                #[cfg(feature = "nu-ansi-term")]
252                let msg = {
253                    let bold = Style::new().bold();
254                    let mut warning = Color::Yellow.paint("warning");
255                    warning.style_ref_mut().is_bold = true;
256                    format!("{}{} {}", warning, bold.paint(":"), bold.paint(msg))
257                };
258                eprintln!("{}", msg);
259            };
260            let ctx_prefixed = |prefix: &str, msg: &str| {
261                #[cfg(not(feature = "nu-ansi-term"))]
262                let msg = format!("{} {}", prefix, msg);
263                #[cfg(feature = "nu-ansi-term")]
264                let msg = {
265                    let mut equal = Color::Fixed(21).paint("="); // dark blue
266                    equal.style_ref_mut().is_bold = true;
267                    format!(" {} {} {}", equal, Style::new().bold().paint(prefix), msg)
268                };
269                eprintln!("{}", msg);
270            };
271            let ctx_help = |msg| ctx_prefixed("help:", msg);
272            let ctx_note = |msg| ctx_prefixed("note:", msg);
273            let ctx = |msg: &str| {
274                #[cfg(not(feature = "nu-ansi-term"))]
275                let msg = format!("note: {}", msg);
276                #[cfg(feature = "nu-ansi-term")]
277                let msg = {
278                    let mut pipe = Color::Fixed(21).paint("|");
279                    pipe.style_ref_mut().is_bold = true;
280                    format!(" {} {}", pipe, msg)
281                };
282                eprintln!("{}", msg);
283            };
284            warn("some trace filter directives would enable traces that are disabled statically");
285            for directive in disabled {
286                let target = if let Some(target) = &directive.target {
287                    format!("the `{}` target", target)
288                } else {
289                    "all targets".into()
290                };
291                let level = directive
292                    .level
293                    .into_level()
294                    .expect("=off would not have enabled any filters");
295                ctx(&format!(
296                    "`{}` would enable the {} level for {}",
297                    directive, level, target
298                ));
299            }
300            ctx_note(&format!("the static max level is `{}`", STATIC_MAX_LEVEL));
301            let help_msg = || {
302                let (feature, filter) = match STATIC_MAX_LEVEL.into_level() {
303                    Some(Level::TRACE) => unreachable!(
304                        "if the max level is trace, no static filtering features are enabled"
305                    ),
306                    Some(Level::DEBUG) => ("max_level_debug", Level::TRACE),
307                    Some(Level::INFO) => ("max_level_info", Level::DEBUG),
308                    Some(Level::WARN) => ("max_level_warn", Level::INFO),
309                    Some(Level::ERROR) => ("max_level_error", Level::WARN),
310                    None => return ("max_level_off", String::new()),
311                };
312                (feature, format!("{} ", filter))
313            };
314            let (feature, earlier_level) = help_msg();
315            ctx_help(&format!(
316                "to enable {}logging, remove the `{}` feature from the `tracing` crate",
317                earlier_level, feature
318            ));
319        }
320
321        let (dynamics, statics) = Directive::make_tables(directives);
322        let has_dynamics = !dynamics.is_empty();
323
324        let mut filter = EnvFilter {
325            statics,
326            dynamics,
327            has_dynamics,
328            by_id: RwLock::new(Default::default()),
329            by_cs: RwLock::new(Default::default()),
330            scope: ThreadLocal::new(),
331            regex: self.regex,
332        };
333
334        if !has_dynamics && filter.statics.is_empty() {
335            if let Some(ref default) = self.default_directive {
336                filter = filter.add_directive(default.clone());
337            }
338        }
339
340        filter
341    }
342
343    fn env_var_name(&self) -> &str {
344        self.env.as_deref().unwrap_or(EnvFilter::DEFAULT_ENV)
345    }
346}
347
348impl Default for Builder {
349    fn default() -> Self {
350        Self {
351            regex: true,
352            env: None,
353            default_directive: None,
354        }
355    }
356}