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}