tracing-core
这是本卷的重点。tracing 的宏、#[instrument]、tracing-subscriber 的每一层,最后都只和 tracing-core 里的几种类型打交道:宏在调用点放一份静态的 Metadata 和一个缓存 Interest 的调用点,判断时读全局的 LevelFilter,再通过 Dispatch 找到当前的 Subscriber,把字段装进 ValueSet 交给它。为什么把这些类型单独拆成一个 crate,见 源码地图。
六章按「一次 info! 依次碰到谁」排列:
- Metadata、Level 与 LevelFilter:宏为每个调用点生成一份
static的Metadata;Level的内部数值和比较方向相反,好让LevelFilter::OFF落在 niche 上;全局MAX_LEVEL是一个初值为OFF的AtomicUsize。 - Callsite 与 Interest 缓存:
DefaultCallsite用两个原子字节记注册状态和缓存的Interest,第一次经过时问所有 dispatcher 并合并答案,之后只读一个字节;新建Dispatch或调用rebuild_interest_cache时整表重算并刷新MAX_LEVEL。 - Dispatch:全局与线程局部:
Dispatch是全局静态引用或Arc的二选一;全局默认只能设一次,线程默认靠thread_local和守卫;get_default在没有线程默认时走快路径。 - Subscriber trait:方法分判断、span 生命周期、事件三组,必须实现的只有 7 个;span 的
Id由 subscriber 分配,tracing 自己不存 span 数据。 - 字段:Field、Value 与 Visit:
FieldSet是静态名字表加调用点标识,ValueSet是栈上借来的值、构造时不分配;subscriber 用Visit按类型取值,没覆盖的方法都落到record_debug。 - 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。
💬 关联讨论
发起讨论加载中...