动手
前面各部分是读 tracing 的源码,这一部分反过来:不依赖 tracing,自己把事件主线和 span 主线的骨架写一遍。写完再回头看,DefaultCallsite 为什么要两个原子字节、全局 MAX_LEVEL 为什么初值是 OFF、span 栈为什么放在 subscriber 里而不是 tracing 里,都会变成你自己做过的选择。
- 从零写一个最小日志门面:约 270 行的库加一个演示程序。
event!/span!宏在调用点放两个static(元数据和调用点),调用点用一个AtomicU8缓存 subscriber 的回答;OnceLock做只能设一次的全局 dispatcher;一个写 stderr 的 subscriber,按RUST_LOG="info,demo::app=debug"的规则过滤,用线程局部的栈记当前 span。用 criterion 实测:被 subscriber 拒绝的debug!,有缓存约 0.44 ns,每次都问约 4.5 ns。最后一张表列出它和 tracing 的差距。
flowchart TB macros["event! 与 span!"] --> statics["static 元数据与调用点"] statics --> gate2["全局 MAX_LEVEL"] gate2 --> cache["调用点的 AtomicU8"] cache --> disp["OnceLock:全局 dispatcher"] disp --> sub["stderr subscriber"] sub --> stack["线程局部 span 栈"] cache --> bench["实测:缓存与不缓存"]
💬 关联讨论
发起讨论加载中...