1 use super::{ 2 directive::{self, Directive}, 3 EnvFilter, FromEnvError, 4 }; 5 use crate::sync::RwLock; 6 use std::env; 7 use thread_local::ThreadLocal; 8 use 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] 15 pub struct Builder { 16 regex: bool, 17 env: Option<String>, 18 default_directive: Option<Directive>, 19 } 20 21 impl 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. with_regex(self, regex: bool) -> Self33 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 with_default_directive(self, default_directive: Directive) -> Self111 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 with_env_var(self, var: impl ToString) -> Self127 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. parse_lossy<S: AsRef<str>>(&self, dirs: S) -> EnvFilter136 pub fn parse_lossy<S: AsRef<str>>(&self, dirs: S) -> EnvFilter { 137 let directives = dirs 138 .as_ref() 139 .split(',') 140 .filter(|s| !s.is_empty()) 141 .filter_map(|s| match Directive::parse(s, self.regex) { 142 Ok(d) => Some(d), 143 Err(err) => { 144 eprintln!("ignoring `{}`: {}", s, err); 145 None 146 } 147 }); 148 self.from_directives(directives) 149 } 150 151 /// Returns a new [`EnvFilter`] from the directives in the given string, 152 /// or an error if any are invalid. parse<S: AsRef<str>>(&self, dirs: S) -> Result<EnvFilter, directive::ParseError>153 pub fn parse<S: AsRef<str>>(&self, dirs: S) -> Result<EnvFilter, directive::ParseError> { 154 let dirs = dirs.as_ref(); 155 if dirs.is_empty() { 156 return Ok(self.from_directives(std::iter::empty())); 157 } 158 let directives = dirs 159 .split(',') 160 .filter(|s| !s.is_empty()) 161 .map(|s| Directive::parse(s, self.regex)) 162 .collect::<Result<Vec<_>, _>>()?; 163 Ok(self.from_directives(directives)) 164 } 165 166 /// Returns a new [`EnvFilter`] from the directives in the configured 167 /// environment variable, ignoring any directives that are invalid. from_env_lossy(&self) -> EnvFilter168 pub fn from_env_lossy(&self) -> EnvFilter { 169 let var = env::var(self.env_var_name()).unwrap_or_default(); 170 self.parse_lossy(var) 171 } 172 173 /// Returns a new [`EnvFilter`] from the directives in the configured 174 /// environment variable. If the environment variable is unset, no directive is added. 175 /// 176 /// An error is returned if the environment contains invalid directives. from_env(&self) -> Result<EnvFilter, FromEnvError>177 pub fn from_env(&self) -> Result<EnvFilter, FromEnvError> { 178 let var = env::var(self.env_var_name()).unwrap_or_default(); 179 self.parse(var).map_err(Into::into) 180 } 181 182 /// Returns a new [`EnvFilter`] from the directives in the configured 183 /// environment variable, or an error if the environment variable is not set 184 /// or contains invalid directives. try_from_env(&self) -> Result<EnvFilter, FromEnvError>185 pub fn try_from_env(&self) -> Result<EnvFilter, FromEnvError> { 186 let var = env::var(self.env_var_name())?; 187 self.parse(var).map_err(Into::into) 188 } 189 190 // TODO(eliza): consider making this a public API? 191 // Clippy doesn't love this naming, because it suggests that `from_` methods 192 // should not take a `Self`...but in this case, it's the `EnvFilter` that is 193 // being constructed "from" the directives, rather than the builder itself. 194 #[allow(clippy::wrong_self_convention)] from_directives( &self, directives: impl IntoIterator<Item = Directive>, ) -> EnvFilter195 pub(super) fn from_directives( 196 &self, 197 directives: impl IntoIterator<Item = Directive>, 198 ) -> EnvFilter { 199 use tracing::Level; 200 201 let mut directives: Vec<_> = directives.into_iter().collect(); 202 let mut disabled = Vec::new(); 203 for directive in &mut directives { 204 if directive.level > STATIC_MAX_LEVEL { 205 disabled.push(directive.clone()); 206 } 207 if !self.regex { 208 directive.deregexify(); 209 } 210 } 211 212 if !disabled.is_empty() { 213 #[cfg(feature = "nu_ansi_term")] 214 use nu_ansi_term::{Color, Style}; 215 // NOTE: We can't use a configured `MakeWriter` because the EnvFilter 216 // has no knowledge of any underlying subscriber or subscriber, which 217 // may or may not use a `MakeWriter`. 218 let warn = |msg: &str| { 219 #[cfg(not(feature = "nu_ansi_term"))] 220 let msg = format!("warning: {}", msg); 221 #[cfg(feature = "nu_ansi_term")] 222 let msg = { 223 let bold = Style::new().bold(); 224 let mut warning = Color::Yellow.paint("warning"); 225 warning.style_ref_mut().is_bold = true; 226 format!("{}{} {}", warning, bold.paint(":"), bold.paint(msg)) 227 }; 228 eprintln!("{}", msg); 229 }; 230 let ctx_prefixed = |prefix: &str, msg: &str| { 231 #[cfg(not(feature = "nu_ansi_term"))] 232 let msg = format!("{} {}", prefix, msg); 233 #[cfg(feature = "nu_ansi_term")] 234 let msg = { 235 let mut equal = Color::Fixed(21).paint("="); // dark blue 236 equal.style_ref_mut().is_bold = true; 237 format!(" {} {} {}", equal, Style::new().bold().paint(prefix), msg) 238 }; 239 eprintln!("{}", msg); 240 }; 241 let ctx_help = |msg| ctx_prefixed("help:", msg); 242 let ctx_note = |msg| ctx_prefixed("note:", msg); 243 let ctx = |msg: &str| { 244 #[cfg(not(feature = "nu_ansi_term"))] 245 let msg = format!("note: {}", msg); 246 #[cfg(feature = "nu_ansi_term")] 247 let msg = { 248 let mut pipe = Color::Fixed(21).paint("|"); 249 pipe.style_ref_mut().is_bold = true; 250 format!(" {} {}", pipe, msg) 251 }; 252 eprintln!("{}", msg); 253 }; 254 warn("some trace filter directives would enable traces that are disabled statically"); 255 for directive in disabled { 256 let target = if let Some(target) = &directive.target { 257 format!("the `{}` target", target) 258 } else { 259 "all targets".into() 260 }; 261 let level = directive 262 .level 263 .into_level() 264 .expect("=off would not have enabled any filters"); 265 ctx(&format!( 266 "`{}` would enable the {} level for {}", 267 directive, level, target 268 )); 269 } 270 ctx_note(&format!("the static max level is `{}`", STATIC_MAX_LEVEL)); 271 let help_msg = || { 272 let (feature, filter) = match STATIC_MAX_LEVEL.into_level() { 273 Some(Level::TRACE) => unreachable!( 274 "if the max level is trace, no static filtering features are enabled" 275 ), 276 Some(Level::DEBUG) => ("max_level_debug", Level::TRACE), 277 Some(Level::INFO) => ("max_level_info", Level::DEBUG), 278 Some(Level::WARN) => ("max_level_warn", Level::INFO), 279 Some(Level::ERROR) => ("max_level_error", Level::WARN), 280 None => return ("max_level_off", String::new()), 281 }; 282 (feature, format!("{} ", filter)) 283 }; 284 let (feature, earlier_level) = help_msg(); 285 ctx_help(&format!( 286 "to enable {}logging, remove the `{}` feature from the `tracing` crate", 287 earlier_level, feature 288 )); 289 } 290 291 let (dynamics, statics) = Directive::make_tables(directives); 292 let has_dynamics = !dynamics.is_empty(); 293 294 let mut filter = EnvFilter { 295 statics, 296 dynamics, 297 has_dynamics, 298 by_id: RwLock::new(Default::default()), 299 by_cs: RwLock::new(Default::default()), 300 scope: ThreadLocal::new(), 301 regex: self.regex, 302 }; 303 304 if !has_dynamics && filter.statics.is_empty() { 305 if let Some(ref default) = self.default_directive { 306 filter = filter.add_directive(default.clone()); 307 } 308 } 309 310 filter 311 } 312 env_var_name(&self) -> &str313 fn env_var_name(&self) -> &str { 314 self.env.as_deref().unwrap_or(EnvFilter::DEFAULT_ENV) 315 } 316 } 317 318 impl Default for Builder { default() -> Self319 fn default() -> Self { 320 Self { 321 regex: true, 322 env: None, 323 default_directive: None, 324 } 325 } 326 } 327