Skip to content

Commit 9b7bab1

Browse files
subscriber: impl Filter for EnvFilter (#1983)
## Motivation Filtering by span and field requires using `EnvFilter` rather than `Targets`. Per-subscriber filtering requires the `Filter` trait, which `EnvFilter` does not implement. ## Solution Implement the `Filter` trait for `EnvFilter`. PR #1973 adds additiional methods to the `Filter` trait, which are necessary for `EnvFilter` to implement dynamic span filtering. Now that those methods are added, we can provide a `Filter` impl for `EnvFilter`. In addition, we changed the globally-scoped `thread_local!` macro to use a `ThreadLocal` struct as a field, so that multiple `EnvFilter`s used as per-subscriber filters don't share a single span scope. Fixes #1868 Follow-up on #1973 Co-authored-by: Eliza Weisman <[email protected]>
1 parent 7e4724d commit 9b7bab1

File tree

5 files changed

+497
-43
lines changed

5 files changed

+497
-43
lines changed

tracing-mock/src/span.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ pub fn mock() -> MockSpan {
2424
}
2525
}
2626

27+
pub fn named<I>(name: I) -> MockSpan
28+
where
29+
I: Into<String>,
30+
{
31+
mock().named(name)
32+
}
33+
2734
impl MockSpan {
2835
pub fn named<I>(self, name: I) -> Self
2936
where

tracing-subscriber/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ rust-version = "1.49.0"
2727
default = ["smallvec", "fmt", "ansi", "tracing-log", "std"]
2828
alloc = []
2929
std = ["alloc", "tracing-core/std"]
30-
env-filter = ["matchers", "regex", "lazy_static", "tracing", "std"]
30+
env-filter = ["matchers", "regex", "lazy_static", "tracing", "std", "thread_local"]
3131
fmt = ["registry", "std"]
3232
ansi = ["fmt", "ansi_term"]
3333
registry = ["sharded-slab", "thread_local", "std"]

tracing-subscriber/src/filter/env/mod.rs

Lines changed: 178 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ mod field;
1010

1111
use crate::{
1212
filter::LevelFilter,
13-
subscribe::{Context, Subscribe},
13+
subscribe::{self, Context, Subscribe},
1414
sync::RwLock,
1515
};
1616
use directive::ParseError;
1717
use std::{cell::RefCell, collections::HashMap, env, error::Error, fmt, str::FromStr};
18+
use thread_local::ThreadLocal;
1819
use tracing_core::{
1920
callsite,
2021
collect::{Collect, Interest},
@@ -25,6 +26,16 @@ use tracing_core::{
2526
/// A [`Subscriber`] which filters spans and events based on a set of filter
2627
/// directives.
2728
///
29+
/// `EnvFilter` implements both the [`Subscribe`](#impl-Subscribe<C>) and
30+
/// [`Filter`] traits, so it may be used for both [global filtering][global] and
31+
/// [per-subscriber filtering][psf], respectively. See [the documentation on
32+
/// filtering with `Subscriber`s][filtering] for details.
33+
///
34+
/// The [`Targets`] type implements a similar form of filtering, but without the
35+
/// ability to dynamically enable events based on the current span context, and
36+
/// without filtering on field values. When these features are not required,
37+
/// [`Targets`] provides a lighter-weight alternative to [`EnvFilter`].
38+
///
2839
/// # Directives
2940
///
3041
/// A filter consists of one or more comma-separated directives which match on [`Span`]s and [`Event`]s.
@@ -71,7 +82,7 @@ use tracing_core::{
7182
/// - A dash in a target will only appear when being specified explicitly:
7283
/// `tracing::info!(target: "target-name", ...);`
7384
///
74-
/// ## Examples
85+
/// ## Example Syntax
7586
///
7687
/// - `tokio::net=info` will enable all spans or events that:
7788
/// - have the `tokio::net` target,
@@ -88,10 +99,54 @@ use tracing_core::{
8899
/// - which has a field named `name` with value `bob`,
89100
/// - at _any_ level.
90101
///
91-
/// The [`Targets`] type implements a similar form of filtering, but without the
92-
/// ability to dynamically enable events based on the current span context, and
93-
/// without filtering on field values. When these features are not required,
94-
/// [`Targets`] provides a lighter-weight alternative to [`EnvFilter`].
102+
/// # Examples
103+
///
104+
/// Parsing an `EnvFilter` from the [default environment
105+
/// variable](EnvFilter::from_default_env) (`RUST_LOG`):
106+
///
107+
/// ```
108+
/// use tracing_subscriber::{EnvFilter, fmt, prelude::*};
109+
///
110+
/// tracing_subscriber::registry()
111+
/// .with(fmt::layer())
112+
/// .with(EnvFilter::from_default_env())
113+
/// .init();
114+
/// ```
115+
///
116+
/// Parsing an `EnvFilter` [from a user-provided environment
117+
/// variable](EnvFilter::from_env):
118+
///
119+
/// ```
120+
/// use tracing_subscriber::{EnvFilter, fmt, prelude::*};
121+
///
122+
/// tracing_subscriber::registry()
123+
/// .with(fmt::layer())
124+
/// .with(EnvFilter::from_env("MYAPP_LOG"))
125+
/// .init();
126+
/// ```
127+
///
128+
/// Using `EnvFilter` as a [per-subscriber filter][psf] to filter only a single
129+
/// [subscriber](crate::subscribe::Subscribe):
130+
///
131+
/// ```
132+
/// use tracing_subscriber::{EnvFilter, fmt, prelude::*};
133+
///
134+
/// // Parse an `EnvFilter` configuration from the `RUST_LOG`
135+
/// // environment variable.
136+
/// let filter = EnvFilter::from_default_env();
137+
///
138+
/// // Apply the filter to this suscriber *only*.
139+
/// let filtered_subscriber = fmt::subscriber().with_filter(filter);
140+
///
141+
/// // Some other subscriber, whose output we don't want to filter.
142+
/// let unfiltered_subscriber = // ...
143+
/// # fmt::subscriber();
144+
///
145+
/// tracing_subscriber::registry()
146+
/// .with(filtered_subscriber)
147+
/// .with(unfiltered_subscriber)
148+
/// .init();
149+
/// ```
95150
///
96151
/// [`Subscriber`]: Subscribe
97152
/// [`env_logger`]: https://docs.rs/env_logger/0.7.1/env_logger/#enabling-logging
@@ -101,6 +156,10 @@ use tracing_core::{
101156
/// [`level`]: tracing_core::Level
102157
/// [`Metadata`]: tracing_core::Metadata
103158
/// [`Targets`]: crate::filter::Targets
159+
/// [`Filter`]: #impl-Filter<C>
160+
/// [global]: crate::subscribe#global-filtering
161+
/// [psf]: crate::subscribe#per-subscriber-filtering
162+
/// [filtering]: crate::subscribe#filtering-with-subscribers
104163
#[cfg_attr(docsrs, doc(cfg(all(feature = "env-filter", feature = "std"))))]
105164
#[derive(Debug)]
106165
pub struct EnvFilter {
@@ -109,10 +168,7 @@ pub struct EnvFilter {
109168
has_dynamics: bool,
110169
by_id: RwLock<HashMap<span::Id, directive::SpanMatcher>>,
111170
by_cs: RwLock<HashMap<callsite::Identifier, directive::CallsiteMatcher>>,
112-
}
113-
114-
thread_local! {
115-
static SCOPE: RefCell<Vec<LevelFilter>> = RefCell::new(Vec::new());
171+
scope: ThreadLocal<RefCell<Vec<LevelFilter>>>,
116172
}
117173

118174
type FieldMap<T> = HashMap<Field, T>;
@@ -347,6 +403,10 @@ impl EnvFilter {
347403
has_dynamics,
348404
by_id: RwLock::new(HashMap::new()),
349405
by_cs: RwLock::new(HashMap::new()),
406+
// TODO(eliza): maybe worth allocating capacity for `num_cpus`
407+
// threads or something (assuming we're running in Tokio)? or
408+
// `num_cpus * 2` or something?
409+
scope: ThreadLocal::new(),
350410
}
351411
}
352412

@@ -362,9 +422,7 @@ impl EnvFilter {
362422
Interest::never()
363423
}
364424
}
365-
}
366425

367-
impl<C: Collect> Subscribe<C> for EnvFilter {
368426
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
369427
if self.has_dynamics && metadata.is_span() {
370428
// If this metadata describes a span, first, check if there is a
@@ -385,20 +443,7 @@ impl<C: Collect> Subscribe<C> for EnvFilter {
385443
}
386444
}
387445

388-
fn max_level_hint(&self) -> Option<LevelFilter> {
389-
if self.dynamics.has_value_filters() {
390-
// If we perform any filtering on span field *values*, we will
391-
// enable *all* spans, because their field values are not known
392-
// until recording.
393-
return Some(LevelFilter::TRACE);
394-
}
395-
std::cmp::max(
396-
self.statics.max_level.into(),
397-
self.dynamics.max_level.into(),
398-
)
399-
}
400-
401-
fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, C>) -> bool {
446+
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
402447
let level = metadata.level();
403448

404449
// is it possible for a dynamic filter directive to enable this event?
@@ -418,14 +463,15 @@ impl<C: Collect> Subscribe<C> for EnvFilter {
418463
}
419464
}
420465

421-
let enabled_by_scope = SCOPE.with(|scope| {
422-
for filter in scope.borrow().iter() {
466+
let enabled_by_scope = {
467+
let scope = self.scope.get_or_default().borrow();
468+
for filter in &*scope {
423469
if filter >= level {
424470
return true;
425471
}
426472
}
427473
false
428-
});
474+
};
429475
if enabled_by_scope {
430476
return true;
431477
}
@@ -441,36 +487,43 @@ impl<C: Collect> Subscribe<C> for EnvFilter {
441487
false
442488
}
443489

444-
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, _: Context<'_, C>) {
490+
fn max_level_hint(&self) -> Option<LevelFilter> {
491+
if self.dynamics.has_value_filters() {
492+
// If we perform any filtering on span field *values*, we will
493+
// enable *all* spans, because their field values are not known
494+
// until recording.
495+
return Some(LevelFilter::TRACE);
496+
}
497+
std::cmp::max(
498+
self.statics.max_level.into(),
499+
self.dynamics.max_level.into(),
500+
)
501+
}
502+
503+
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id) {
445504
let by_cs = try_lock!(self.by_cs.read());
446505
if let Some(cs) = by_cs.get(&attrs.metadata().callsite()) {
447506
let span = cs.to_span_match(attrs);
448507
try_lock!(self.by_id.write()).insert(id.clone(), span);
449508
}
450509
}
451510

452-
fn on_record(&self, id: &span::Id, values: &span::Record<'_>, _: Context<'_, C>) {
453-
if let Some(span) = try_lock!(self.by_id.read()).get(id) {
454-
span.record_update(values);
455-
}
456-
}
457-
458-
fn on_enter(&self, id: &span::Id, _: Context<'_, C>) {
511+
fn on_enter(&self, id: &span::Id) {
459512
// XXX: This is where _we_ could push IDs to the stack instead, and use
460513
// that to allow changing the filter while a span is already entered.
461514
// But that might be much less efficient...
462515
if let Some(span) = try_lock!(self.by_id.read()).get(id) {
463-
SCOPE.with(|scope| scope.borrow_mut().push(span.level()));
516+
self.scope.get_or_default().borrow_mut().push(span.level());
464517
}
465518
}
466519

467-
fn on_exit(&self, id: &span::Id, _: Context<'_, C>) {
520+
fn on_exit(&self, id: &span::Id) {
468521
if self.cares_about_span(id) {
469-
SCOPE.with(|scope| scope.borrow_mut().pop());
522+
self.scope.get_or_default().borrow_mut().pop();
470523
}
471524
}
472525

473-
fn on_close(&self, id: span::Id, _: Context<'_, C>) {
526+
fn on_close(&self, id: span::Id) {
474527
// If we don't need to acquire a write lock, avoid doing so.
475528
if !self.cares_about_span(&id) {
476529
return;
@@ -481,6 +534,90 @@ impl<C: Collect> Subscribe<C> for EnvFilter {
481534
}
482535
}
483536

537+
impl<C: Collect> Subscribe<C> for EnvFilter {
538+
#[inline]
539+
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
540+
EnvFilter::register_callsite(self, metadata)
541+
}
542+
543+
#[inline]
544+
fn max_level_hint(&self) -> Option<LevelFilter> {
545+
EnvFilter::max_level_hint(self)
546+
}
547+
548+
#[inline]
549+
fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, C>) -> bool {
550+
self.enabled(metadata)
551+
}
552+
553+
#[inline]
554+
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, _: Context<'_, C>) {
555+
self.on_new_span(attrs, id)
556+
}
557+
558+
fn on_record(&self, id: &span::Id, values: &span::Record<'_>, _: Context<'_, C>) {
559+
if let Some(span) = try_lock!(self.by_id.read()).get(id) {
560+
span.record_update(values);
561+
}
562+
}
563+
564+
#[inline]
565+
fn on_enter(&self, id: &span::Id, _: Context<'_, C>) {
566+
self.on_enter(id);
567+
}
568+
569+
#[inline]
570+
fn on_exit(&self, id: &span::Id, _: Context<'_, C>) {
571+
self.on_exit(id);
572+
}
573+
574+
#[inline]
575+
fn on_close(&self, id: span::Id, _: Context<'_, C>) {
576+
self.on_close(id);
577+
}
578+
}
579+
580+
feature! {
581+
#![all(feature = "registry", feature = "std")]
582+
583+
impl<C> subscribe::Filter<C> for EnvFilter {
584+
#[inline]
585+
fn enabled(&self, meta: &Metadata<'_>, _: &Context<'_, C>) -> bool {
586+
self.enabled(meta)
587+
}
588+
589+
#[inline]
590+
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
591+
self.register_callsite(meta)
592+
}
593+
594+
#[inline]
595+
fn max_level_hint(&self) -> Option<LevelFilter> {
596+
EnvFilter::max_level_hint(self)
597+
}
598+
599+
#[inline]
600+
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, _: Context<'_, C>) {
601+
self.on_new_span(attrs, id)
602+
}
603+
604+
#[inline]
605+
fn on_enter(&self, id: &span::Id, _: Context<'_, C>) {
606+
self.on_enter(id);
607+
}
608+
609+
#[inline]
610+
fn on_exit(&self, id: &span::Id, _: Context<'_, C>) {
611+
self.on_exit(id);
612+
}
613+
614+
#[inline]
615+
fn on_close(&self, id: span::Id, _: Context<'_, C>) {
616+
self.on_close(id);
617+
}
618+
}
619+
}
620+
484621
impl FromStr for EnvFilter {
485622
type Err = directive::ParseError;
486623

tracing-subscriber/tests/filter.rs renamed to tracing-subscriber/tests/env_filter/main.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
#![cfg(feature = "env-filter")]
22

3+
#[path = "../support.rs"]
4+
mod support;
5+
use self::support::*;
6+
7+
mod per_subscriber;
8+
39
use tracing::{self, collect::with_default, Level};
4-
use tracing_mock::*;
510
use tracing_subscriber::{
611
filter::{EnvFilter, LevelFilter},
712
prelude::*,

0 commit comments

Comments
 (0)