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}