Rust in Everything
基于 tracing-subscriber 0.3.23

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 single Subscriber for 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 的元数据和父子关系、记当前 spanRegistry(一个 Subscriber)
过滤EnvFilter、LevelFilter、Targets 等 layer
输出fmt::Layer,或者自己写的 layer
把它们拼起来Layered,它本身实现 Subscriber
💡TIP
打个比方:医院的挂号处和诊室

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_dispatchon_register_dispatch先内层,再本层
register_callsiteregister_callsite先本层;本层不是 never 才问内层,用 pick_interest 合并
enabledenabled先本层;本层 false 就返回,不问内层
max_level_hintmax_level_hint两边都问,用 pick_level_hint 合并
new_spanon_new_span先内层(拿到 Id),再本层
recordon_record先内层,再本层
record_follows_fromon_follows_from先内层,再本层
event_enabledevent_enabled先本层;本层 false 就返回
eventon_event先内层,再本层
enter / exiton_enter / on_exit先内层,再本层
clone_spanon_id_change先内层;内层返回的 Id 变了才通知本层
try_closeon_close先内层;内层返回 true 才通知本层
current_span—只问内层
downcast_rawdowncast_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)
    }

with_subscriber:

    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 是每层过滤的状态清理,见 过滤:全局与每层。

通知方法,以 new_span 和 event 为例:

    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 栈,不打印。
📝NOTE
没有蠢问题

问: 我想写一个 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 为假)时,规则是:

  1. 外层 never → never,不问内层。
  2. 外层 sometimes → 先让内层注册一下,结果固定是 sometimes。
  3. 外层 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)。

❗IMPORTANT
动脑筋:内层说了 never,它的 on_event 还会被调用吗?

内层 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)。

📝NOTE
冷知识:Layered 直到 0.3.21 才转发 on_register_dispatch

Subscriber::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 拿 CloseGuardRegistryRegistry 本身
判断某一层带不带每层过滤MagicPlfDowncastMarkerFiltered
判断某一层是不是 Option::NoneNoneLayerMarkerNone 的 Option<L>

后两个标记的指针从来不会被解引用,downcast_raw 在这里只是一个「穿过类型擦除问是或否」的通道。Layered 作为 Layer 的 downcast_raw 对每层过滤的标记有特殊处理:只有两边都带每层过滤,才算这个组合带每层过滤(.and 而不是 .or_else),Vec 的实现也要求全部元素都带。源码注释承认这条规则「is slightly counter-intuitive」,理由是如果一边带、一边不带也算带,不带过滤的那一边给出的 Interest 会被忽略。

动脑筋答案

连跑 3 次输出相同:

Inner on_event
Outer on_event

两行都打印了。过程是:

  1. 注册调用点:外层 sometimes,按 pick_interest 的第 2 条规则,先让内层注册(内层说 never),结果固定是 sometimes。内层的 never 被丢掉了。
  2. 每次经过 info!:sometimes 意味着要问 enabled。外层的 enabled 是默认的 true,内层也是默认的 true,Registry 也是 true。
  3. 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 起才转发。

💬 关联讨论

发起讨论
加载中...