]>
Commit | Line | Data |
---|---|---|
f035d41b XL |
1 | //! A `Layer` that enables or disables spans and events based on a set of |
2 | //! filtering directives. | |
3 | ||
4 | // these are publicly re-exported, but the compiler doesn't realize | |
5 | // that for some reason. | |
6 | #[allow(unreachable_pub)] | |
7 | pub use self::{ | |
8 | directive::{Directive, ParseError}, | |
9 | field::BadName as BadFieldName, | |
10 | }; | |
11 | mod directive; | |
12 | mod field; | |
13 | ||
14 | use crate::{ | |
15 | filter::LevelFilter, | |
16 | layer::{Context, Layer}, | |
17 | sync::RwLock, | |
18 | }; | |
19 | use std::{cell::RefCell, collections::HashMap, env, error::Error, fmt, str::FromStr}; | |
20 | use tracing_core::{ | |
21 | callsite, | |
22 | field::Field, | |
23 | span, | |
24 | subscriber::{Interest, Subscriber}, | |
25 | Metadata, | |
26 | }; | |
27 | ||
28 | /// A [`Layer`] which filters spans and events based on a set of filter | |
29 | /// directives. | |
30 | /// | |
31 | /// # Directives | |
32 | /// | |
33 | /// A filter consists of one or more directives which match on [`Span`]s and [`Event`]s. | |
34 | /// Each directive may have a corresponding maximum verbosity [`level`] which | |
35 | /// enables (e.g., _selects for_) spans and events that match. Like `log`, | |
36 | /// `tracing` considers less exclusive levels (like `trace` or `info`) to be more | |
37 | /// verbose than more exclusive levels (like `error` or `warn`). | |
38 | /// | |
39 | /// The directive syntax is similar to that of [`env_logger`]'s. At a high level, the syntax for directives | |
40 | /// consists of several parts: | |
41 | /// | |
42 | /// ```text | |
43 | /// target[span{field=value}]=level | |
44 | /// ``` | |
45 | /// | |
46 | /// Each component (`target`, `span`, `field`, `value`, and `level`) will be covered in turn. | |
47 | /// | |
48 | /// - `target` matches the event or span's target. In general, this is the module path and/or crate name. | |
49 | /// Examples of targets `h2`, `tokio::net`, or `tide::server`. For more information on targets, | |
50 | /// please refer to [`Metadata`]'s documentation. | |
51 | /// - `span` matches on the span's name. If a `span` directive is provided alongside a `target`, | |
52 | /// the `span` directive will match on spans _within_ the `target`. | |
53 | /// - `field` matches on [fields] within spans. Field names can also be supplied without a `value` | |
54 | /// and will match on any [`Span`] or [`Event`] that has a field with that name. | |
55 | /// For example: `[span{field=\"value\"}]=debug`, `[{field}]=trace`. | |
56 | /// - `value` matches on the value of a span's field. If a value is a numeric literal or a bool, | |
57 | /// it will match _only_ on that value. Otherwise, this filter acts as a regex on | |
58 | /// the `std::fmt::Debug` output from the value. | |
59 | /// - `level` sets a maximum verbosity level accepted by this directive. | |
60 | /// | |
61 | /// ## Usage Notes | |
62 | /// | |
63 | /// - The portion of the directive which is included within the square brackets is `tracing`-specific. | |
64 | /// - Any portion of the directive can be omitted. | |
65 | /// - The sole exception are the `field` and `value` directives. If a `value` is provided, | |
66 | /// a `field` must _also_ be provided. However, the converse does not hold, as fields can | |
67 | /// be matched without a value. | |
68 | /// - If only a level is provided, it will set the maximum level for all `Span`s and `Event`s | |
69 | /// that are not enabled by other filters. | |
70 | /// - A directive without a level will enable anything that it matches. This is equivalent to `=trace`. | |
29967ef6 XL |
71 | /// - When a crate has a dash in its name, the default target for events will be the |
72 | /// crate's module path as it appears in Rust. This means every dash will be replaced | |
73 | /// with an underscore. | |
74 | /// - A dash in a target will only appear when being specified explicitly: | |
75 | /// `tracing::info!(target: "target-name", ...);` | |
f035d41b XL |
76 | /// |
77 | /// ## Examples | |
78 | /// | |
79 | /// - `tokio::net=info` will enable all spans or events that: | |
80 | /// - have the `tokio::net` target, | |
81 | /// - at the level `info` or above. | |
82 | /// - `my_crate[span_a]=trace` will enable all spans and events that: | |
83 | /// - are within the `span_a` span or named `span_a` _if_ `span_a` has the target `my_crate`, | |
84 | /// - at the level `trace` or above. | |
85 | /// - `[span_b{name=\"bob\"}]` will enable all spans or event that: | |
86 | /// - have _any_ target, | |
87 | /// - are inside a span named `span_b`, | |
88 | /// - which has a field named `name` with value `bob`, | |
89 | /// - at _any_ level. | |
90 | /// | |
91 | /// [`Layer`]: ../layer/trait.Layer.html | |
92 | /// [`env_logger`]: https://docs.rs/env_logger/0.7.1/env_logger/#enabling-logging | |
29967ef6 XL |
93 | /// [`Span`]: https://docs.rs/tracing-core/latest/tracing_core/span/index.html |
94 | /// [fields]: https://docs.rs/tracing-core/latest/tracing_core/struct.Field.html | |
95 | /// [`Event`]: https://docs.rs/tracing-core/latest/tracing_core/struct.Event.html | |
96 | /// [`level`]: https://docs.rs/tracing-core/latest/tracing_core/struct.Level.html | |
97 | /// [`Metadata`]: https://docs.rs/tracing-core/latest/tracing_core/struct.Metadata.html | |
f035d41b XL |
98 | #[cfg(feature = "env-filter")] |
99 | #[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))] | |
100 | #[derive(Debug)] | |
101 | pub struct EnvFilter { | |
102 | statics: directive::Statics, | |
103 | dynamics: directive::Dynamics, | |
104 | has_dynamics: bool, | |
105 | by_id: RwLock<HashMap<span::Id, directive::SpanMatcher>>, | |
106 | by_cs: RwLock<HashMap<callsite::Identifier, directive::CallsiteMatcher>>, | |
107 | } | |
108 | ||
109 | thread_local! { | |
110 | static SCOPE: RefCell<Vec<LevelFilter>> = RefCell::new(Vec::new()); | |
111 | } | |
112 | ||
113 | type FieldMap<T> = HashMap<Field, T>; | |
114 | ||
115 | #[cfg(feature = "smallvec")] | |
116 | type FilterVec<T> = smallvec::SmallVec<[T; 8]>; | |
117 | #[cfg(not(feature = "smallvec"))] | |
118 | type FilterVec<T> = Vec<T>; | |
119 | ||
120 | /// Indicates that an error occurred while parsing a `EnvFilter` from an | |
121 | /// environment variable. | |
122 | #[derive(Debug)] | |
123 | pub struct FromEnvError { | |
124 | kind: ErrorKind, | |
125 | } | |
126 | ||
127 | #[derive(Debug)] | |
128 | enum ErrorKind { | |
129 | Parse(ParseError), | |
130 | Env(env::VarError), | |
131 | } | |
132 | ||
133 | impl EnvFilter { | |
134 | /// `RUST_LOG` is the default environment variable used by | |
135 | /// [`EnvFilter::from_default_env`] and [`EnvFilter::try_from_default_env`]. | |
136 | /// | |
137 | /// [`EnvFilter::from_default_env`]: #method.from_default_env | |
138 | /// [`EnvFilter::try_from_default_env`]: #method.try_from_default_env | |
139 | pub const DEFAULT_ENV: &'static str = "RUST_LOG"; | |
140 | ||
141 | /// Returns a new `EnvFilter` from the value of the `RUST_LOG` environment | |
142 | /// variable, ignoring any invalid filter directives. | |
143 | pub fn from_default_env() -> Self { | |
144 | Self::from_env(Self::DEFAULT_ENV) | |
145 | } | |
146 | ||
147 | /// Returns a new `EnvFilter` from the value of the given environment | |
148 | /// variable, ignoring any invalid filter directives. | |
149 | pub fn from_env<A: AsRef<str>>(env: A) -> Self { | |
150 | env::var(env.as_ref()).map(Self::new).unwrap_or_default() | |
151 | } | |
152 | ||
153 | /// Returns a new `EnvFilter` from the directives in the given string, | |
154 | /// ignoring any that are invalid. | |
155 | pub fn new<S: AsRef<str>>(dirs: S) -> Self { | |
156 | let directives = dirs.as_ref().split(',').filter_map(|s| match s.parse() { | |
157 | Ok(d) => Some(d), | |
158 | Err(err) => { | |
159 | eprintln!("ignoring `{}`: {}", s, err); | |
160 | None | |
161 | } | |
162 | }); | |
163 | Self::from_directives(directives) | |
164 | } | |
165 | ||
166 | /// Returns a new `EnvFilter` from the directives in the given string, | |
167 | /// or an error if any are invalid. | |
168 | pub fn try_new<S: AsRef<str>>(dirs: S) -> Result<Self, ParseError> { | |
169 | let directives = dirs | |
170 | .as_ref() | |
171 | .split(',') | |
172 | .map(|s| s.parse()) | |
173 | .collect::<Result<Vec<_>, _>>()?; | |
174 | Ok(Self::from_directives(directives)) | |
175 | } | |
176 | ||
177 | /// Returns a new `EnvFilter` from the value of the `RUST_LOG` environment | |
178 | /// variable, or an error if the environment variable contains any invalid | |
179 | /// filter directives. | |
180 | pub fn try_from_default_env() -> Result<Self, FromEnvError> { | |
181 | Self::try_from_env(Self::DEFAULT_ENV) | |
182 | } | |
183 | ||
184 | /// Returns a new `EnvFilter` from the value of the given environment | |
185 | /// variable, or an error if the environment variable is unset or contains | |
186 | /// any invalid filter directives. | |
187 | pub fn try_from_env<A: AsRef<str>>(env: A) -> Result<Self, FromEnvError> { | |
188 | env::var(env.as_ref())?.parse().map_err(Into::into) | |
189 | } | |
190 | ||
191 | /// Add a filtering directive to this `EnvFilter`. | |
192 | /// | |
193 | /// The added directive will be used in addition to any previously set | |
194 | /// directives, either added using this method or provided when the filter | |
195 | /// is constructed. | |
196 | /// | |
29967ef6 | 197 | /// Filters may be created from [`LevelFilter`] or [`Level`], which will |
f035d41b XL |
198 | /// enable all traces at or below a certain verbosity level, or |
199 | /// parsed from a string specifying a directive. | |
200 | /// | |
201 | /// If a filter directive is inserted that matches exactly the same spans | |
202 | /// and events as a previous filter, but sets a different level for those | |
203 | /// spans and events, the previous directive is overwritten. | |
204 | /// | |
29967ef6 XL |
205 | /// [`LevelFilter`]: ../filter/struct.LevelFilter.html |
206 | /// [`Level`]: https://docs.rs/tracing-core/latest/tracing_core/struct.Level.html | |
f035d41b XL |
207 | /// |
208 | /// # Examples | |
29967ef6 XL |
209 | /// |
210 | /// From [`LevelFilter`]: | |
211 | //// | |
f035d41b XL |
212 | /// ```rust |
213 | /// use tracing_subscriber::filter::{EnvFilter, LevelFilter}; | |
214 | /// let mut filter = EnvFilter::from_default_env() | |
215 | /// .add_directive(LevelFilter::INFO.into()); | |
216 | /// ``` | |
29967ef6 XL |
217 | /// |
218 | /// Or from [`Level`]: | |
219 | /// | |
220 | /// ```rust | |
221 | /// # use tracing_subscriber::filter::{EnvFilter, LevelFilter}; | |
222 | /// # use tracing::Level; | |
223 | /// let mut filter = EnvFilter::from_default_env() | |
224 | /// .add_directive(Level::INFO.into()); | |
225 | /// ``` | |
226 | //// | |
227 | /// Parsed from a string: | |
228 | //// | |
f035d41b XL |
229 | /// ```rust |
230 | /// use tracing_subscriber::filter::{EnvFilter, Directive}; | |
231 | /// | |
232 | /// # fn try_mk_filter() -> Result<(), Box<dyn ::std::error::Error>> { | |
233 | /// let mut filter = EnvFilter::try_from_default_env()? | |
234 | /// .add_directive("my_crate::module=trace".parse()?) | |
235 | /// .add_directive("my_crate::my_other_module::something=info".parse()?); | |
236 | /// # Ok(()) | |
237 | /// # } | |
238 | /// ``` | |
239 | pub fn add_directive(mut self, directive: Directive) -> Self { | |
240 | if let Some(stat) = directive.to_static() { | |
241 | self.statics.add(stat) | |
242 | } else { | |
243 | self.has_dynamics = true; | |
244 | self.dynamics.add(directive); | |
245 | } | |
246 | self | |
247 | } | |
248 | ||
249 | fn from_directives(directives: impl IntoIterator<Item = Directive>) -> Self { | |
29967ef6 XL |
250 | use tracing::level_filters::STATIC_MAX_LEVEL; |
251 | use tracing::Level; | |
252 | ||
253 | let directives: Vec<_> = directives.into_iter().collect(); | |
254 | ||
255 | let disabled: Vec<_> = directives | |
256 | .iter() | |
257 | .filter(|directive| directive.level > STATIC_MAX_LEVEL) | |
258 | .collect(); | |
259 | ||
260 | if !disabled.is_empty() { | |
261 | #[cfg(feature = "ansi_term")] | |
262 | use ansi_term::{Color, Style}; | |
263 | // NOTE: We can't use a configured `MakeWriter` because the EnvFilter | |
264 | // has no knowledge of any underlying subscriber or collector, which | |
265 | // may or may not use a `MakeWriter`. | |
266 | let warn = |msg: &str| { | |
267 | #[cfg(not(feature = "ansi_term"))] | |
268 | let msg = format!("warning: {}", msg); | |
269 | #[cfg(feature = "ansi_term")] | |
270 | let msg = { | |
271 | let bold = Style::new().bold(); | |
272 | let mut warning = Color::Yellow.paint("warning"); | |
273 | warning.style_ref_mut().is_bold = true; | |
136023e0 | 274 | format!("{}{} {}", warning, bold.paint(":"), bold.paint(msg)) |
29967ef6 XL |
275 | }; |
276 | eprintln!("{}", msg); | |
277 | }; | |
278 | let ctx_prefixed = |prefix: &str, msg: &str| { | |
279 | #[cfg(not(feature = "ansi_term"))] | |
280 | let msg = format!("note: {}", msg); | |
281 | #[cfg(feature = "ansi_term")] | |
282 | let msg = { | |
283 | let mut equal = Color::Fixed(21).paint("="); // dark blue | |
284 | equal.style_ref_mut().is_bold = true; | |
285 | format!(" {} {} {}", equal, Style::new().bold().paint(prefix), msg) | |
286 | }; | |
287 | eprintln!("{}", msg); | |
288 | }; | |
289 | let ctx_help = |msg| ctx_prefixed("help:", msg); | |
290 | let ctx_note = |msg| ctx_prefixed("note:", msg); | |
291 | let ctx = |msg: &str| { | |
292 | #[cfg(not(feature = "ansi_term"))] | |
293 | let msg = format!("note: {}", msg); | |
294 | #[cfg(feature = "ansi_term")] | |
295 | let msg = { | |
296 | let mut pipe = Color::Fixed(21).paint("|"); | |
297 | pipe.style_ref_mut().is_bold = true; | |
298 | format!(" {} {}", pipe, msg) | |
299 | }; | |
300 | eprintln!("{}", msg); | |
301 | }; | |
302 | warn("some trace filter directives would enable traces that are disabled statically"); | |
303 | for directive in disabled { | |
304 | let target = if let Some(target) = &directive.target { | |
305 | format!("the `{}` target", target) | |
306 | } else { | |
307 | "all targets".into() | |
308 | }; | |
309 | let level = directive | |
310 | .level | |
29967ef6 XL |
311 | .into_level() |
312 | .expect("=off would not have enabled any filters"); | |
313 | ctx(&format!( | |
314 | "`{}` would enable the {} level for {}", | |
315 | directive, level, target | |
316 | )); | |
317 | } | |
318 | ctx_note(&format!("the static max level is `{}`", STATIC_MAX_LEVEL)); | |
319 | let help_msg = || { | |
320 | let (feature, filter) = match STATIC_MAX_LEVEL.into_level() { | |
321 | Some(Level::TRACE) => unreachable!( | |
322 | "if the max level is trace, no static filtering features are enabled" | |
323 | ), | |
324 | Some(Level::DEBUG) => ("max_level_debug", Level::TRACE), | |
325 | Some(Level::INFO) => ("max_level_info", Level::DEBUG), | |
326 | Some(Level::WARN) => ("max_level_warn", Level::INFO), | |
327 | Some(Level::ERROR) => ("max_level_error", Level::WARN), | |
328 | None => return ("max_level_off", String::new()), | |
329 | }; | |
330 | (feature, format!("{} ", filter)) | |
331 | }; | |
332 | let (feature, earlier_level) = help_msg(); | |
333 | ctx_help(&format!( | |
334 | "to enable {}logging, remove the `{}` feature", | |
335 | earlier_level, feature | |
336 | )); | |
337 | } | |
338 | ||
f035d41b XL |
339 | let (dynamics, mut statics) = Directive::make_tables(directives); |
340 | let has_dynamics = !dynamics.is_empty(); | |
341 | ||
342 | if statics.is_empty() && !has_dynamics { | |
343 | statics.add(directive::StaticDirective::default()); | |
344 | } | |
345 | ||
346 | Self { | |
347 | statics, | |
348 | dynamics, | |
349 | has_dynamics, | |
350 | by_id: RwLock::new(HashMap::new()), | |
351 | by_cs: RwLock::new(HashMap::new()), | |
352 | } | |
353 | } | |
354 | ||
355 | fn cares_about_span(&self, span: &span::Id) -> bool { | |
356 | let spans = try_lock!(self.by_id.read(), else return false); | |
357 | spans.contains_key(span) | |
358 | } | |
359 | ||
360 | fn base_interest(&self) -> Interest { | |
361 | if self.has_dynamics { | |
362 | Interest::sometimes() | |
363 | } else { | |
364 | Interest::never() | |
365 | } | |
366 | } | |
367 | } | |
368 | ||
369 | impl<S: Subscriber> Layer<S> for EnvFilter { | |
370 | fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { | |
371 | if self.has_dynamics && metadata.is_span() { | |
372 | // If this metadata describes a span, first, check if there is a | |
373 | // dynamic filter that should be constructed for it. If so, it | |
374 | // should always be enabled, since it influences filtering. | |
375 | if let Some(matcher) = self.dynamics.matcher(metadata) { | |
376 | let mut by_cs = try_lock!(self.by_cs.write(), else return self.base_interest()); | |
377 | by_cs.insert(metadata.callsite(), matcher); | |
378 | return Interest::always(); | |
379 | } | |
380 | } | |
381 | ||
382 | // Otherwise, check if any of our static filters enable this metadata. | |
383 | if self.statics.enabled(metadata) { | |
384 | Interest::always() | |
385 | } else { | |
386 | self.base_interest() | |
387 | } | |
388 | } | |
389 | ||
3dfed10e XL |
390 | fn max_level_hint(&self) -> Option<LevelFilter> { |
391 | if self.dynamics.has_value_filters() { | |
392 | // If we perform any filtering on span field *values*, we will | |
393 | // enable *all* spans, because their field values are not known | |
394 | // until recording. | |
395 | return Some(LevelFilter::TRACE); | |
396 | } | |
397 | std::cmp::max( | |
136023e0 XL |
398 | self.statics.max_level.into(), |
399 | self.dynamics.max_level.into(), | |
3dfed10e XL |
400 | ) |
401 | } | |
402 | ||
f035d41b XL |
403 | fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, S>) -> bool { |
404 | let level = metadata.level(); | |
405 | ||
406 | // is it possible for a dynamic filter directive to enable this event? | |
407 | // if not, we can avoid the thread local access + iterating over the | |
408 | // spans in the current scope. | |
409 | if self.has_dynamics && self.dynamics.max_level >= *level { | |
29967ef6 XL |
410 | if metadata.is_span() { |
411 | // If the metadata is a span, see if we care about its callsite. | |
412 | let enabled_by_cs = self | |
413 | .by_cs | |
414 | .read() | |
415 | .ok() | |
416 | .map(|by_cs| by_cs.contains_key(&metadata.callsite())) | |
417 | .unwrap_or(false); | |
418 | if enabled_by_cs { | |
419 | return true; | |
420 | } | |
421 | } | |
422 | ||
f035d41b XL |
423 | let enabled_by_scope = SCOPE.with(|scope| { |
424 | for filter in scope.borrow().iter() { | |
425 | if filter >= level { | |
426 | return true; | |
427 | } | |
428 | } | |
429 | false | |
430 | }); | |
431 | if enabled_by_scope { | |
432 | return true; | |
433 | } | |
434 | } | |
435 | ||
436 | // is it possible for a static filter directive to enable this event? | |
437 | if self.statics.max_level >= *level { | |
438 | // Otherwise, fall back to checking if the callsite is | |
439 | // statically enabled. | |
f035d41b XL |
440 | return self.statics.enabled(metadata); |
441 | } | |
442 | ||
443 | false | |
444 | } | |
445 | ||
446 | fn new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, _: Context<'_, S>) { | |
447 | let by_cs = try_lock!(self.by_cs.read()); | |
448 | if let Some(cs) = by_cs.get(&attrs.metadata().callsite()) { | |
449 | let span = cs.to_span_match(attrs); | |
450 | try_lock!(self.by_id.write()).insert(id.clone(), span); | |
451 | } | |
452 | } | |
453 | ||
454 | fn on_record(&self, id: &span::Id, values: &span::Record<'_>, _: Context<'_, S>) { | |
455 | if let Some(span) = try_lock!(self.by_id.read()).get(id) { | |
456 | span.record_update(values); | |
457 | } | |
458 | } | |
459 | ||
460 | fn on_enter(&self, id: &span::Id, _: Context<'_, S>) { | |
461 | // XXX: This is where _we_ could push IDs to the stack instead, and use | |
462 | // that to allow changing the filter while a span is already entered. | |
463 | // But that might be much less efficient... | |
464 | if let Some(span) = try_lock!(self.by_id.read()).get(id) { | |
465 | SCOPE.with(|scope| scope.borrow_mut().push(span.level())); | |
466 | } | |
467 | } | |
468 | ||
469 | fn on_exit(&self, id: &span::Id, _: Context<'_, S>) { | |
470 | if self.cares_about_span(id) { | |
471 | SCOPE.with(|scope| scope.borrow_mut().pop()); | |
472 | } | |
473 | } | |
474 | ||
475 | fn on_close(&self, id: span::Id, _: Context<'_, S>) { | |
476 | // If we don't need to acquire a write lock, avoid doing so. | |
477 | if !self.cares_about_span(&id) { | |
478 | return; | |
479 | } | |
480 | ||
481 | let mut spans = try_lock!(self.by_id.write()); | |
482 | spans.remove(&id); | |
483 | } | |
484 | } | |
485 | ||
486 | impl FromStr for EnvFilter { | |
487 | type Err = ParseError; | |
488 | ||
489 | fn from_str(spec: &str) -> Result<Self, Self::Err> { | |
490 | Self::try_new(spec) | |
491 | } | |
492 | } | |
493 | ||
494 | impl<S> From<S> for EnvFilter | |
495 | where | |
496 | S: AsRef<str>, | |
497 | { | |
498 | fn from(s: S) -> Self { | |
499 | Self::new(s) | |
500 | } | |
501 | } | |
502 | ||
503 | impl Default for EnvFilter { | |
504 | fn default() -> Self { | |
505 | Self::from_directives(std::iter::empty()) | |
506 | } | |
507 | } | |
508 | ||
509 | impl fmt::Display for EnvFilter { | |
510 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
511 | let mut statics = self.statics.iter(); | |
512 | let wrote_statics = if let Some(next) = statics.next() { | |
513 | fmt::Display::fmt(next, f)?; | |
514 | for directive in statics { | |
515 | write!(f, ",{}", directive)?; | |
516 | } | |
517 | true | |
518 | } else { | |
519 | false | |
520 | }; | |
521 | ||
522 | let mut dynamics = self.dynamics.iter(); | |
523 | if let Some(next) = dynamics.next() { | |
524 | if wrote_statics { | |
525 | f.write_str(",")?; | |
526 | } | |
527 | fmt::Display::fmt(next, f)?; | |
528 | for directive in dynamics { | |
529 | write!(f, ",{}", directive)?; | |
530 | } | |
531 | } | |
532 | Ok(()) | |
533 | } | |
534 | } | |
535 | ||
536 | // ===== impl FromEnvError ===== | |
537 | ||
538 | impl From<ParseError> for FromEnvError { | |
539 | fn from(p: ParseError) -> Self { | |
540 | Self { | |
541 | kind: ErrorKind::Parse(p), | |
542 | } | |
543 | } | |
544 | } | |
545 | ||
546 | impl From<env::VarError> for FromEnvError { | |
547 | fn from(v: env::VarError) -> Self { | |
548 | Self { | |
549 | kind: ErrorKind::Env(v), | |
550 | } | |
551 | } | |
552 | } | |
553 | ||
554 | impl fmt::Display for FromEnvError { | |
555 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
556 | match self.kind { | |
557 | ErrorKind::Parse(ref p) => p.fmt(f), | |
558 | ErrorKind::Env(ref e) => e.fmt(f), | |
559 | } | |
560 | } | |
561 | } | |
562 | ||
563 | impl Error for FromEnvError { | |
564 | fn source(&self) -> Option<&(dyn Error + 'static)> { | |
565 | match self.kind { | |
566 | ErrorKind::Parse(ref p) => Some(p), | |
567 | ErrorKind::Env(ref e) => Some(e), | |
568 | } | |
569 | } | |
570 | } | |
571 | ||
572 | #[cfg(test)] | |
573 | mod tests { | |
574 | use super::*; | |
575 | use tracing_core::field::FieldSet; | |
576 | use tracing_core::*; | |
577 | ||
578 | struct NoSubscriber; | |
579 | impl Subscriber for NoSubscriber { | |
580 | #[inline] | |
581 | fn register_callsite(&self, _: &'static Metadata<'static>) -> subscriber::Interest { | |
582 | subscriber::Interest::always() | |
583 | } | |
584 | fn new_span(&self, _: &span::Attributes<'_>) -> span::Id { | |
585 | span::Id::from_u64(0xDEAD) | |
586 | } | |
587 | fn event(&self, _event: &Event<'_>) {} | |
588 | fn record(&self, _span: &span::Id, _values: &span::Record<'_>) {} | |
589 | fn record_follows_from(&self, _span: &span::Id, _follows: &span::Id) {} | |
590 | ||
591 | #[inline] | |
592 | fn enabled(&self, _metadata: &Metadata<'_>) -> bool { | |
593 | true | |
594 | } | |
595 | fn enter(&self, _span: &span::Id) {} | |
596 | fn exit(&self, _span: &span::Id) {} | |
597 | } | |
598 | ||
599 | struct Cs; | |
600 | impl Callsite for Cs { | |
601 | fn set_interest(&self, _interest: Interest) {} | |
602 | fn metadata(&self) -> &Metadata<'_> { | |
603 | unimplemented!() | |
604 | } | |
605 | } | |
606 | ||
607 | #[test] | |
608 | fn callsite_enabled_no_span_directive() { | |
609 | let filter = EnvFilter::new("app=debug").with_subscriber(NoSubscriber); | |
610 | static META: &Metadata<'static> = &Metadata::new( | |
611 | "mySpan", | |
612 | "app", | |
613 | Level::TRACE, | |
614 | None, | |
615 | None, | |
616 | None, | |
617 | FieldSet::new(&[], identify_callsite!(&Cs)), | |
618 | Kind::SPAN, | |
619 | ); | |
620 | ||
621 | let interest = filter.register_callsite(META); | |
622 | assert!(interest.is_never()); | |
623 | } | |
624 | ||
625 | #[test] | |
626 | fn callsite_off() { | |
627 | let filter = EnvFilter::new("app=off").with_subscriber(NoSubscriber); | |
628 | static META: &Metadata<'static> = &Metadata::new( | |
629 | "mySpan", | |
630 | "app", | |
631 | Level::ERROR, | |
632 | None, | |
633 | None, | |
634 | None, | |
635 | FieldSet::new(&[], identify_callsite!(&Cs)), | |
636 | Kind::SPAN, | |
637 | ); | |
638 | ||
639 | let interest = filter.register_callsite(&META); | |
640 | assert!(interest.is_never()); | |
641 | } | |
642 | ||
643 | #[test] | |
644 | fn callsite_enabled_includes_span_directive() { | |
645 | let filter = EnvFilter::new("app[mySpan]=debug").with_subscriber(NoSubscriber); | |
646 | static META: &Metadata<'static> = &Metadata::new( | |
647 | "mySpan", | |
648 | "app", | |
649 | Level::TRACE, | |
650 | None, | |
651 | None, | |
652 | None, | |
653 | FieldSet::new(&[], identify_callsite!(&Cs)), | |
654 | Kind::SPAN, | |
655 | ); | |
656 | ||
657 | let interest = filter.register_callsite(&META); | |
658 | assert!(interest.is_always()); | |
659 | } | |
660 | ||
661 | #[test] | |
662 | fn callsite_enabled_includes_span_directive_field() { | |
663 | let filter = | |
664 | EnvFilter::new("app[mySpan{field=\"value\"}]=debug").with_subscriber(NoSubscriber); | |
665 | static META: &Metadata<'static> = &Metadata::new( | |
666 | "mySpan", | |
667 | "app", | |
668 | Level::TRACE, | |
669 | None, | |
670 | None, | |
671 | None, | |
672 | FieldSet::new(&["field"], identify_callsite!(&Cs)), | |
673 | Kind::SPAN, | |
674 | ); | |
675 | ||
676 | let interest = filter.register_callsite(&META); | |
677 | assert!(interest.is_always()); | |
678 | } | |
679 | ||
680 | #[test] | |
681 | fn callsite_enabled_includes_span_directive_multiple_fields() { | |
682 | let filter = EnvFilter::new("app[mySpan{field=\"value\",field2=2}]=debug") | |
683 | .with_subscriber(NoSubscriber); | |
684 | static META: &Metadata<'static> = &Metadata::new( | |
685 | "mySpan", | |
686 | "app", | |
687 | Level::TRACE, | |
688 | None, | |
689 | None, | |
690 | None, | |
691 | FieldSet::new(&["field"], identify_callsite!(&Cs)), | |
692 | Kind::SPAN, | |
693 | ); | |
694 | ||
695 | let interest = filter.register_callsite(&META); | |
696 | assert!(interest.is_never()); | |
697 | } | |
698 | ||
699 | #[test] | |
700 | fn roundtrip() { | |
701 | let f1: EnvFilter = | |
702 | "[span1{foo=1}]=error,[span2{bar=2 baz=false}],crate2[{quux=\"quuux\"}]=debug" | |
703 | .parse() | |
704 | .unwrap(); | |
705 | let f2: EnvFilter = format!("{}", f1).parse().unwrap(); | |
706 | assert_eq!(f1.statics, f2.statics); | |
707 | assert_eq!(f1.dynamics, f2.dynamics); | |
708 | } | |
709 | } |