Rust in Everything
基于 tracing-subscriber 0.3.23

tracing-subscriber

tracing-core 只定义了 Subscriber 这个 trait,真正收下事件、记住 span 的代码几乎都来自 tracing-subscriber。它不写一个大而全的 Subscriber,而是拆成两类东西:一个 Registry 负责发 span 的 Id、存 span 的数据、记每个线程的当前 span;若干个 Layer 各管一件事(过滤、格式化输出、统计耗时),用 .with(...) 叠到 Registry 外面,叠出来的 Layered<..> 本身又是一个 Subscriber。fmt().with_env_filter(..).init() 这一行,展开后就是 Registry 外面先套一个 fmt::Layer、再套一个 EnvFilter。

六章按「先组合、再存储、再过滤、最后输出」的顺序:

  1. Layer 与 Layered:Layer 是 Subscriber 去掉发 Id 的那部分;Layered 的判断方法从外往里问、遇到「不要」就短路,通知方法先交给内层再调本层;register_callsite 由 pick_interest 合并;Option<L>、Vec<L>、Box<dyn Layer> 也是 Layer。
  2. Registry:span 存在 sharded-slab 的 Pool<DataInner> 里,Id 是打包了槽地址、线程号、代数的键加一;每个线程一个 span 栈;CloseGuard 保证所有层的 on_close 跑完才释放槽;LookupSpan、SpanRef、scope() 让 layer 查 span 数据。
  3. 过滤:全局与每层:.with(filter) 挡住所有层,layer.with_filter(filter) 只挡这一层;每层过滤靠 FilterId 和线程局部的位图实现。
  4. EnvFilter:RUST_LOG 指令语法,静态指令和动态指令的区别,错误指令的处理。
  5. fmt:fmt::Layer 的四个类型参数、字段预先格式化存进 Extensions、四种输出格式、MakeWriter、init 做了什么。
  6. reload 与运行时改级别:reload::Layer 换掉 filter 后重建 Interest 缓存。
flowchart TB
  initcall["fmt().init()"] --> outer["Layered:EnvFilter"]
  outer --> mid["Layered:fmt::Layer"]
  mid --> reg["Registry"]
  reg --> pool["span 池:sharded-slab"]
  reg --> stackn["每线程 span 栈"]
  mid -.->|"ctx.span(id)"| reg

前两章是后四章的基础:过滤、fmt、reload 都是某种 Layer,都靠 Context 向 Registry 查 span。

💬 关联讨论

发起讨论
加载中...