过滤:全局与每层
主线位置:事件主线的第二、三、四道门,以及最后的
on_event。全局过滤在register_callsite和enabled上说never/false,整个栈到此为止;每层过滤在这两处只把自己的意见记进线程局部状态、对外放行,等事件真正分发时,各层在on_event前查自己那一位。span 主线上,new_span时把这份意见存进 span 的数据,之后enter、exit、close都按它跳过。
flowchart TB
ev["info! 经过门"] --> lay["Layered::enabled 从外往里问"]
lay --> g{"全局过滤层"}
g -->|false| stop["整个栈都看不到"]
g -->|true| f{"Filtered:过滤器"}
f -->|"不要:置位,返回 true"| reg["Registry::enabled"]
f -->|"要:清位,问里层"| reg
reg -->|true| disp["构造事件,分发"]
disp --> oe{"Filtered::on_event 查位"}
oe -->|"位为 0"| inner["内层 layer 收到"]
oe -->|"位为 1"| skip["跳过并清位"]
一个 Layer 的 enabled 返回 false,整个栈都看不到这条事件(Layer 与 Layered 讲过这种短路)。这正是过滤器想要的效果,但也只能有这一种效果:想让输出到终端的层只要 INFO、写文件的层要 DEBUG,单靠 enabled 做不到。tracing-subscriber 因此有两种过滤位置:全局,过滤器本身当作一层用 .with(filter) 叠上去;每层,用 layer.with_filter(filter) 包住某一层,返回一个 Filtered。这一页先用一个实验看两者的差别,再拆开每层过滤的实现,最后看现成的几种过滤器。EnvFilter 两种位置都能用,它自己的语法和内部结构在 EnvFilter。
两个位置,一个实验
两个 fmt::Layer,一个叫 stdout,一个叫 file(都打印到标准输出,每行前面加上名字以便区分)。目标是 stdout 只要 INFO,file 要 DEBUG。
tracing = "=0.1.44"
tracing-subscriber = "=0.3.23"
use std::io::{self, Write};
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{Registry, fmt};
// 给每行加一个前缀,区分是哪一层写的
struct Tagged(&'static str);
impl Write for Tagged {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
print!("[{}] {}", self.0, String::from_utf8_lossy(buf));
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
fn layer<S>(tag: &'static str) -> impl tracing_subscriber::Layer<S>
where
S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
{
fmt::layer()
.with_ansi(false)
.without_time()
.with_target(false)
.with_writer(move || Tagged(tag))
}
fn emit() {
tracing::info!("info event");
tracing::debug!("debug event");
}
fn main() {
println!("== 1. 全局 INFO:.with(LevelFilter::INFO)");
let sub = Registry::default()
.with(LevelFilter::INFO)
.with(layer("stdout"))
.with(layer("file"));
tracing::subscriber::with_default(sub, emit);
println!("== 2. 全局 DEBUG:.with(LevelFilter::DEBUG)");
let sub = Registry::default()
.with(LevelFilter::DEBUG)
.with(layer("stdout"))
.with(layer("file"));
tracing::subscriber::with_default(sub, emit);
println!("== 3. 全局 INFO 放在两层中间");
let sub = Registry::default()
.with(layer("stdout"))
.with(LevelFilter::INFO)
.with(layer("file"));
tracing::subscriber::with_default(sub, emit);
println!("== 4. 每层过滤:stdout INFO,file DEBUG");
let sub = Registry::default()
.with(layer("stdout").with_filter(LevelFilter::INFO))
.with(layer("file").with_filter(LevelFilter::DEBUG));
tracing::subscriber::with_default(sub, emit);
}
M2 Max、rustc 1.99.0、--release,跑 3 次,输出相同:
== 1. 全局 INFO:.with(LevelFilter::INFO)
[stdout] INFO info event
[file] INFO info event
== 2. 全局 DEBUG:.with(LevelFilter::DEBUG)
[stdout] INFO info event
[file] INFO info event
[stdout] DEBUG debug event
[file] DEBUG debug event
== 3. 全局 INFO 放在两层中间
[stdout] INFO info event
[file] INFO info event
== 4. 每层过滤:stdout INFO,file DEBUG
[stdout] INFO info event
[file] INFO info event
[file] DEBUG debug event
- 1 和 2:全局过滤只有一个级别,两层看到的永远一样。设成
INFO,file少了DEBUG;设成DEBUG,stdout多了DEBUG。 - 3:把
LevelFilter::INFO挪到两层中间,它并不只挡住外面的file。enabled从最外层往里问,问到它时返回false,整个栈短路,里面的stdout也看不到。.with的顺序不改变全局过滤的作用范围。 - 4:每层过滤,两层各拿到自己要的。
tracing_subscriber::fmt().with_env_filter(..) 和 fmt().with_max_level(..) 都属于第一种:SubscriberBuilder::finish 先把 fmt::Layer 叠在 Registry 上,再用 self.filter.with_subscriber(..) 把过滤器叠在最外面。
全局过滤:过滤器自己就是一层
LevelFilter 能直接传给 .with,是因为它实现了 Layer,见 filter/level.rs:
impl<S: Subscriber> crate::Layer<S> for LevelFilter {
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
if self >= metadata.level() {
Interest::always()
} else {
Interest::never()
}
}
fn enabled(&self, metadata: &Metadata<'_>, _: crate::layer::Context<'_, S>) -> bool {
self >= metadata.level()
}
fn max_level_hint(&self) -> Option<LevelFilter> {
Some(*self)
}
}
三个判断方法全部实现,通知方法一个都没有:它不记录任何东西,只回答「要不要」。Targets、FilterFn、EnvFilter 也都实现了 Layer,用法一样。起作用的是 Layered 的 enabled(这是 Layered 作为 Subscriber 时的版本):
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
if self.layer.enabled(metadata, self.ctx()) {
// if the outer layer enables the callsite metadata, ask the subscriber.
self.inner.enabled(metadata)
} else {
// otherwise, the callsite is disabled by the layer
// If per-layer filters are in use, and we are short-circuiting
// (rather than calling into the inner type), clear the current
// per-layer filter `enabled` state.
#[cfg(feature = "registry")]
filter::FilterState::clear_enabled();
false
}
}
本层说 false 就返回 false,Dispatch 拿到 false,宏不构造事件,所有层都看不到。register_callsite 也一样:本层说 never,pick_interest 直接返回 never,这个调用点被缓存成 never(Callsite 与 Interest 缓存)。中间那句 clear_enabled 是给每层过滤收拾残局的:外面的 Filtered 可能已经在线程局部里写下了自己的意见,这里一短路,后面就没有人会清掉它,只能当场清。
clear_enabled 是一个 panic 换来的per-layer filtering 在 tracing-subscriber 0.2.21 首次发布(PR #1523)。几天后的 issue #1563 报告:registry().with(Targets).with(fmt_layer.with_filter(filter_fn(..))) 这种「全局过滤加每层过滤」的组合,在 debug 构建里 panic,报的是下面会看到的那组调试计数器的断言:「if we are in or starting a filter pass, we must not be in an interest pass.」原因是全局过滤短路后,每层过滤留在线程局部里的状态没人清。先修的是 register_callsite 那一半(PR #1569,0.2.23,pick_interest 短路时调用 take_interest),再修 enabled 这一半(PR #1575),提交说明是「When a global filter returns false from enabled, ensure that the per-layer filter FilterMap and debug counters are cleared, so that they are empty on the next enabled call.」后者随 tracing-subscriber 0.2.24 发布,0.3 线从 0.3.0 起就带着它。
每层过滤:Filtered
Layer::with_filter 只是 Filtered::new(self, filter)。Filtered:
pub struct Filtered<L, F, S> {
filter: F,
layer: L,
id: MagicPlfDowncastMarker,
_s: PhantomData<fn(S)>,
}
filter 实现的是 Filter<S> 而不是 Layer<S>。两个 trait 的判断方法一一对应,语义不同:
Filter 的方法 | 对应 Layer 的 | 区别 |
|---|---|---|
enabled(&self, &Metadata, &Context) | enabled | 返回 false 只让被包的这一层看不到,其他层照常 |
callsite_enabled(&self, &'static Metadata) | register_callsite | 默认返回 sometimes,不像 Layer 的默认那样把 enabled 冻结成 always / never |
event_enabled(&self, &Event, &Context) | event_enabled | 默认 true;enabled 已经说 false 时不会被调用 |
max_level_hint(&self) | max_level_hint | 默认 None |
Filter 还有 on_new_span、on_enter、on_exit、on_close、on_record 五个通知方法(默认空),给需要跟踪 span 状态的过滤器用,EnvFilter 的动态指令就靠它们。
id 字段是 FilterId 外面包一层 #[repr(transparent)] 的标记类型,用来让 downcast_raw 认出「这一层带每层过滤」,见 Layer 与 Layered。下面只关心里面的 FilterId。
一条事件经过时,同一线程上的所有 Filtered 共用一张传阅单(线程局部的 FilterMap),单子上每个过滤器有自己的一栏(FilterId 那一位)。传阅时每个过滤器只在自己那栏打钩表示「我这层不要」(置位),然后照样往下传(返回 true)。传到最里面的登记员(Registry::enabled)手上,他看看是不是每栏都打了钩。没全打,就把事件正式发出来;各层收件前看一眼自己那栏,打了钩的不收,顺手把钩擦掉(did_enable 清位),单子就干净了,下一条事件接着用。
FilterId:一个位,最多 64 个
FilterId 是 FilterId(u64),存的是掩码而不是编号。它在 Filtered 被叠上去的那一刻分配,on_layer 向内层要:
fn on_layer(&mut self, subscriber: &mut S) {
self.id = MagicPlfDowncastMarker(subscriber.register_filter());
self.layer.on_layer(subscriber);
}
register_filter 一路经 Layered 转到 Registry::register_filter,用的是 Registry 里一个 u8 计数器 next_filter_id,再交给 FilterId::new:
fn register_filter(&mut self) -> FilterId {
let id = FilterId::new(self.next_filter_id);
self.next_filter_id += 1;
id
}
pub(crate) fn new(id: u8) -> Self {
assert!(id < 64, "filter IDs may not be greater than 64");
Self(1 << id as usize)
}
第 n 个过滤器拿到 1 << n。位图是一个 u64,所以上限是 64 个。在构造 Filtered 时 id 先填 FilterId::disabled(),即 u64::MAX;没经过 on_layer 就被调用时,Filtered::id() 里的 debug_assert! 会报「was it registered with the subscriber?」。
只有 Registry 实现了 register_filter(LookupSpan 的默认实现直接 panic:「does not currently support filters」),这就是文档说「Registry 是唯一能承载每层过滤的根 subscriber」的原因。Registry::has_per_layer_filters 也只是看 next_filter_id > 0。
用 Vec<Box<dyn Layer>> 一次放 64 个和 65 个 Filtered:
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::layer::{Identity, Layer};
use tracing_subscriber::prelude::*;
use tracing_subscriber::Registry;
fn build(n: usize) {
let layers: Vec<Box<dyn Layer<Registry> + Send + Sync>> = (0..n)
.map(|_| Identity::new().with_filter(LevelFilter::INFO).boxed())
.collect();
let _sub = Registry::default().with(layers);
println!("{n} 个 Filtered:ok");
}
fn main() {
build(64);
build(65);
}
64 个 Filtered:ok
thread 'main' (1420970) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tracing-subscriber-0.3.23/src/filter/layer_filters/mod.rs:913:9:
filter IDs may not be greater than 64
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
跑 3 次,除了线程号都相同。panic 发生在 .with(layers) 里,也就是组装 subscriber 时,不会拖到第一条日志。消息说「不能大于 64」,实际第 65 个(编号 64)就触发了。
掩码而不是编号,是为了嵌套。FilterId::and 把两个掩码按位或起来:Filtered { filter1, Layered { layer1, Filtered { filter2, layer2 } } } 里,layer2 拿到的 Context 带着 filter1 | filter2,查 span 时只要任一位置位就算「被禁用」。源码注释举了这个例子,说用编号的话,layer2 遍历父 span 时会看到被 filter1 挡掉的 span。
enabled:记下意见,对外放行
每个线程有一份 FilterState,放在线程局部变量 FILTERING 里:
pub(crate) struct FilterState {
enabled: Cell<FilterMap>,
// TODO(eliza): `Interest`s should _probably_ be `Copy`. The only reason
// they're not is our Obsessive Commitment to Forwards-Compatibility. If
// this changes in tracing-core`, we can make this a `Cell` rather than
// `RefCell`...
interest: RefCell<Option<Interest>>,
#[cfg(debug_assertions)]
counters: DebugCounters,
}
enabled 是这一轮 enabled 调用的结果,FilterMap 就是一个 bits: u64,某位为 1 表示对应的过滤器不要(它的 Debug 输出字段名就叫 disabled_by)。interest 是 register_callsite 那一轮的汇总,下面讲。counters 只在 debug 构建里存在,专门用来断言这些状态在每轮开始时是空的,上面冷知识里的 panic 就出自它。
fn enabled(&self, metadata: &Metadata<'_>, cx: Context<'_, S>) -> bool {
let cx = cx.with_filter(self.id());
let enabled = self.filter.enabled(metadata, &cx);
FILTERING.with(|filtering| filtering.set(self.id(), enabled));
if enabled {
// If the filter enabled this metadata, ask the wrapped layer if
// _it_ wants it --- it might have a global filter.
self.layer.enabled(metadata, cx)
} else {
// Otherwise, return `true`. The _per-layer_ filter disabled this
// metadata, but returning `false` in `Layer::enabled` will
// short-circuit and globally disable the span or event. This is
// *not* what we want for per-layer filters, as other layers may
// still want this event. Returning `true` here means we'll continue
// asking the next layer in the stack.
// ...
true
}
}
过滤器说「不要」时,FilterMap::set 把自己那位置 1;说「要」时清 0,再去问被包的层,被包的层自己的 enabled 仍然是全局语义。返回值几乎总是 true,这样 Layered 不会短路,后面的层都有机会表态。
问到最里面,Registry::enabled 做最后的汇总:
fn enabled(&self, _: &Metadata<'_>) -> bool {
if self.has_per_layer_filters() {
return FilterState::event_enabled();
}
true
}
FilterState::event_enabled 读的是 FilterMap::any_enabled:
#[inline]
pub(crate) fn any_enabled(self) -> bool {
self.bits != u64::MAX
}
FilterState 的文档说这一步的用意是「If all the bits are set, then every per-layer filter has decided it doesn't want to enable that span or event. In that case, the Registry's enabled method will return false」。但比较的对象是 u64::MAX:只有 64 位全为 1 才算「没人要」,而 n 个过滤器最多只会置 n 位。下面用 dynamic_filter_fn(|_, _| false) 让每个过滤器的 Interest 都是 sometimes、每次都说不要,再用一个有副作用的字段表达式看事件有没有被构造:
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use tracing::{Event, Subscriber};
use tracing_subscriber::filter::dynamic_filter_fn;
use tracing_subscriber::layer::{Context, Layer};
use tracing_subscriber::prelude::*;
use tracing_subscriber::Registry;
static EVALUATED: AtomicUsize = AtomicUsize::new(0);
static DELIVERED: AtomicUsize = AtomicUsize::new(0);
fn bump() -> u64 {
EVALUATED.fetch_add(1, Relaxed);
1
}
struct Count;
impl<S: Subscriber> Layer<S> for Count {
fn on_event(&self, _: &Event<'_>, _: Context<'_, S>) {
DELIVERED.fetch_add(1, Relaxed);
}
}
fn run(n: usize) {
EVALUATED.store(0, Relaxed);
DELIVERED.store(0, Relaxed);
// n 个每层过滤,全都说「不要」;dynamic_filter_fn 让 Interest 是 sometimes
let layers: Vec<Box<dyn Layer<Registry> + Send + Sync>> = (0..n)
.map(|_| Count.with_filter(dynamic_filter_fn(|_, _| false)).boxed())
.collect();
let sub = Registry::default().with(layers);
tracing::subscriber::with_default(sub, || {
for _ in 0..1000 {
tracing::info!(x = bump());
}
});
println!(
"{n:>2} 个过滤全说 false:字段求值 {} 次,on_event {} 次",
EVALUATED.load(Relaxed),
DELIVERED.load(Relaxed)
);
}
fn main() {
run(1);
run(2);
run(63);
run(64);
}
跑 3 次,输出相同:
1 个过滤全说 false:字段求值 1000 次,on_event 0 次
2 个过滤全说 false:字段求值 1000 次,on_event 0 次
63 个过滤全说 false:字段求值 1000 次,on_event 0 次
64 个过滤全说 false:字段求值 0 次,on_event 0 次
结果仍然正确,没有任何一层收到事件;但不满 64 个过滤器时,Registry::enabled 返回了 true,事件被构造、字段被求值、分发到每一层再被各自跳过。这只影响 Interest 是 sometimes 的调用点:所有过滤器都在 register_callsite 时说 never 的调用点,在缓存那一步就停了(下面的「意见不一时的 Interest」)。
on_event:查自己那一位,顺手清掉
事件分发后,Filtered::on_event 把调用包进 did_enable:
fn did_enable(&self, filter: FilterId, f: impl FnOnce()) {
let map = self.enabled.get();
if map.is_enabled(filter) {
// If the filter didn't disable the current span/event, run the
// callback.
f();
} else {
// Otherwise, if this filter _did_ disable the span or event
// currently being processed, clear its bit from this thread's
// `FilterState`. The bit has already been "consumed" by skipping
// this callback, and we need to ensure that the `FilterMap` for
// this thread is reset when the *next* `enabled` call occurs.
self.enabled.set(map.set(filter, true));
}
// ...
}
is_enabled 是 bits & mask == 0。位为 1 的层跳过 on_event 并把位清掉;位为 0 的层本来就是 0。所有 Filtered 都过一遍之后,FilterMap 回到全 0,下一条事件从干净的状态开始。这套做法能成立,依赖两件事:同一条事件的 enabled 和 on_event 在同一个线程上连续发生(宏里先 enabled 再 Event::dispatch,见 Dispatch);中途短路时有人负责清空(上面的 clear_enabled)。
event_enabled 是第二轮:Filtered::event_enabled 用 FilterState::and,只有本位还是 0 时才调用过滤器的 event_enabled,结果再写回本位。这时字段值已经在 Event 里了,所以每层过滤也能按字段值过滤。
span:位图存进 span 的数据
span 也走同样的 enabled,但 span 不是一次性的,之后还有 enter、exit、record、close。Registry::new_span 在分配槽位时把当前的 FilterMap 拷进 span 的 DataInner:
data.filter_map = crate::filter::FILTERING.with(|filtering| filtering.filter_map());
之后 Filtered::on_enter / on_exit / on_close / on_record 都用 cx.if_enabled_for(id, self.id()) 查这份存下来的位图(Registry 的 span 数据上的 is_enabled_for),不再问过滤器。on_follows_from 要求两个 span 都对本层可见。
被过滤掉的 span 在这一层的 Context 里也看不到:Context 带着本层的 FilterId,Context::span 用 try_with_filter 过滤,lookup_current 和 scope() 遍历父 span 时跳过本层不可见的。两个 fmt::Layer,一个只要 INFO,一个要 DEBUG,事件在一个 DEBUG 级的 span 里:
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{Registry, fmt};
fn main() {
let sub = Registry::default()
.with(fmt::layer().with_ansi(false).without_time().with_filter(LevelFilter::INFO))
.with(fmt::layer().with_ansi(false).without_time().with_filter(LevelFilter::DEBUG));
tracing::subscriber::with_default(sub, || {
let outer = tracing::info_span!("outer");
let _a = outer.enter();
let inner = tracing::debug_span!("inner");
let _b = inner.enter();
tracing::info!("hello");
});
}
跑 3 次,输出相同:
INFO outer: span_scope: hello
INFO outer:inner: span_scope: hello
第一行来自 INFO 那层(它在里面,on_event 先跑),前缀里没有 inner;第二行来自 DEBUG 那层。同一条事件,两层看到的 span 树不一样。span 槽位本身的分配和回收在 Registry。
register_callsite:意见不一时的 Interest
enabled 每次都要走一遍,Interest 缓存才是省开销的地方。Filtered::register_callsite:
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
let interest = self.filter.callsite_enabled(metadata);
// If the filter didn't disable the callsite, allow the inner layer to
// register it — since `register_callsite` is also used for purposes
// such as reserving/caching per-callsite data, we want the inner layer
// to be able to perform any other registration steps. However, we'll
// ignore its `Interest`.
if !interest.is_never() {
self.layer.register_callsite(metadata);
}
// Add our `Interest` to the current sum of per-layer filter `Interest`s
// for this callsite.
FILTERING.with(|filtering| filtering.add_interest(interest));
// don't short circuit! ...
Interest::always()
}
每个过滤器的答案经 add_interest 合并进线程局部的 interest:
if let Some(curr_interest) = curr_interest.as_mut() {
if (curr_interest.is_always() && !interest.is_always())
|| (curr_interest.is_never() && !interest.is_never())
{
*curr_interest = Interest::sometimes();
}
// If the two interests are the same, do nothing. If the current
// interest is `sometimes`, stay sometimes.
} else {
*curr_interest = Some(interest);
}
规则和 tracing-core 合并多个 dispatcher 时一样:全相同就保留,有分歧就是 sometimes。最后 Registry::register_callsite 用 take_interest 取出汇总值作为自己的答案。Filtered 对外一律返回 always,以及外面再套普通 layer 时 never 会被改成 sometimes,这两条是 pick_interest 里的规则,在 Layer 与 Layered 讲过。
两个过滤器 A(INFO)和 B(DEBUG)意见不一时,实测每个调用点的 enabled 被问了几次。为了数次数,自己写一个和 LevelFilter 行为相同的 Filter:
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use tracing::subscriber::Interest;
use tracing::{Event, Metadata, Subscriber};
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::layer::{Context, Filter, Layer};
use tracing_subscriber::prelude::*;
use tracing_subscriber::Registry;
// 和 LevelFilter 一样的过滤,只是数一下 enabled 被问了几次
struct CountingLevel(LevelFilter, &'static AtomicUsize);
impl<S> Filter<S> for CountingLevel {
fn enabled(&self, meta: &Metadata<'_>, _: &Context<'_, S>) -> bool {
self.1.fetch_add(1, Relaxed);
meta.level() <= &self.0
}
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
if meta.level() <= &self.0 { Interest::always() } else { Interest::never() }
}
fn max_level_hint(&self) -> Option<LevelFilter> {
Some(self.0)
}
}
// 被过滤的层:数 on_event
struct Count(&'static AtomicUsize);
impl<S: Subscriber> Layer<S> for Count {
fn on_event(&self, _: &Event<'_>, _: Context<'_, S>) {
self.0.fetch_add(1, Relaxed);
}
}
// 不带过滤的普通层,什么都不做
struct Plain;
impl<S: Subscriber> Layer<S> for Plain {}
static A_ENABLED: AtomicUsize = AtomicUsize::new(0);
static B_ENABLED: AtomicUsize = AtomicUsize::new(0);
static A_EVENTS: AtomicUsize = AtomicUsize::new(0);
static B_EVENTS: AtomicUsize = AtomicUsize::new(0);
fn run(label: &str, f: impl Fn()) {
for c in [&A_ENABLED, &B_ENABLED, &A_EVENTS, &B_EVENTS] {
c.store(0, Relaxed);
}
for _ in 0..1000 {
f();
}
println!(
"{label:<7} A.enabled={:<5} B.enabled={:<5} A.on_event={:<5} B.on_event={}",
A_ENABLED.load(Relaxed),
B_ENABLED.load(Relaxed),
A_EVENTS.load(Relaxed),
B_EVENTS.load(Relaxed),
);
}
fn body() {
println!("LevelFilter::current() = {}", LevelFilter::current());
run("info!", || tracing::info!("x"));
run("debug!", || tracing::debug!("x"));
run("trace!", || tracing::trace!("x"));
}
fn main() {
println!("== A 只要 INFO,B 要 DEBUG");
let sub = Registry::default()
.with(Count(&A_EVENTS).with_filter(CountingLevel(LevelFilter::INFO, &A_ENABLED)))
.with(Count(&B_EVENTS).with_filter(CountingLevel(LevelFilter::DEBUG, &B_ENABLED)));
tracing::subscriber::with_default(sub, body);
println!("== 外面再套一个不带过滤的层");
let sub = Registry::default()
.with(Count(&A_EVENTS).with_filter(CountingLevel(LevelFilter::INFO, &A_ENABLED)))
.with(Count(&B_EVENTS).with_filter(CountingLevel(LevelFilter::DEBUG, &B_ENABLED)))
.with(Plain);
tracing::subscriber::with_default(sub, body);
}
跑 3 次,输出相同:
== A 只要 INFO,B 要 DEBUG
LevelFilter::current() = debug
info! A.enabled=0 B.enabled=0 A.on_event=1000 B.on_event=1000
debug! A.enabled=1000 B.enabled=1000 A.on_event=0 B.on_event=1000
trace! A.enabled=0 B.enabled=0 A.on_event=0 B.on_event=0
== 外面再套一个不带过滤的层
LevelFilter::current() = trace
info! A.enabled=0 B.enabled=0 A.on_event=1000 B.on_event=1000
debug! A.enabled=1000 B.enabled=1000 A.on_event=0 B.on_event=1000
trace! A.enabled=1000 B.enabled=1000 A.on_event=0 B.on_event=0
第一组:
info!:A、B 都是always,合并后always,enabled一次都不问,两层都收到。debug!:A 是never、B 是always,合并成sometimes。于是每次都问两个过滤器的enabled:A 置位、B 清位,on_event时 A 跳过、B 收到。意见不一的代价是这个调用点失去了缓存,每条debug!都要走一遍两个过滤器。trace!:没走到Interest。LevelFilter::current()是两个提示的最大值debug(Filtered::max_level_hint直接转发过滤器的提示),第二道门就挡住了,见 级别过滤的几道门。
第二组只是在最外面加了一个什么都不做的 Plain:
LevelFilter::current()变成trace。Plain没有max_level_hint,按pick_level_hint的规则,内层含每层过滤而外层提示为None时整个栈就是None,也就是不设上限。trace!因此走到了Interest:A、B 都是never,但外面的Plain想要(Layer默认的register_callsite问自己的enabled,默认是true,于是得到always),pick_interest把never改成sometimes。每条trace!都要问两个过滤器、构造事件、交给Plain。
所以给一个栈里加一个不带过滤、也不给提示的层,会让每层过滤的过滤器在所有级别上都被问到。
问: 每层过滤这么绕,为什么不在自己的 on_event 里判断一下级别,不要就直接 return?
答: 结果一样,代价不一样。on_event 被调用时,事件已经构造好、字段已经求值,前面几道门一道都没起作用。Filter 参与了 register_callsite(所有层都不要的调用点缓存成 never)和 max_level_hint(所有层的上限决定第二道门),这两样是 on_event 里的判断给不了的。此外 Filtered 还会替被包的层隐藏被过滤掉的 span,on_event 里的判断管不到 on_enter、scope() 这些地方。
现成的过滤器
LevelFilter
除了上面的 Layer 实现,impl Filter for LevelFilter 内容相同:callsite_enabled 按级别给 always / never,max_level_hint 返回自己。只按级别过滤时,它是开销最小的选择:所有判断都在缓存阶段完成。LevelFilter 本身的定义在 Metadata、Level 与 LevelFilter。
Targets
Targets 是 Targets(DirectiveSet<StaticDirective>),即「target 前缀 → 级别」的表,可以从 "my_app=debug,hyper=warn,info" 这样的字符串解析(FromStr),也可以用 with_target、with_default 构造。它和 EnvFilter 共用 filter/directive.rs 里的 StaticDirective 和 DirectiveSet,匹配规则(前缀匹配、最具体的指令优先)在 EnvFilter 讲。
区别在于只有静态指令:没有 span 名、没有字段值。所以 interested 对每个调用点总能给出 always 或 never,max_level_hint 是表里的最大级别,内部没有锁、没有线程局部。EnvFilter 的文档自己也说,不需要按 span 和字段过滤时,Targets 是「a lighter-weight alternative」。另外 Targets 不读环境变量,RUST_LOG 要自己读出来再 parse。
filter_fn 与 dynamic_filter_fn
filter_fn 把一个 Fn(&Metadata) -> bool 变成 FilterFn。闭包只看元数据,所以 is_callsite_enabled 直接拿它的结果冻结成 always / never,注释是「we can reasonably assume its results are cacheable」。这也意味着闭包里读一个运行时会变的开关是不生效的:调用点第一次经过时的答案会一直被缓存,除非有人调用 rebuild_interest_cache。
dynamic_filter_fn 的闭包多一个 &Context,可以看当前 span。它的 default_callsite_enabled 除了超出 max_level_hint 的级别给 never,其余一律 sometimes,每次都问;也可以用 with_callsite_filter 自己提供 register_callsite 的逻辑。
两者的 max_level_hint 默认都是 None,要用 with_max_level_hint 手动给。下一节的实验里能看到不给提示的后果。
组合子:and、or、not
FilterExt 给所有 Filter 加了 and、or、not、boxed。注意它们组合的是 Filter,结果只能用在 with_filter 里,不是 Layer。各自的规则:
| 组合子 | enabled | callsite_enabled | max_level_hint |
|---|---|---|---|
And | a && b,短路 | a 为 never 就是 never;否则 b 不是 always 就取 b;否则取 a | cmp::min(a, b),任一为 None 结果就是 None |
Or | a || b,短路 | 任一 always 为 always;任一 sometimes 为 sometimes;否则 never | 两者都有才取较大者,否则 None |
Not | !a | always 与 never 互换,sometimes 不变 | 总是 None,注释是「TODO(eliza): figure this out???」 |
Not 还有一处不对称:它反转 enabled 和 callsite_enabled,但 event_enabled 一律返回 true,不反转。FilterExt::not 的文档解释说,正确的反转应该是 !(enabled() && event_enabled()),而要做到这一点就得额外的线程局部状态记住 enabled 的结果,「to support a very niche use case」,所以没做。用 not 包一个按字段值过滤的过滤器时,字段那部分不会被反转。
谁给了第二道门提示
各种过滤器装上后,LevelFilter::current() 是多少:
use tracing_subscriber::filter::{FilterExt, LevelFilter, Targets, filter_fn};
use tracing_subscriber::layer::Identity;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{EnvFilter, Registry};
fn show<L>(label: &str, layer: L)
where
L: tracing_subscriber::Layer<Registry> + Send + Sync + 'static,
{
let sub = Registry::default().with(layer);
tracing::subscriber::with_default(sub, || {
println!("{label:<52} LevelFilter::current() = {}", LevelFilter::current());
});
}
fn main() {
let is_app = |m: &tracing::Metadata<'_>| m.target().starts_with("app");
// 当全局过滤用:filter 自己就是一层
show("全局 LevelFilter::INFO", LevelFilter::INFO);
show("全局 Targets(app=debug,warn)", "app=debug,warn".parse::<Targets>().unwrap());
show("全局 filter_fn(..)", filter_fn(is_app));
show(
"全局 filter_fn(..).with_max_level_hint(INFO)",
filter_fn(is_app).with_max_level_hint(LevelFilter::INFO),
);
show("全局 EnvFilter::new(\"info\")", EnvFilter::new("info"));
show("全局 EnvFilter::new(\"\")", EnvFilter::new(""));
show("全局 EnvFilter::try_new(\"\")", EnvFilter::try_new("").unwrap());
show("全局 EnvFilter::new(\"info,[req]=debug\")", EnvFilter::new("info,[req]=debug"));
show("全局 EnvFilter::new(\"info,[{id=1}]=debug\")", EnvFilter::new("info,[{id=1}]=debug"));
// 当每层过滤用
show("每层 INFO", Identity::new().with_filter(LevelFilter::INFO));
show(
"每层 INFO + 每层 DEBUG",
Identity::new()
.with_filter(LevelFilter::INFO)
.and_then(Identity::new().with_filter(LevelFilter::DEBUG)),
);
show(
"每层 INFO.and(filter_fn)",
Identity::new().with_filter(LevelFilter::INFO.and(filter_fn(is_app))),
);
show(
"每层 INFO.or(filter_fn)",
Identity::new().with_filter(LevelFilter::INFO.or(filter_fn(is_app))),
);
show(
"每层 WARN.not()",
Identity::new().with_filter(LevelFilter::WARN.not()),
);
}
跑 3 次,输出相同:
全局 LevelFilter::INFO LevelFilter::current() = info
全局 Targets(app=debug,warn) LevelFilter::current() = debug
全局 filter_fn(..) LevelFilter::current() = trace
全局 filter_fn(..).with_max_level_hint(INFO) LevelFilter::current() = info
全局 EnvFilter::new("info") LevelFilter::current() = info
全局 EnvFilter::new("") LevelFilter::current() = error
全局 EnvFilter::try_new("") LevelFilter::current() = off
全局 EnvFilter::new("info,[req]=debug") LevelFilter::current() = debug
全局 EnvFilter::new("info,[{id=1}]=debug") LevelFilter::current() = trace
每层 INFO LevelFilter::current() = info
每层 INFO + 每层 DEBUG LevelFilter::current() = debug
每层 INFO.and(filter_fn) LevelFilter::current() = trace
每层 INFO.or(filter_fn) LevelFilter::current() = trace
每层 WARN.not() LevelFilter::current() = trace
(中文字符宽度不同,列没有对齐,按原样贴出。)filter_fn 不给提示,就算和 LevelFilter::INFO 用 and 组合,结果也是 trace:And 的 cmp::min(Some(INFO), None) 是 None。逻辑上 INFO && 任意条件 不可能放行 DEBUG,但实现没有利用这一点。not 同样让上限消失。上限是 trace 时,调用点照样能被 Interest 缓存挡住(filter_fn 给 never),损失的只是第二道门那一次原子读;但对 sometimes 的过滤器,每条被关掉的低级别日志都要走一遍 enabled。EnvFilter 的几行在 EnvFilter 解释。
还是开头那个程序的 layer 和 emit,main 换成:
fn main() {
println!("== 全局 INFO + file 层自己的 DEBUG");
let sub = Registry::default()
.with(LevelFilter::INFO)
.with(layer("stdout"))
.with(layer("file").with_filter(LevelFilter::DEBUG));
tracing::subscriber::with_default(sub, emit);
}
M2 Max、rustc 1.99.0、--release。[file] 开头的行有几行?
动脑筋答案
== 全局 INFO + file 层自己的 DEBUG
[stdout] INFO info event
[file] INFO info event
跑 3 次,都是 1 行,file 没有收到 debug event。
max_level_hint:外层file的过滤器提示DEBUG,内层(stdout加全局INFO)提示INFO,取最大值DEBUG。在同一个 subscriber 里打印LevelFilter::current(),得到的是debug,第二道门放行debug!。register_callsite:最外层Filtered先把file的意见always记进线程局部的汇总,对外返回always;pick_interest因为本层是Filtered,直接用内层的答案。内层里的全局LevelFilter::INFO对debug!回答never,它所在的Layered短路返回never,同时调用take_interest把汇总清掉,file的意见就此作废。调用点缓存成never,enabled都不会再被问。
全局过滤和每层过滤叠在一起时,是「与」的关系:每层过滤只能在全局放行的范围内再收窄,不能放宽。想让某一层看到比别的层更多的东西,全局那层的级别就得放到最宽,其余的限制都改成每层过滤。
小结
- 全局过滤是一个实现了
Layer的过滤器,用.with(filter)叠上去;它在register_callsite说never、在enabled说false,整个栈短路,所有层都看不到,.with的位置不影响这一点。fmt().with_env_filter(..)属于这一种。 - 每层过滤用
layer.with_filter(filter),得到Filtered。每个Filtered在on_layer时从Registry领一个FilterId,是1 << n的掩码,第 65 个会 panic「filter IDs may not be greater than 64」。只有Registry能分配FilterId。 Filtered::enabled把过滤器的意见写进线程局部FilterMap(位为 1 表示不要),对外返回true;on_event前查自己那一位,跳过时把位清掉。span 的FilterMap在new_span时存进DataInner,之后的回调和 span 遍历都按它跳过。Registry::enabled只有在 64 位全为 1 时才返回false;过滤器不满 64 个时,sometimes调用点上所有层都不要的事件仍会被构造、分发、再被逐层跳过。- 各过滤器的
callsite_enabled合并规则是「相同保留,不同为sometimes」;意见不一的调用点每次都要问所有过滤器。不带过滤、不给提示的普通层会让第二道门失效,并把never改成sometimes。 LevelFilter、Targets、filter_fn都能在缓存阶段给出always/never;dynamic_filter_fn默认sometimes。filter_fn和not、以及含它们的and/or没有max_level_hint。- 全局与每层叠加是「与」:每层过滤只能在全局放行的范围内收窄。