Layer 与 Layered
主线位置:两条主线的最后一段。事件主线上,
register_callsite、enabled、event_enabled这几道门由最外层的Layered从外往里逐层问,任何一层说「不要」就停;门都开了,Layered::event先把事件交给内层,再调本层的on_event,最里面的Registry什么都不做,各层按从里到外的顺序收到事件。span 主线上,new_span一路传到最里面的Registry拿到Id,各层的on_new_span、on_enter、on_exit、on_close同样从里往外收到通知。
flowchart TB disp["Dispatch"] --> lc["Layered:层 C"] lc -->|inner| lb["Layered:层 B"] lb -->|inner| la["Layered:层 A"] la -->|inner| reg["Registry:发 Id"] ask["判断:C、B、A、Registry"] -.-> lc notify["通知:Registry、A、B、C"] -.-> reg
tracing_subscriber::registry().with(A).with(B).with(C) 得到的类型是 Layered<C, Layered<B, Layered<A, Registry>>>:一层套一层,最后加的在最外面。这一页先讲 Layer 为什么存在、它和 Subscriber 的方法怎么对应,再看 Layered 怎么把一次调用分给内外两层,最后讲 Interest 和最大级别的合并规则,以及 Option、Vec、Box 这几种「容器 layer」。Subscriber 本身的方法见 Subscriber trait,Registry 怎么存 span 见 Registry。
为什么要有 Layer
layer 模块文档开头就讲了这件事。Subscriber 是「一整套」收集策略,其中包括给 span 发 Id:
In particular,
Subscribers are responsible for generating span IDs and assigning them to spans. Since these IDs must uniquely identify a span within the context of the current trace, this means that there may only be a singleSubscriberfor a given thread at any point in time — otherwise, there would be no authoritative source of span IDs.
Id 只能有一个来源,所以同一时刻一个线程只能有一个 Subscriber。但 Subscriber 的其余功能是可以叠加的:多个观察者都可以看事件、看 span 进出,只要 Id 有唯一的来源。Layer 就是这一部分:「it can observe events and spans, but does not assign IDs」。于是 tracing-subscriber 的分工是:
| 角色 | 谁来做 |
|---|---|
发 Id、存 span 的元数据和父子关系、记当前 span | Registry(一个 Subscriber) |
| 过滤 | EnvFilter、LevelFilter、Targets 等 layer |
| 输出 | fmt::Layer,或者自己写的 layer |
| 把它们拼起来 | Layered,它本身实现 Subscriber |
Registry 是挂号处:每个病人(span)来了先发一个号(Id),病历(DataInner)也存在这里。各个 layer 是诊室,只认号、不发号,需要病历就拿号去挂号处查(ctx.span(id))。病人进门之前,从最外面的诊室开始依次问「这个病人你们接不接」(enabled),有一个说不接就不用再往里问;接诊以后,挂号处先登记(Registry::enter 压栈),各诊室再按从里到外的顺序收到通知(on_enter)。
比方到这里为止:真实的 Layered 不是一排并列的诊室,而是嵌套的类型,每一层只知道自己的 inner;而且这里的「挂号处」也会被问「接不接」,排在最后。
Layer trait:方法和 Subscriber 一一对应
去掉文档和默认方法体后,Layer 是这样:
pub trait Layer<S>
where
S: Subscriber,
Self: 'static,
{
fn on_register_dispatch(&self, subscriber: &Dispatch) { /* ... */ }
fn on_layer(&mut self, subscriber: &mut S) { /* ... */ }
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { /* ... */ }
fn enabled(&self, metadata: &Metadata<'_>, ctx: Context<'_, S>) -> bool { /* ... */ }
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) { /* ... */ }
#[doc(hidden)]
fn max_level_hint(&self) -> Option<LevelFilter> { /* ... */ }
fn on_record(&self, _span: &span::Id, _values: &span::Record<'_>, _ctx: Context<'_, S>) {}
fn on_follows_from(&self, _span: &span::Id, _follows: &span::Id, _ctx: Context<'_, S>) {}
fn event_enabled(&self, _event: &Event<'_>, _ctx: Context<'_, S>) -> bool { /* ... */ }
fn on_event(&self, _event: &Event<'_>, _ctx: Context<'_, S>) {}
fn on_enter(&self, _id: &span::Id, _ctx: Context<'_, S>) {}
fn on_exit(&self, _id: &span::Id, _ctx: Context<'_, S>) {}
fn on_close(&self, _id: span::Id, _ctx: Context<'_, S>) {}
fn on_id_change(&self, _old: &span::Id, _new: &span::Id, _ctx: Context<'_, S>) {}
// ... and_then、with_subscriber、with_filter、boxed 等组合方法
#[doc(hidden)]
unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> { /* ... */ }
}
和 Subscriber 比,有三处不同:
- 全部方法都有默认实现。
Subscriber必须实现 7 个方法,Layer一个都不用写:impl<S: Subscriber> Layer<S> for MyLayer {}就能编译,它什么都不做。enabled默认返回true;默认的register_callsite和Subscriber的一样,把enabled的答案冻结成always/never,只是传进去的是Context::none()。 - 没有发
Id的方法。 没有返回Id的new_span,只有拿到Id之后的通知on_new_span;没有clone_span、try_close、current_span。引用计数和当前 span 都是Registry的事。 - 每个通知都多一个
Context,用来向内层查 span 数据,下一节讲。
类型参数 S 是这个 layer 套在什么 Subscriber 外面。写 impl<S: Subscriber> Layer<S> 的 layer 能套在任何东西外面;需要查 span 数据的 layer 写 S: Subscriber + for<'a> LookupSpan<'a>,就只能套在 Registry(或别的实现了 LookupSpan 的 subscriber)外面。
Layered 实现 Subscriber 时,每个方法对应到 Layer 的哪个方法、内外两层按什么顺序调用(impl Subscriber for Layered):
Subscriber 方法 | 本层调用 | 顺序 |
|---|---|---|
on_register_dispatch | on_register_dispatch | 先内层,再本层 |
register_callsite | register_callsite | 先本层;本层不是 never 才问内层,用 pick_interest 合并 |
enabled | enabled | 先本层;本层 false 就返回,不问内层 |
max_level_hint | max_level_hint | 两边都问,用 pick_level_hint 合并 |
new_span | on_new_span | 先内层(拿到 Id),再本层 |
record | on_record | 先内层,再本层 |
record_follows_from | on_follows_from | 先内层,再本层 |
event_enabled | event_enabled | 先本层;本层 false 就返回 |
event | on_event | 先内层,再本层 |
enter / exit | on_enter / on_exit | 先内层,再本层 |
clone_span | on_id_change | 先内层;内层返回的 Id 变了才通知本层 |
try_close | on_close | 先内层;内层返回 true 才通知本层 |
current_span | — | 只问内层 |
downcast_raw | downcast_raw | 自己、本层、内层依次试 |
规律只有一条:判断从外往里问,通知从里往外发。 Registry 的 clone_span 永远返回同一个 Id,所以套在 Registry 外面的 layer 收不到 on_id_change。
Context:layer 看到的是它里面的那一截
Context 只有两个字段:
pub struct Context<'a, S> {
subscriber: Option<&'a S>,
// ...
#[cfg(all(feature = "registry", feature = "std"))]
filter: FilterId,
}
subscriber 指向哪里,看 Layered::ctx:
fn ctx(&self) -> Context<'_, S> {
Context::new(&self.inner)
}
每一层拿到的 Context 包着的是它自己的 inner,不是整个栈。层 B 的 ctx 指向 Layered<A, Registry>,看不到外面的 C。查 span 数据的请求顺着 inner 一路落到 Registry(Layered 的 LookupSpan 只是转给 inner),所以哪一层查到的都是同一份数据。
常用的方法:
| 方法 | 做什么 |
|---|---|
span(id) | 按 Id 查 span,返回 SpanRef |
lookup_current() | 当前线程的当前 span |
event_span(event) | 事件的父 span:按 is_root / is_contextual / parent() 三选一 |
event_scope(event) / span_scope(id) | 从某个 span 往根走的迭代器 |
enabled(meta) | 问内层要不要这个元数据 |
除了 current_span、enabled、event,这些方法都要求 S: for<'lookup> LookupSpan<'lookup>。SpanRef、scope() 怎么实现见 Registry。filter 字段在用了每层过滤时记下「这一层被哪些过滤器管着」,让上面这些查询跳过本层看不到的 span,见 过滤:全局与每层。
register_callsite 的默认实现传的是 Context::none(),subscriber 是 None。注册调用点是对整个程序的一次性判断,没有「当前 span」可言;这时 ctx.enabled(..) 返回 true,源码注释说这是为了「so that the layer does not incorrectly assume that the inner subscriber has disabled this metadata」。
Layered:一层套一层
with 做了什么
SubscriberExt::with 对所有 Subscriber 都可用,它只是反过来调 layer 的 with_subscriber:
fn with<L>(self, layer: L) -> Layered<L, Self>
where
L: Layer<Self>,
Self: Sized,
{
layer.with_subscriber(self)
}
fn with_subscriber(mut self, mut inner: S) -> Layered<Self, S>
where
Self: Sized,
{
let inner_has_layer_filter = filter::subscriber_has_plf(&inner);
self.on_layer(&mut inner);
Layered::new(self, inner, inner_has_layer_filter)
}
两件事。一是调 on_layer(&mut inner):layer 在被套上去的那一刻拿到内层的 &mut,这是唯一一次能改内层的机会;每层过滤的 Filtered 就在这里向 Registry 要一个 FilterId。二是先算出内层里有没有每层过滤,交给 Layered::new 存起来,下面合并 Interest 时用。
L: Layer<Self> 这个约束决定了能套什么:registry().with(A) 要求 A: Layer<Registry>,再 .with(B) 要求 B: Layer<Layered<A, Registry>>。写成 impl<S: Subscriber> Layer<S> 的 layer 两处都满足。
结构
Layered(去掉注释):
#[derive(Clone)]
pub struct Layered<L, I, S = I> {
layer: L,
inner: I,
inner_is_registry: bool,
has_layer_filter: bool,
inner_has_layer_filter: bool,
_s: PhantomData<fn(S)>,
}
三个类型参数:L 是本层,I 是内层,S 是最终的 Subscriber。用 with 叠出来的 Layered 里 I 和 S 相同(默认参数 S = I),这时 Layered 实现 Subscriber。另一种叠法是 Layer::and_then:两个 layer 先拼成一个 Layered<A, B, S>,此时 I 是 layer、不是 subscriber,这个 Layered 实现的是 Layer<S>,可以整体再 with 到别的 subscriber 上。两个 impl 的转发顺序基本一样,有一处不同:Layer 版本的 on_register_dispatch 先本层后内层。
三个布尔值在 Layered::new 里一次算好:
let inner_is_registry = TypeId::of::<S>() == TypeId::of::<crate::registry::Registry>();
// ...
let inner_has_layer_filter = inner_has_layer_filter || inner_is_registry;
let has_layer_filter = filter::layer_has_plf(&layer);
TypeId::of::<S>() 是 Registry 只有一种情况:这是紧贴着 Registry 的那一层。注意 inner_is_registry 会让 inner_has_layer_filter 也为真,原因在合并 Interest 那一节。
转发:判断短路,通知先内后外
判断方法,以 enabled 为例:
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
// ...
#[cfg(feature = "registry")]
filter::FilterState::clear_enabled();
false
}
}
本层说 false,内层根本不会被问到。event_enabled 同样短路。clear_enabled 是每层过滤的状态清理,见 过滤:全局与每层。
fn new_span(&self, span: &span::Attributes<'_>) -> span::Id {
let id = self.inner.new_span(span);
self.layer.on_new_span(span, &id, self.ctx());
id
}
// ...
fn event(&self, event: &Event<'_>) {
self.inner.event(event);
self.layer.on_event(event, self.ctx());
}
new_span 必须先内层:Id 是最里面的 Registry 发的,本层的 on_new_span 要拿着这个 Id,而且常常要立刻 ctx.span(id) 去往 span 的 Extensions 里存东西(fmt::Layer 就是这样),这要求 Registry 已经把 span 存好。其余通知照同样的顺序写,于是所有通知都是从里往外。
try_close 多了一步:
fn try_close(&self, id: span::Id) -> bool {
#[cfg(all(feature = "registry", feature = "std"))]
let subscriber = &self.inner as &dyn Subscriber;
#[cfg(all(feature = "registry", feature = "std"))]
let mut guard = subscriber
.downcast_ref::<Registry>()
.map(|registry| registry.start_close(id.clone()));
if self.inner.try_close(id.clone()) {
// ...
#[cfg(all(feature = "registry", feature = "std"))]
{
if let Some(g) = guard.as_mut() {
g.set_closing()
};
}
self.layer.on_close(id, self.ctx());
true
} else {
false
}
}
内层的 try_close 返回 true(引用计数归零)才调本层的 on_close。downcast_ref::<Registry>() 拿到的 CloseGuard 是为了让 span 的槽在所有层的 on_close 都跑完之后才释放,具体机制见 Registry 的 try_close 与 CloseGuard。
实测:三层的回调顺序
三个只打印回调名的 layer A、B、C,依次 with 上去。为了让 enabled 每次都被问到,register_callsite 返回 sometimes。
tracing = "=0.1.44"
tracing-core = "=0.1.36"
tracing-subscriber = "=0.3.23"
use tracing::span::{Attributes, Id, Record};
use tracing::{Dispatch, Event, Metadata, Subscriber};
use tracing_core::Interest;
use tracing_subscriber::layer::{Context, Layer, SubscriberExt};
// 一个只打印回调名字的 layer,名字用来区分 A、B、C
struct P(&'static str);
impl<S: Subscriber> Layer<S> for P {
fn on_register_dispatch(&self, _: &Dispatch) {
println!(" {} on_register_dispatch", self.0);
}
fn register_callsite(&self, meta: &'static Metadata<'static>) -> Interest {
println!(" {} register_callsite {}", self.0, meta.name());
Interest::sometimes() // 让 enabled 每次都被问到
}
fn enabled(&self, _: &Metadata<'_>, _: Context<'_, S>) -> bool {
println!(" {} enabled", self.0);
true
}
fn on_new_span(&self, _: &Attributes<'_>, id: &Id, _: Context<'_, S>) {
println!(" {} on_new_span {:?}", self.0, id);
}
fn on_record(&self, _: &Id, _: &Record<'_>, _: Context<'_, S>) {
println!(" {} on_record", self.0);
}
fn on_enter(&self, _: &Id, _: Context<'_, S>) {
println!(" {} on_enter", self.0);
}
fn event_enabled(&self, _: &Event<'_>, _: Context<'_, S>) -> bool {
println!(" {} event_enabled", self.0);
true
}
fn on_event(&self, _: &Event<'_>, _: Context<'_, S>) {
println!(" {} on_event", self.0);
}
fn on_exit(&self, _: &Id, _: Context<'_, S>) {
println!(" {} on_exit", self.0);
}
fn on_close(&self, _: Id, _: Context<'_, S>) {
println!(" {} on_close", self.0);
}
}
fn main() {
// Layered<C, Layered<B, Layered<A, Registry>>>
let sub = tracing_subscriber::registry()
.with(P("A"))
.with(P("B"))
.with(P("C"));
println!("-- Dispatch::new");
let dispatch = Dispatch::new(sub);
tracing::dispatcher::with_default(&dispatch, || {
println!("-- info_span!");
let span = tracing::info_span!("req", id = tracing::field::Empty);
println!("-- record");
span.record("id", 7);
println!("-- enter");
let guard = span.enter();
println!("-- info!");
tracing::info!("hello");
println!("-- exit");
drop(guard);
println!("-- drop(span)");
drop(span);
});
}
M2 Max、rustc 1.99.0、--release,连跑 3 次输出相同:
-- Dispatch::new
A on_register_dispatch
B on_register_dispatch
C on_register_dispatch
-- info_span!
C register_callsite req
B register_callsite req
A register_callsite req
C enabled
B enabled
A enabled
A on_new_span Id(1)
B on_new_span Id(1)
C on_new_span Id(1)
-- record
A on_record
B on_record
C on_record
-- enter
A on_enter
B on_enter
C on_enter
-- info!
C register_callsite event src/bin/order.rs:61
B register_callsite event src/bin/order.rs:61
A register_callsite event src/bin/order.rs:61
C enabled
B enabled
A enabled
C event_enabled
B event_enabled
A event_enabled
A on_event
B on_event
C on_event
-- exit
A on_exit
B on_exit
C on_exit
-- drop(span)
A on_close
B on_close
C on_close
register_callsite、enabled、event_enabled都是 C、B、A:最外层先问。- 所有通知都是 A、B、C:最里面的 layer 先收到。
on_close也一样,因为最外层的try_close先递归到最里面,Registry返回true后从 A 开始逐层返回、逐层调on_close。 on_register_dispatch在Dispatch::new里调用,顺序是 A、B、C(先内层)。- 没有任何一行来自
Registry:它的event、record是空函数,enter/exit只改 span 栈,不打印。
问: 我想写一个 layer,看到某些事件就不让外面的 layer 输出。在 on_event 里能做到吗?
答: 做不到。on_event 没有返回值,而且从上面的输出看,内层的 on_event 先跑,跑完 Layered 无条件调外层的 on_event,中间没有任何检查。要拦住事件只能在判断方法里做:enabled 只看元数据,event_enabled 能看到字段值;它们从外往里问,任何一层返回 false,整个栈都看不到这条事件。这正是 Layer::event_enabled 被加进来的原因(见 Subscriber trait 的冷知识)。只想让「自己这一层」看不到,而不影响别的层,用每层过滤 with_filter。
register_callsite 的合并:pick_interest
enabled 的合并是简单的「全部同意」,register_callsite 要合并三种值,规则在 pick_interest:
fn pick_interest(&self, outer: Interest, inner: impl FnOnce() -> Interest) -> Interest {
if self.has_layer_filter {
return inner();
}
// If the outer layer has disabled the callsite, return now so that
// the inner layer/subscriber doesn't get its hopes up.
if outer.is_never() {
// ...
#[cfg(feature = "registry")]
filter::FilterState::take_interest();
return outer;
}
// The `inner` closure will call `inner.register_callsite()`. We do this
// before the `if` statement to ensure that the inner subscriber is
// informed that the callsite exists regardless of the outer layer's
// filtering decision.
let inner = inner();
if outer.is_sometimes() {
// if this interest is "sometimes", return "sometimes" to ensure that
// filters are reevaluated.
return outer;
}
// If there is a per-layer filter in the `inner` stack, and it returns
// `never`, change the interest to `sometimes`, because the `outer`
// layer didn't return `never`. This means that _some_ layer still wants
// ...
if inner.is_never() && self.inner_has_layer_filter {
return Interest::sometimes();
}
// otherwise, allow the inner subscriber or subscriber to weigh in.
inner
}
内层的答案以闭包传入,所以外层说 never 时内层的 register_callsite 根本不会被调用。不考虑每层过滤(has_layer_filter 为假)时,规则是:
- 外层
never→never,不问内层。 - 外层
sometimes→ 先让内层注册一下,结果固定是sometimes。 - 外层
always→ 用内层的答案。
用一个对任何调用点都回答固定值的 layer,把 3×3 种组合都跑一遍。为了直接调用 register_callsite,手写了一个静态调用点拿到 &'static Metadata:
use tracing::{Metadata, Subscriber};
use tracing_core::callsite::Callsite;
use tracing_core::metadata::Kind;
use tracing_core::{Interest, Level};
use tracing_subscriber::layer::{Layer, SubscriberExt};
// 手写一个静态调用点,拿到 &'static Metadata 直接喂给 register_callsite
struct Cs;
static CS: Cs = Cs;
impl Callsite for Cs {
fn set_interest(&self, _: Interest) {}
fn metadata(&self) -> &Metadata<'_> {
&META
}
}
static META: Metadata<'static> = tracing_core::metadata! {
name: "demo",
target: "demo",
level: Level::INFO,
fields: &[],
callsite: &CS,
kind: Kind::EVENT,
};
// 对任何调用点都回答同一个 Interest 的 layer
struct Fixed(&'static str, fn() -> Interest);
impl<S: Subscriber> Layer<S> for Fixed {
fn register_callsite(&self, _: &'static Metadata<'static>) -> Interest {
print!(" {}问过", self.0);
(self.1)()
}
}
fn name(i: &Interest) -> &'static str {
if i.is_always() {
"always"
} else if i.is_sometimes() {
"sometimes"
} else {
"never"
}
}
fn main() {
let all: [(&str, fn() -> Interest); 3] = [
("never", Interest::never),
("sometimes", Interest::sometimes),
("always", Interest::always),
];
for (on, o) in all {
for (inn, i) in all {
// Layered<外层, Layered<内层, Registry>>
let stack = tracing_subscriber::registry()
.with(Fixed("内", i))
.with(Fixed("外", o));
print!("外 {on:<9} 内 {inn:<9} |");
let got = stack.register_callsite(&META);
println!(" => {}", name(&got));
}
}
}
输出(连跑 3 次相同):
外 never 内 never | 外问过 => never
外 never 内 sometimes | 外问过 => never
外 never 内 always | 外问过 => never
外 sometimes 内 never | 外问过 内问过 => sometimes
外 sometimes 内 sometimes | 外问过 内问过 => sometimes
外 sometimes 内 always | 外问过 内问过 => sometimes
外 always 内 never | 外问过 内问过 => never
外 always 内 sometimes | 外问过 内问过 => sometimes
外 always 内 always | 外问过 内问过 => always
前三行内层没被问到;其余六行和上面三条规则一致。Registry 的 register_callsite 在没有每层过滤时总是返回 always,所以 Layered<内层, Registry> 的结果就是内层自己的答案,外面那一层拿到的「内层」也就是 Fixed("内", ..) 的值。
第 4 行值得多看一眼:内层说了 never,合并结果却是 sometimes。调用点缓存的是合并后的 sometimes,之后每次都会走到 enabled,而内层这个 layer 没有覆盖 enabled(默认 true)。
内层 layer 的 register_callsite 返回 never,没有覆盖 enabled;外层 layer 返回 sometimes。两层都在 on_event 里打印一行:
use tracing::{Event, Metadata, Subscriber};
use tracing_core::Interest;
use tracing_subscriber::layer::{Context, Layer, SubscriberExt};
// 内层:register_callsite 说 never,但没有覆盖 enabled(默认 true)
struct Inner;
impl<S: Subscriber> Layer<S> for Inner {
fn register_callsite(&self, _: &'static Metadata<'static>) -> Interest {
Interest::never()
}
fn on_event(&self, _: &Event<'_>, _: Context<'_, S>) {
println!("Inner on_event");
}
}
// 外层:说 sometimes
struct Outer;
impl<S: Subscriber> Layer<S> for Outer {
fn register_callsite(&self, _: &'static Metadata<'static>) -> Interest {
Interest::sometimes()
}
fn on_event(&self, _: &Event<'_>, _: Context<'_, S>) {
println!("Outer on_event");
}
}
fn main() {
let sub = tracing_subscriber::registry().with(Inner).with(Outer);
tracing::subscriber::with_default(sub, || {
tracing::info!("hello");
});
}
M2 Max、rustc 1.99.0、--release。程序打印一行还是两行?
每层过滤让规则多出两条
has_layer_filter 和 inner_has_layer_filter 只在用了每层过滤(layer.with_filter(..),类型是 Filtered)时才可能为真。Filtered 的 register_callsite 把自己过滤器的答案记进一个线程局部变量,对外永远返回 always,源码注释说如果它返回真实的 never,「a Layered layer would short-circuit and not allow any Filtered layers below us if they are interested in the callsite」。所有 Filtered 的答案在线程局部里汇总,最后由 Registry::register_callsite 取出来当作自己的答案返回。于是:
- 本层就是
Filtered(has_layer_filter):它返回的always不代表真实意见,直接用内层的答案。 - 内层含每层过滤、汇总后是
never,而本层不是never(inner_has_layer_filter):说明那些带过滤器的层都不要,但本层要,改成sometimes,让enabled每次都被调用,带过滤器的层在那里被跳过,本层照常收到。 Registry返回的正是那个汇总值,所以紧贴Registry的那一层要把它当作「内层含每层过滤」处理:这就是Layered::new里inner_is_registry会让inner_has_layer_filter为真的原因。
「某个 layer 是否带每层过滤」在类型擦除之后是用 downcast_raw 问出来的:layer_has_plf 拿一个私有的标记类型 MagicPlfDowncastMarker 的 TypeId 去 downcast,只有 Filtered 会认领它。它的文档自己说这是「a horrible and bad abuse of the downcasting system」,为的是不在公开 API 里暴露这个信息。过滤的完整机制在 过滤:全局与每层。
max_level_hint 的合并
pick_level_hint 决定整个栈报给 tracing-core 的最大级别,也就是第二道门 LevelFilter::current() 的值。去掉每层过滤的分支后:
if self.inner_is_registry {
return outer_hint;
}
// ...
if super::layer_is_none(&self.layer) {
return cmp::max(outer_hint, Some(inner_hint?));
}
// Similarly, if the layer on the inside is `None` and it returned an
// `Off` hint, we want to override that with the outer hint.
if inner_is_none && inner_hint == Some(LevelFilter::OFF) {
return outer_hint;
}
cmp::max(outer_hint, inner_hint)
Option 的 cmp::max 里 None < Some(_),而这里的 None 表示「没有意见」,按 TRACE 处理;所以只要有一层(比如 fmt::Layer)不给提示,整个栈就是 None,第二道门什么都挡不住。紧贴 Registry 的那一层直接用本层的提示,因为 Registry 自己不提示。剩下两个分支是给 Option::None 这种 layer 的,下一节讲。
Option、Vec、Box:容器也是 Layer
Option<L>
impl Layer for Option<L>:Some 时逐个方法转给里面的 layer;None 时 register_callsite 返回 always、enabled 返回 true,通知都是空操作,等于这一层不存在。它的用途是按配置开关某一层而不改变整个栈的类型:开和关都是 Layered<Option<L>, ..>。
唯一的例外是 max_level_hint:None 返回 Some(LevelFilter::OFF),理由写在注释里:「There is no inner layer, so this layer will never enable anything.」这个 OFF 只在「整个栈只有它」时才该生效,否则一个关掉的可选 layer 会把全局最大级别拉成 OFF。所以 Option 的 downcast_raw 在 None 时认领一个私有标记 NoneLayerMarker,pick_level_hint 通过 layer_is_none 认出它,跳过它的 OFF(上一节代码里的两个分支)。
Vec<L>
impl Layer for Vec<L> 让 layer 的数量在运行时决定(类型必须相同,通常是 Vec<Box<dyn Layer<S> + Send + Sync>>)。通知按下标顺序逐个转发;三个合并规则各不相同:
| 方法 | 合并 |
|---|---|
register_callsite | 取最大:always 高于 sometimes 高于 never,空 Vec 是 never |
enabled / event_enabled | 全部同意:self.iter().all(..) |
max_level_hint | 有一个 None 就是 None,否则取最大;空 Vec 是 OFF |
register_callsite 取最大而 enabled 要全部同意,两者并不一致。把两个过滤器放进一个 Vec:
use tracing::{Event, Subscriber};
use tracing_core::LevelFilter;
use tracing_subscriber::layer::{Context, Layer, SubscriberExt};
struct Print;
impl<S: Subscriber> Layer<S> for Print {
fn on_event(&self, e: &Event<'_>, _: Context<'_, S>) {
println!("Print 收到 {}", e.metadata().level());
}
}
fn main() {
let sub = tracing_subscriber::registry()
.with(Print)
.with(vec![LevelFilter::INFO, LevelFilter::DEBUG]);
tracing::subscriber::with_default(sub, || {
tracing::info!("i");
tracing::debug!("d");
tracing::trace!("t");
});
}
Print 收到 INFO
Print 收到 DEBUG
debug! 的调用点注册时,INFO 说 never、DEBUG 说 always,取最大得 always 并缓存下来,此后不再问 enabled,INFO 那个「全部同意」的否决权根本没机会行使。Vec 里放过滤器,效果是「或」,不是「与」。
Box<L> 与 Box<dyn Layer>
Box<L> 和 Box<dyn Layer<S> + Send + Sync> 共用一个宏 layer_impl_body!,每个方法都转给 deref()。Layer::boxed() 就是 Box::new(self),用来把类型不同的 layer 装进同一个 Vec,或者在 if / else 两个分支里返回同一个类型。
谁转发 on_register_dispatch
逐个看这几个 impl 会发现,Option<L> 和 Vec<L> 都没有写 on_register_dispatch,用的是 Layer 的默认实现(什么都不做);Box 的宏里有。把同一个 layer 用四种方式装进去,看谁收得到,顺便打印装上之后的全局最大级别:
use tracing::{Dispatch, Subscriber};
use tracing_core::LevelFilter;
use tracing_subscriber::layer::{Layer, SubscriberExt};
use tracing_subscriber::registry;
struct Hello(&'static str);
impl<S: Subscriber> Layer<S> for Hello {
fn on_register_dispatch(&self, _: &Dispatch) {
println!(" {} 收到 on_register_dispatch", self.0);
}
}
// 装进 Dispatch,打印此时的全局最大级别
fn show<S: Subscriber + Send + Sync>(label: &str, sub: S) {
println!("{label}");
let d = Dispatch::new(sub);
println!(" LevelFilter::current() = {}", LevelFilter::current());
drop(d);
}
fn main() {
show("L", registry().with(Hello("L")));
show("Box<L>", registry().with(Box::new(Hello("Box"))));
show("Some(L)", registry().with(Some(Hello("Some"))));
show("vec![L]", registry().with(vec![Hello("Vec")]));
show("None", registry().with(None::<Hello>));
show("INFO + None", registry().with(LevelFilter::INFO).with(None::<Hello>));
show("L + None", registry().with(Hello("L")).with(None::<Hello>));
show("vec![INFO, DEBUG]", registry().with(vec![LevelFilter::INFO, LevelFilter::DEBUG]));
show("vec![]", registry().with(Vec::<LevelFilter>::new()));
}
L
L 收到 on_register_dispatch
LevelFilter::current() = trace
Box<L>
Box 收到 on_register_dispatch
LevelFilter::current() = trace
Some(L)
LevelFilter::current() = trace
vec![L]
LevelFilter::current() = trace
None
LevelFilter::current() = off
INFO + None
LevelFilter::current() = info
L + None
L 收到 on_register_dispatch
LevelFilter::current() = trace
vec![INFO, DEBUG]
LevelFilter::current() = debug
vec![]
LevelFilter::current() = off
连跑 3 次相同。Some(L) 和 vec![L] 里的 layer 收不到 on_register_dispatch;需要在装上时拿到 Dispatch 的 layer(比如要把它交给后台线程),不要包进 Option 或 Vec。最大级别那一列:只有一个 None 的栈是 off,这时确实没有任何 layer 在工作;INFO + None 是 info,None 的 OFF 被跳过了;L + None 是 trace,因为 L 没有提示(None 当 TRACE)。
Layered 直到 0.3.21 才转发 on_register_dispatchSubscriber::on_register_dispatch 和 Layer::on_register_dispatch 是 PR #2269 一起加的,tracing-subscriber 0.3.16 起就有。但 Layered 的 Subscriber impl 一直没有覆盖这个方法,用的是 Subscriber 的空默认实现,于是 registry().with(layer) 里的 layer 从来收不到它。PR #3379 的描述是:「The Layered implementation of Subscriber does not implement and propagate the on_register_dispatch callback. This means that combined layers will never have their on_register_dispatch methods called.」修法就是本页表格里那 4 行:先内层、再本层。它首次出现在 tracing-subscriber 0.3.21,CHANGELOG 记作「Make Layered propagate on_register_dispatch」;0.3.21 因为依赖被撤回的 tracing 0.1.42 而被 yank,实际可用的第一个版本是 0.3.22。上面的实验说明,到 0.3.23 为止 Option<L> 和 Vec<L> 还有同样的缺口。
downcast_raw:在层层包装里找东西
Layered 作为 Subscriber 的 downcast_raw 先认自己,再问本层,再问内层,Subscriber trait 一页已经讲过它怎么让 downcast_ref::<Registry>() 穿过任意多层。tracing-subscriber 自己用它做三件事:
| 用途 | 标记类型 | 谁认领 |
|---|---|---|
关闭 span 时找 Registry 拿 CloseGuard | Registry | Registry 本身 |
| 判断某一层带不带每层过滤 | MagicPlfDowncastMarker | Filtered |
判断某一层是不是 Option::None | NoneLayerMarker | None 的 Option<L> |
后两个标记的指针从来不会被解引用,downcast_raw 在这里只是一个「穿过类型擦除问是或否」的通道。Layered 作为 Layer 的 downcast_raw 对每层过滤的标记有特殊处理:只有两边都带每层过滤,才算这个组合带每层过滤(.and 而不是 .or_else),Vec 的实现也要求全部元素都带。源码注释承认这条规则「is slightly counter-intuitive」,理由是如果一边带、一边不带也算带,不带过滤的那一边给出的 Interest 会被忽略。
动脑筋答案
连跑 3 次输出相同:
Inner on_event
Outer on_event
两行都打印了。过程是:
- 注册调用点:外层
sometimes,按pick_interest的第 2 条规则,先让内层注册(内层说never),结果固定是sometimes。内层的never被丢掉了。 - 每次经过
info!:sometimes意味着要问enabled。外层的enabled是默认的true,内层也是默认的true,Registry也是true。 event从里往外分发,内层的on_event照常被调用。
register_callsite 返回 never 只是「我对这个调用点的建议」,能不能生效取决于外面的层。想让一个过滤 layer 的判断在任何组合下都成立,register_callsite 和 enabled 要给出一致的答案:默认的 register_callsite 就是调 enabled,所以只覆盖 enabled 的 layer 天然一致,LevelFilter 那样两个都覆盖的也写成了同一个判断。
小结
Subscriber要发Id,所以一个线程同一时刻只能有一个;Layer是去掉发Id的那部分,可以任意叠加。所有方法都有默认实现。with调on_layer让 layer 改一次内层,再用Layered::new记下三个布尔值;registry().with(A).with(B).with(C)的类型是Layered<C, Layered<B, Layered<A, Registry>>>。Layered的判断方法(register_callsite、enabled、event_enabled)从外往里问并短路,通知方法先内层后本层;实测顺序:判断 C、B、A,通知 A、B、C。- 每层的
Context包的是它自己的inner,查 span 最终都落到Registry。 pick_interest:外层never不问内层,外层sometimes一定得sometimes,外层always听内层的;每层过滤另加两条规则。Option::None等于不存在(最大级别的OFF被特殊跳过);Vec的register_callsite取最大、enabled要全体同意,过滤器放进Vec效果是「或」;Option和Vec不转发on_register_dispatch,Layered从 0.3.21 起才转发。