Rust in Everything
基于 tracing 0.1.44

源码地图

主线位置:不在主线上。本页说明两条主线经过的代码在哪个 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版本管什么本卷
tracing0.1.44「Application-level tracing for Rust.」给库和应用用的门面:info! 等宏、Span、Instrument全卷,主讲 03
tracing-core0.1.36「Core primitives for application-level tracing.」Metadata、Callsite、Subscriber、Dispatch、字段,以及全局调用点注册表全卷,主讲 02
tracing-attributes0.1.31「Procedural macro attributes for automatically instrumenting functions.」#[instrument]04
tracing-subscriber0.3.23「Utilities for implementing and composing tracing subscribers.」Layer、Registry、过滤、fmt05
tracing-log0.2.0「Provides compatibility between tracing and the log crate.」LogTracer 把 log 记录转成 tracing 事件提到:03 的「和 log 互通」
tracing-appender0.2.5「Provides utilities for file appenders and making non-blocking writers.」按时间滚动的文件、后台线程写日志提到:常见坑
tracing-futures0.2.5「Utilities for instrumenting futures with tracing.」把 span 或 subscriber 挂到 future、sink、stream 或执行器上不讲
tracing-error0.2.1「Utilities for enriching errors with tracing.」SpanTrace:错误里带上当时的 span 链不讲
tracing-flame0.2.0「Tracing layer for creating flamegraphs from span timings」不讲
tracing-journald0.3.2「rich journald subscriber for tracing」,写进 Linux 的 journald不讲
tracing-serde0.2.0「A compatibility layer for serializing trace data with serde」;tracing-subscriber 的 json feature 依赖它不讲
tracing-tower0.1.0「Compatibility with the tower ecosystem.」不讲
tracing-macros0.1.0「Macros for emitting trace events」,目前只有一个 trace_dbg!不讲
tracing-mock0.1.0-beta.3「Utilities for testing tracing and crates that uses it.」断言被测代码发出了哪些事件和 span不讲
tracing-test0.1.0仓库内部测试用的小工具,publish = false不讲
tracing-examples0.0.0examples/,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

Cargo.toml 第 42 行起:

[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:比它详细的级别,宏展开出的判断在编译期就是 false03
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

Cargo.toml 第 25 行起:

[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 格式、MakeWriter05 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_json05 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.rs1 372Field、FieldSet、Value、ValueSet、Visit字段
metadata.rs1 122Metadata、Kind、Level、LevelFilter、全局 MAX_LEVELMetadata、Level 与 LevelFilter
dispatcher.rs1 067Dispatch、全局与线程局部的默认 dispatcher、get_defaultDispatch
subscriber.rs875Subscriber trait、Interest、NoSubscriberSubscriber trait
callsite.rs618Callsite trait、DefaultCallsite、全局调用点注册表、rebuild_interest_cacheCallsite 与 Interest 缓存
span.rs343span::Id、Attributes、Record、Currentspan 与 Event 的数据
lib.rs311crate 文档,identify_callsite!、metadata! 两个宏—
spin/283精简版 spin 0.5.2,只在关掉 std 时编译—
event.rs128Event、Event::dispatchspan 与 Event 的数据
lazy.rs、sync.rs、parent.rs110Lazy 在 std / no_std 下的两种实现、Mutex 包装、Parent 枚举—

tracing-core 是本卷的重点,却只有六千多行,其中 field.rs 和 metadata.rs 两个文件就占了四成。

tracing 0.1.44:7 083 行

文件行数管什么本卷
macros.rs3 342span!、event!、各级别的 info! / info_span! 等、level_enabled!、valueset!宏展开
span.rs1 615Span、Entered、EnteredSpanSpan 与 Entered
lib.rs1 205大部分是 crate 文档;__macro_support(宏展开后调用的函数)、log 模块宏展开、和 log 互通
instrument.rs429Instrument、Instrumented、WithSubscriber、WithDispatchInstrument 与异步
field.rs170重新导出 tracing-core 的 field,加一个 AsField trait—
dispatcher.rs145只有文档和 tracing-core dispatcher 的重新导出—
level_filters.rs113STATIC_MAX_LEVEL级别过滤的几道门
subscriber.rs64set_global_default、set_default、with_default 的泛型包装—

macros.rs 占了将近一半:同一套字段语法的匹配规则要对 trace / debug / info / warn / error 五个级别、对 span 和事件各写一遍。

tracing-attributes 0.1.31:2 072 行

文件行数管什么本卷
expand.rs848生成代码:同步函数、async fn、返回 impl Future 的函数各怎么包展开成什么
lib.rs717#[instrument] 的入口和长篇文档展开成什么
attr.rs507解析 skip、fields、level、err、ret 等参数参数

tracing-subscriber 0.3.23:23 184 行

路径行数管什么本卷
fmt/9 529fmt_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 898env/(2 890,EnvFilter)、layer_filters/(1 874,每层过滤)、targets.rs、filter_fn.rs、directive.rs过滤、EnvFilter
layer/3 208Layer trait(mod.rs 1 910)、Layered、Context;其中 tests.rs 310 行Layer 与 Layered
registry/1 854sharded.rs(Registry、CloseGuard)、stack.rs(span 栈)、extensions.rs、mod.rs(LookupSpan、SpanRef)Registry
field/777给 fmt 用的字段格式化工具—
reload.rs394reload::Layer 与 Handlereload
其余524lib.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/ 占七成)。

💬 关联讨论

发起讨论
加载中...