Rust in Everything
基于 tracing-core 0.1.36

tracing-core

这是本卷的重点。tracing 的宏、#[instrument]、tracing-subscriber 的每一层,最后都只和 tracing-core 里的几种类型打交道:宏在调用点放一份静态的 Metadata 和一个缓存 Interest 的调用点,判断时读全局的 LevelFilter,再通过 Dispatch 找到当前的 Subscriber,把字段装进 ValueSet 交给它。为什么把这些类型单独拆成一个 crate,见 源码地图。

六章按「一次 info! 依次碰到谁」排列:

  1. Metadata、Level 与 LevelFilter:宏为每个调用点生成一份 static 的 Metadata;Level 的内部数值和比较方向相反,好让 LevelFilter::OFF 落在 niche 上;全局 MAX_LEVEL 是一个初值为 OFF 的 AtomicUsize。
  2. Callsite 与 Interest 缓存:DefaultCallsite 用两个原子字节记注册状态和缓存的 Interest,第一次经过时问所有 dispatcher 并合并答案,之后只读一个字节;新建 Dispatch 或调用 rebuild_interest_cache 时整表重算并刷新 MAX_LEVEL。
  3. Dispatch:全局与线程局部:Dispatch 是全局静态引用或 Arc 的二选一;全局默认只能设一次,线程默认靠 thread_local 和守卫;get_default 在没有线程默认时走快路径。
  4. Subscriber trait:方法分判断、span 生命周期、事件三组,必须实现的只有 7 个;span 的 Id 由 subscriber 分配,tracing 自己不存 span 数据。
  5. 字段:Field、Value 与 Visit:FieldSet 是静态名字表加调用点标识,ValueSet 是栈上借来的值、构造时不分配;subscriber 用 Visit 按类型取值,没覆盖的方法都落到 record_debug。
  6. span 与 Event 的数据:Attributes、Record、Event 都是元数据加 ValueSet 加父 span 标记(显式、Current 或 Root);Event::dispatch 把事件交给当前 dispatcher。
flowchart TB
  macroexp["info! 展开"] --> meta["Metadata 与 Level"]
  meta --> lvl["LevelFilter::current"]
  lvl --> cs["Callsite 的 Interest"]
  cs --> disp["Dispatch:当前 subscriber"]
  disp --> sub["Subscriber 方法"]
  macroexp --> vs["ValueSet 字段"]
  vs --> ev["Event 或 Attributes"]
  ev --> disp

前两章是「要不要记」:级别和 Interest 两道门大多数时候只花一次原子读。后四章是「记给谁、记什么」。宏怎么把这些类型拼起来,在下一部分的 宏展开;tracing-subscriber 怎么实现 Subscriber,在 Layer 与 Layered。

💬 关联讨论

发起讨论
加载中...