tracing_filter/legacy/
mod.rs

1//! Support for legacy filters, which match tracing-subscriber's legacy
2//! `EnvFilter` format.
3//!
4//! [`EnvFilter`]: https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/struct.EnvFilter.html
5
6use {
7    self::matcher::SpanMatcher,
8    crate::{Diagnostics, DEFAULT_ENV},
9    std::{cell::RefCell, collections::HashMap, env, ffi::OsStr, fmt, sync::RwLock},
10    thread_local::ThreadLocal,
11    tracing_core::{callsite, span, Interest, LevelFilter, Metadata, Subscriber},
12    tracing_subscriber::{
13        layer::Context,
14        registry::{LookupSpan, SpanRef},
15    },
16};
17
18mod directive;
19mod matcher;
20mod parse;
21#[cfg(test)]
22mod tests;
23
24/// A filter matching tracing-subscriber's legacy [`EnvFilter`] format.
25///
26/// [`EnvFilter`]: https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/struct.EnvFilter.html
27///
28/// # Example
29///
30/// Use the `RUST_LOG` filter if it is set, but provide a fallback filter if the
31/// environment variable is not set.
32///
33/// ```rust
34/// # use tracing_filter::{DiagnosticsTheme, legacy::Filter};
35/// # use {tracing::{error, warn}, tracing_subscriber::prelude::*};
36/// let (filter, diagnostics) = Filter::from_default_env()
37///     .unwrap_or_else(|| Filter::parse("noisy_crate=warn,info"));
38///
39/// tracing_subscriber::registry()
40///     .with(filter.layer())
41///     .with(tracing_subscriber::fmt::layer())
42///     .init();
43///
44/// if let Some(diagnostics) = diagnostics {
45///     if let Some(error) = diagnostics.error(DiagnosticsTheme::default()) {
46///         error!("{error}");
47///     }
48///     if let Some(warn) = diagnostics.warn(DiagnosticsTheme::default()) {
49///         warn!("{warn}");
50///     }
51/// }
52/// ```
53#[derive(Debug)]
54pub struct Filter {
55    scope: ThreadLocal<RefCell<Vec<LevelFilter>>>,
56    statics: directive::Statics,
57    dynamics: directive::Dynamics,
58    by_cs: RwLock<HashMap<callsite::Identifier, matcher::CallsiteMatcher>>,
59}
60
61impl Filter {
62    /// Create a new filter, ignoring any invalid directives. It is highly
63    /// recommended you use [`Self::parse`] instead, and display the warnings for
64    /// ignored directives.
65    pub fn new(spec: &str) -> Self {
66        Self::parse(spec).0
67    }
68
69    /// Create an empty filter (i.e. one that filters nothing out).
70    pub fn empty() -> Self {
71        Self::parse("").0
72    }
73
74    /// Create a filter from the default `RUST_LOG` environment.
75    pub fn from_default_env() -> Option<(Self, Option<Diagnostics<'static>>)> {
76        Self::from_env(DEFAULT_ENV)
77    }
78
79    /// Create a filter from the environment.
80    ///
81    /// Returns `None` if the environment variable is not set.
82    pub fn from_env(key: impl AsRef<OsStr>) -> Option<(Self, Option<Diagnostics<'static>>)> {
83        let s = env::var(key).ok()?;
84        let (filter, err) = Self::parse(&s);
85        Some((
86            filter,
87            match err {
88                None => None,
89                Some(x) => Some({
90                    Diagnostics {
91                        error: x.error,
92                        ignored: x.ignored,
93                        disabled: x.disabled,
94                        source: s.into(),
95                    }
96                }),
97            },
98        ))
99    }
100
101    /// Lift this filter to a filter layer.
102    pub fn layer(self) -> crate::FilterLayer<Self> {
103        crate::FilterLayer::new(self)
104    }
105}
106
107impl Filter {
108    fn has_dynamics(&self) -> bool {
109        !self.dynamics.directives.is_empty()
110    }
111
112    fn cares_about_span<R: for<'a> LookupSpan<'a>>(&self, span: SpanRef<'_, R>) -> bool {
113        let ext = span.extensions();
114        ext.get::<SpanMatcher>().is_some()
115    }
116
117    fn base_interest(&self) -> Interest {
118        if self.has_dynamics() {
119            Interest::sometimes()
120        } else {
121            Interest::never()
122        }
123    }
124}
125
126impl<S: Subscriber + for<'a> LookupSpan<'a>> crate::Filter<S> for Filter {
127    fn enabled(&self, metadata: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
128        let level = metadata.level();
129
130        // Is it possible for a dynamic filter directive to enable this event?
131        // If not, we can avoid the thread local access & iterating over the
132        // spans in the current scope.
133
134        if self.has_dynamics() && self.dynamics.level >= *level {
135            if metadata.is_span() {
136                // If the metadata is a span, see if we care about its callsite.
137                let enabled = self
138                    .by_cs
139                    .read()
140                    .map(|cs| cs.contains_key(&metadata.callsite()))
141                    .unwrap_or_default();
142                if enabled {
143                    return true;
144                }
145            }
146
147            let enabled = self
148                .scope
149                .get_or_default()
150                .borrow()
151                .iter()
152                .any(|filter| filter >= level);
153            if enabled {
154                return true;
155            }
156        }
157
158        // Otherwise, fall back to checking if the callsite is statically enabled.
159        self.statics.enabled(metadata)
160    }
161
162    fn callsite_enabled(&self, metadata: &Metadata<'_>) -> tracing_core::Interest {
163        if self.has_dynamics() && metadata.is_span() {
164            // If this metadata describes a span, first, check if there is a
165            // dynamic filter that should be constructed for it. If so, it
166            // should always be enabled, since it influences filtering.
167            if let Some(matcher) = self.dynamics.matcher(metadata) {
168                let mut by_cs = try_lock!(self.by_cs.write(), else return self.base_interest());
169                by_cs.insert(metadata.callsite(), matcher);
170                return Interest::always();
171            }
172        }
173
174        if self.statics.enabled(metadata) {
175            Interest::always()
176        } else {
177            self.base_interest()
178        }
179    }
180
181    fn max_level_hint(&self) -> Option<LevelFilter> {
182        if self.dynamics.has_value_filters() {
183            // If we perform any filtering on span field *values*, we will
184            // enable *all* spans, because their field values are not known
185            // until recording.
186            return Some(LevelFilter::TRACE);
187        }
188        std::cmp::max(self.statics.level.into(), self.dynamics.level.into())
189    }
190
191    fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
192        let by_cs = try_lock!(self.by_cs.read());
193        if let Some(cs) = by_cs.get(&attrs.metadata().callsite()) {
194            let span = ctx.span(id).expect("span should be registered");
195            let matcher = cs.to_span_matcher(attrs);
196            span.extensions_mut().insert(matcher);
197        }
198    }
199
200    fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
201        let span = ctx.span(id).expect("span should be registered");
202        let ext = span.extensions();
203        if let Some(matcher) = ext.get::<SpanMatcher>() {
204            matcher.record_update(values);
205        }
206    }
207
208    fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
209        // We _could_ push IDs to the stack instead, and use that to allow
210        // changing the filter while a span is already entered. But that seems
211        // much less efficient...
212        let span = ctx.span(id).expect("span should be registered");
213        let ext = span.extensions();
214        if let Some(matcher) = ext.get::<SpanMatcher>() {
215            self.scope
216                .get_or_default()
217                .borrow_mut()
218                .push(matcher.level());
219        }
220    }
221
222    fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
223        let span = ctx.span(id).expect("span should be registered");
224        if self.cares_about_span(span) {
225            self.scope.get_or_default().borrow_mut().pop();
226        }
227    }
228
229    fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
230        let span = ctx.span(&id).expect("span should be registered");
231        span.extensions_mut().remove::<SpanMatcher>();
232    }
233}
234
235impl fmt::Display for Filter {
236    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237        let mut wrote_any = false;
238
239        for directive in self.statics.directives.iter() {
240            if wrote_any {
241                write!(f, ",")?;
242            }
243            write!(f, "{}", directive)?;
244            wrote_any = true;
245        }
246
247        for directive in self.dynamics.directives.iter() {
248            if wrote_any {
249                write!(f, ",")?;
250            }
251            write!(f, "{}", directive)?;
252            wrote_any = true;
253        }
254
255        Ok(())
256    }
257}