源码地图
主线位置:不在主线上。本页说明两条主线经过的代码在哪个 crate、哪个文件,以及要打开哪个 feature 才能编译到它。
一个用 tracing 的程序依赖的几个 crate(实线是 crate 之间的依赖,虚线是「通常由你自己加进来」或者「要开 feature 才有」):
flowchart TB app[你的程序] --> tracing[tracing] app -.-> sub[tracing-subscriber] tracing --> core[tracing-core] tracing --> attrs["tracing-attributes:attributes"] tracing --> pin[pin-project-lite] sub --> core sub --> tlog["tracing-log:默认开"] sub --> slab["sharded-slab 等:registry"] sub -.-> tracing2["tracing:env-filter"] tlog --> logcrate[log] tlog --> core
仓库里的 16 个 crate
根目录的 Cargo.toml 的 [workspace] members 列了 16 个 crate:15 个 tracing*,加一个放示例的 examples(包名 tracing-examples)。下表的「管什么」取自各 crate Cargo.toml 的 description,没有 description 的取自 lib.rs:
| crate | 版本 | 管什么 | 本卷 |
|---|---|---|---|
tracing | 0.1.44 | 「Application-level tracing for Rust.」给库和应用用的门面:info! 等宏、Span、Instrument | 全卷,主讲 03 |
tracing-core | 0.1.36 | 「Core primitives for application-level tracing.」Metadata、Callsite、Subscriber、Dispatch、字段,以及全局调用点注册表 | 全卷,主讲 02 |
tracing-attributes | 0.1.31 | 「Procedural macro attributes for automatically instrumenting functions.」#[instrument] | 04 |
tracing-subscriber | 0.3.23 | 「Utilities for implementing and composing tracing subscribers.」Layer、Registry、过滤、fmt | 05 |
tracing-log | 0.2.0 | 「Provides compatibility between tracing and the log crate.」LogTracer 把 log 记录转成 tracing 事件 | 提到:03 的「和 log 互通」 |
tracing-appender | 0.2.5 | 「Provides utilities for file appenders and making non-blocking writers.」按时间滚动的文件、后台线程写日志 | 提到:常见坑 |
tracing-futures | 0.2.5 | 「Utilities for instrumenting futures with tracing.」把 span 或 subscriber 挂到 future、sink、stream 或执行器上 | 不讲 |
tracing-error | 0.2.1 | 「Utilities for enriching errors with tracing.」SpanTrace:错误里带上当时的 span 链 | 不讲 |
tracing-flame | 0.2.0 | 「Tracing layer for creating flamegraphs from span timings」 | 不讲 |
tracing-journald | 0.3.2 | 「rich journald subscriber for tracing」,写进 Linux 的 journald | 不讲 |
tracing-serde | 0.2.0 | 「A compatibility layer for serializing trace data with serde」;tracing-subscriber 的 json feature 依赖它 | 不讲 |
tracing-tower | 0.1.0 | 「Compatibility with the tower ecosystem.」 | 不讲 |
tracing-macros | 0.1.0 | 「Macros for emitting trace events」,目前只有一个 trace_dbg! | 不讲 |
tracing-mock | 0.1.0-beta.3 | 「Utilities for testing tracing and crates that uses it.」断言被测代码发出了哪些事件和 span | 不讲 |
tracing-test | 0.1.0 | 仓库内部测试用的小工具,publish = false | 不讲 |
tracing-examples | 0.0.0 | examples/,40 个示例程序,publish = false | 不讲 |
「版本」一栏,本卷用到的六个 crate 写的是本卷锁定的版本,其余十个是 tracing-0.1.44 这个 tag 里 Cargo.toml 写的版本。tracing-subscriber 和 tracing-appender 在 tracing-0.1.44 里还是 0.3.22 和 0.2.4,所以本卷用它们各自的 tag(见怎么读这一卷)。
为什么拆出 tracing-core
tracing-core 的 crate 文档「Usage」一节:
Application authors will typically not use this crate directly. Instead,
they will use the [`tracing`] crate, which provides a much more
fully-featured API. However, this crate's API will change very infrequently,
so it may be used when dependencies must be very stable.
`Subscriber` implementations may depend on `tracing-core` rather than
`tracing`, as the additional APIs provided by `tracing` are primarily useful
for instrumenting libraries and applications, and are generally not
necessary for `Subscriber` implementations.
仓库根目录 README 的「Project layout」说得更直接:「Authors of trace subscribers may depend on tracing-core, which guarantees a higher level of stability.」
这层拆分有一个硬理由:一个程序里所有打日志的库和 subscriber 必须共用同一个 tracing-core。全局调用点注册表 CALLSITES 和全局 dispatcher GLOBAL_DISPATCH 都是 tracing-core 里的 static。hyper、tower-http 和你自己的代码在打日志,它们的调用点要登记进同一个 CALLSITES,事件要交给同一个 GLOBAL_DISPATCH,你装的 subscriber 才看得到。Cargo 把两个不兼容的大版本当成两个不同的 crate,各有一份 static;如果 tracing-core 发一个 0.2,依赖 0.1 和依赖 0.2 的库就各说各的。所以 tracing-core 必须极少发破坏性版本。它从 2019-06-27 发布 0.1.0 起,到 0.1.36 一直没有离开 0.1(仓库里有 37 个 tracing-core-0.1.* tag)。
门面放在 tracing 里,宏、Span 的便利方法、Instrument 可以接着加;tracing 用 pub use 把核心类型原样转出来(lib.rs 里的 subscriber::Subscriber、dispatcher::Dispatch、Level、Metadata 都来自 tracing-core),tracing::Subscriber 和 tracing_core::Subscriber 是同一个 trait,用 tracing 写应用的人感觉不到这层拆分。
tracing-core 也确实很轻:默认只依赖 once_cell 一个 crate。tracing-subscriber 是最好的例子:它实现的是 Subscriber,只需要 tracing-core。下面的依赖树里,默认 feature 下的 tracing-subscriber 根本不依赖 tracing,只有打开 env-filter 才会拉进来(Cargo.toml 里那一行的注释是「only required by the filter feature」)。
依赖树
三种写法下 cargo tree -e normal --depth 2 的输出(M2 Max、rustc 1.99.0,路径行已删)。默认 feature:
tracing = "=0.1.44"
tracing-subscriber = "=0.3.23"
├── tracing v0.1.44
│ ├── pin-project-lite v0.2.17
│ ├── tracing-attributes v0.1.31 (proc-macro)
│ └── tracing-core v0.1.36
└── tracing-subscriber v0.3.23
├── nu-ansi-term v0.50.3
├── sharded-slab v0.1.7
├── smallvec v1.16.4
├── thread_local v1.1.10
├── tracing-core v0.1.36 (*)
└── tracing-log v0.2.0
打开 env-filter 和 json:
tracing = "=0.1.44"
tracing-subscriber = { version = "=0.3.23", features = ["env-filter", "json"] }
├── tracing v0.1.44
│ ├── pin-project-lite v0.2.17
│ ├── tracing-attributes v0.1.31 (proc-macro)
│ └── tracing-core v0.1.36
└── tracing-subscriber v0.3.23
├── matchers v0.2.0
├── nu-ansi-term v0.50.3
├── once_cell v1.21.4
├── regex-automata v0.4.18
├── serde v1.0.229
├── serde_json v1.0.154
├── sharded-slab v0.1.7
├── smallvec v1.16.4
├── thread_local v1.1.10
├── tracing v0.1.44 (*)
├── tracing-core v0.1.36 (*)
├── tracing-log v0.2.0
└── tracing-serde v0.2.0
两边都关掉默认 feature:
tracing = { version = "=0.1.44", default-features = false }
tracing-subscriber = { version = "=0.3.23", default-features = false }
├── tracing v0.1.44
│ ├── pin-project-lite v0.2.17
│ └── tracing-core v0.1.36
└── tracing-subscriber v0.3.23
└── tracing-core v0.1.36
全关时 tracing-subscriber 只剩 Layer trait、Layered 和 LevelFilter、filter_fn 这样不需要分配内存的过滤器,fmt、Registry、EnvFilter、Targets 都没有了;tracing 少了 #[instrument],也少了 tracing-core 的 std(tracing-core 在 no_std 下用自带的一份精简版 spin 0.5.2 代替 once_cell)。
tracing 的 feature
[features]
default = ["std", "attributes"]
max_level_off = []
max_level_error = []
max_level_warn = []
max_level_info = []
max_level_debug = []
max_level_trace = []
release_max_level_off = []
release_max_level_error = []
release_max_level_warn = []
release_max_level_info = []
release_max_level_debug = []
release_max_level_trace = []
# This feature flag is no longer necessary.
async-await = []
std = ["tracing-core/std"]
log-always = ["log"]
attributes = ["tracing-attributes"]
valuable = ["tracing-core/valuable"]
log 没有出现在 [features] 里,它是可选依赖 log = { version = "0.4.17", optional = true } 自动生成的同名 feature;attributes 打开的 tracing-attributes 也是可选依赖。每个 feature 管到哪里:
| feature | 默认 | 打开了什么 | 本卷 |
|---|---|---|---|
std | 是 | tracing-core 的 std:线程局部的默认 dispatcher、set_default / with_default 等 | 02 |
attributes | 是 | 依赖 tracing-attributes,重新导出 #[instrument];crate 文档提醒它会带进 syn,增加编译时间 | 04 |
log | 否 | 宏里多展开一段 __tracing_log!:进程里还从没设过任何 dispatcher(has_been_set() 为假)时,把事件和 span 生命周期也作为 log 记录发出去 | 03 |
log-always | 否 | 打开 log,并且装了 subscriber 也照样发 log 记录;crate 文档说库一般不要开,和 LogTracer 一起用会重复 | 03 |
max_level_* | 否 | 决定常量 STATIC_MAX_LEVEL:比它详细的级别,宏展开出的判断在编译期就是 false | 03 |
release_max_level_* | 否 | 同上,只在 not(debug_assertions) 时生效;release 构建里优先于 max_level_* | 03 |
valuable | 否 | 用 valuable crate 记录结构化的字段值;不稳定,还要加 --cfg tracing_unstable | 不讲 |
async-await | 否 | 空 feature,注释说「no longer necessary」,留着只为兼容 | — |
max_level_* 这一组是全局的:feature 在整个依赖图里合并,任何一个依赖打开了 release_max_level_info,整个程序 release 构建里的 debug! 都会消失。具体怎么判断、多个 feature 同时打开时谁生效,见级别过滤的几道门。
tracing-core 的 feature 只有 std(默认开,std = ["once_cell"])和不稳定的 valuable;tracing-attributes 是 proc-macro crate,只有一个同样作废了的 async-await。
tracing-subscriber 的 feature
[features]
default = ["smallvec", "fmt", "ansi", "tracing-log", "std"]
alloc = []
std = ["alloc", "tracing-core/std"]
env-filter = ["matchers", "once_cell", "tracing", "std", "thread_local", "dep:regex-automata"]
fmt = ["registry", "std"]
ansi = ["fmt", "nu-ansi-term"]
registry = ["sharded-slab", "thread_local", "std"]
json = ["tracing-serde", "serde", "serde_json"]
valuable = ["tracing-core/valuable", "valuable_crate", "valuable-serde", "tracing-serde/valuable"]
# Enables support for local time when using the `time` crate timestamp
# formatters.
local-time = ["time/local-offset"]
nu-ansi-term = ["dep:nu-ansi-term"]
# For backwards compatibility only
regex = []
默认开 5 个:smallvec、fmt、ansi、tracing-log、std。fmt 依赖 registry、std 依赖 alloc,所以默认情况下 registry 和 alloc 也是开着的。smallvec、tracing-log、time、chrono、parking_lot 都是可选依赖自动生成的 feature。每个 feature 管到哪里:
| feature | 默认 | 打开了什么 | 本卷 |
|---|---|---|---|
std / alloc | 是 | reload 模块要 std;alloc 让 Box<dyn Layer>、Vec<L> 这类组合可用 | 05 |
registry | 是(经 fmt) | Registry:sharded-slab 存 span 数据,thread_local 存每个线程的 span 栈 | 05 Registry |
fmt | 是 | fmt 模块:fmt::Layer、fmt() 构造器、Full / Compact / Pretty 格式、MakeWriter | 05 fmt |
ansi | 是 | 依赖 nu-ansi-term,fmt 输出带颜色 | 05 fmt |
tracing-log | 是 | init() / try_init() 装好全局 subscriber 后顺手装上 LogTracer,把 log 记录转进来;fmt 格式化这类事件时用它还原原来的文件、行号 | 03 和 log 互通 |
smallvec | 是 | 过滤指令列表用 SmallVec 代替 Vec | — |
env-filter | 否 | EnvFilter:从 RUST_LOG 读指令;依赖 matchers、regex-automata,以及 tracing 本身 | 05 EnvFilter |
json | 否 | fmt 的 Json 格式;依赖 tracing-serde、serde、serde_json | 05 fmt,只提一句 |
time / local-time | 否 | 用 time crate 格式化时间戳;local-time 打开 time 的 local-offset,用本地时区 | 只提一句 |
chrono | 否 | 用 chrono 格式化时间戳 | 只提一句 |
parking_lot | 否 | 用 parking_lot 的 RwLock 代替标准库的 | — |
valuable | 否 | 同 tracing 的 valuable,不稳定 | 不讲 |
regex | 否 | 空 feature,「For backwards compatibility only」 | — |
tracing-log 起作用的地方在 SubscriberInitExt::try_init:先 set_global_default,成功了再用当前的 LevelFilter 设置 LogTracer 的最大级别并初始化它。所以 fmt().init() 之后,用 log 打日志的依赖也会出现在输出里。
src/ 的布局
下面的行数是各 crate 在自己 tag 下 src/ 里 .rs 文件的总行数,含测试和注释,只用来估分量。
tracing-core 0.1.36:6 229 行
| 文件 | 行数 | 管什么 | 本卷 |
|---|---|---|---|
field.rs | 1 372 | Field、FieldSet、Value、ValueSet、Visit | 字段 |
metadata.rs | 1 122 | Metadata、Kind、Level、LevelFilter、全局 MAX_LEVEL | Metadata、Level 与 LevelFilter |
dispatcher.rs | 1 067 | Dispatch、全局与线程局部的默认 dispatcher、get_default | Dispatch |
subscriber.rs | 875 | Subscriber trait、Interest、NoSubscriber | Subscriber trait |
callsite.rs | 618 | Callsite trait、DefaultCallsite、全局调用点注册表、rebuild_interest_cache | Callsite 与 Interest 缓存 |
span.rs | 343 | span::Id、Attributes、Record、Current | span 与 Event 的数据 |
lib.rs | 311 | crate 文档,identify_callsite!、metadata! 两个宏 | — |
spin/ | 283 | 精简版 spin 0.5.2,只在关掉 std 时编译 | — |
event.rs | 128 | Event、Event::dispatch | span 与 Event 的数据 |
lazy.rs、sync.rs、parent.rs | 110 | Lazy 在 std / no_std 下的两种实现、Mutex 包装、Parent 枚举 | — |
tracing-core 是本卷的重点,却只有六千多行,其中 field.rs 和 metadata.rs 两个文件就占了四成。
tracing 0.1.44:7 083 行
| 文件 | 行数 | 管什么 | 本卷 |
|---|---|---|---|
macros.rs | 3 342 | span!、event!、各级别的 info! / info_span! 等、level_enabled!、valueset! | 宏展开 |
span.rs | 1 615 | Span、Entered、EnteredSpan | Span 与 Entered |
lib.rs | 1 205 | 大部分是 crate 文档;__macro_support(宏展开后调用的函数)、log 模块 | 宏展开、和 log 互通 |
instrument.rs | 429 | Instrument、Instrumented、WithSubscriber、WithDispatch | Instrument 与异步 |
field.rs | 170 | 重新导出 tracing-core 的 field,加一个 AsField trait | — |
dispatcher.rs | 145 | 只有文档和 tracing-core dispatcher 的重新导出 | — |
level_filters.rs | 113 | STATIC_MAX_LEVEL | 级别过滤的几道门 |
subscriber.rs | 64 | set_global_default、set_default、with_default 的泛型包装 | — |
macros.rs 占了将近一半:同一套字段语法的匹配规则要对 trace / debug / info / warn / error 五个级别、对 span 和事件各写一遍。
tracing-attributes 0.1.31:2 072 行
| 文件 | 行数 | 管什么 | 本卷 |
|---|---|---|---|
expand.rs | 848 | 生成代码:同步函数、async fn、返回 impl Future 的函数各怎么包 | 展开成什么 |
lib.rs | 717 | #[instrument] 的入口和长篇文档 | 展开成什么 |
attr.rs | 507 | 解析 skip、fields、level、err、ret 等参数 | 参数 |
tracing-subscriber 0.3.23:23 184 行
| 路径 | 行数 | 管什么 | 本卷 |
|---|---|---|---|
fmt/ | 9 529 | fmt_layer.rs(1 715)、format/(3 730,含 json.rs、pretty.rs)、writer.rs(1 485,MakeWriter)、mod.rs(1 388,fmt() 构造器)、time/(1 211) | fmt |
filter/ | 6 898 | env/(2 890,EnvFilter)、layer_filters/(1 874,每层过滤)、targets.rs、filter_fn.rs、directive.rs | 过滤、EnvFilter |
layer/ | 3 208 | Layer trait(mod.rs 1 910)、Layered、Context;其中 tests.rs 310 行 | Layer 与 Layered |
registry/ | 1 854 | sharded.rs(Registry、CloseGuard)、stack.rs(span 栈)、extensions.rs、mod.rs(LookupSpan、SpanRef) | Registry |
field/ | 777 | 给 fmt 用的字段格式化工具 | — |
reload.rs | 394 | reload::Layer 与 Handle | reload |
| 其余 | 524 | lib.rs、util.rs(SubscriberInitExt:init / try_init)、sync.rs、macros.rs、prelude.rs | — |
tracing-subscriber 比另外三个加起来还大一倍多,其中 fmt/ 和 filter/ 占了七成。本卷讲 Layer、Registry、过滤和 fmt 的主干,fmt/time 的几种时间格式和 json 格式只提一句。
另外两个只在一两节出现的 crate:tracing-log 0.2.0 的 src/ 1 445 行(lib.rs、log_tracer.rs、interest_cache.rs),tracing-appender 0.2.5 的 2 588 行(rolling.rs 和 rolling/ 合计 1 734 行,non_blocking.rs 490 行)。
小结
- 仓库的 workspace 有 16 个 crate;本卷讲 tracing、tracing-core、tracing-attributes、tracing-subscriber,提到 tracing-log 和 tracing-appender。
- tracing-core 放所有库共用的核心类型和全局注册表,承诺「change very infrequently」,2019 年起一直是 0.1;实现
Subscriber只需要依赖它。默认 feature 下的 tracing-subscriber 甚至不依赖 tracing。 - tracing 默认开
std、attributes;log、log-always、max_level_*、release_max_level_*要手动开,后两组在整个依赖图里合并、对整个程序生效。 - tracing-subscriber 默认开
smallvec、fmt、ansi、tracing-log、std(连带registry、alloc);env-filter、json、time、local-time等要手动开。默认的tracing-log让init()顺手装上LogTracer。 - 行数:tracing-core 6 229,tracing 7 083(
macros.rs占 3 342),tracing-attributes 2 072,tracing-subscriber 23 184(fmt/与filter/占七成)。