🛈 Note: This is pre-release documentation for the upcoming tracing 0.2.0 ecosystem.

For the release documentation, please see docs.rs, instead.

tracing_subscriber/filter/env/
builder.rs

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