#[instrument]
#[instrument] 是大多数人用 span 的方式:在函数上写一行属性,函数每次被调用就有一个 span,参数自动成为字段。它由 tracing-attributes 这个过程宏 crate 提供,tracing 默认开启的 attributes feature 把它重新导出成 tracing::instrument。宏本身不碰任何运行时类型,它只是把你的函数改写成一段调用 span!、enter、Instrument::instrument、event! 的代码;这些代码运行时做什么,前几部分已经讲过。
源码只有三个文件,共 2 072 行:lib.rs(717 行,多半是文档)是入口,expand.rs(848 行)生成代码,attr.rs(507 行)解析参数。两章分别对应后两个文件:
- 展开成什么:同步函数先过一次级别判断,再创建 span 并
enter;async fn的函数体变成async move块,交给Instrument::instrument;返回async move块或Box::pin(async move {})的普通函数(async-trait 生成的就是这种)靠AsyncInfo::from_fn按语法认出来;基本类型参数按值记录,其余按Debug;span 被关掉时同步路径什么都不建,异步路径仍然要建一个空 span。 - 参数:
attr.rs用一个手写的循环解析skip、skip_all、fields、level、name、target、parent、follows_from、err、ret;每个参数最后变成span!或event!里的哪一段;err默认用Display、级别ERROR,ret默认用Debug、级别跟 span 一样;常见的编译错误长什么样。
flowchart TB attrnode["#[instrument(...)]"] --> parse["attr.rs:解析参数"] attrnode --> fnitem["函数本身"] fnitem --> detect["AsyncInfo::from_fn"] detect -->|"认出 async 块"| genasync["gen_async"] detect -->|"认不出"| genfn["gen_function"] parse --> genblock["gen_block:拼 span! 和 event!"] genasync --> genblock genfn --> genblock genblock --> out["改写后的函数"]
生成代码里的 span! 走的是和手写 info_span! 完全一样的路,见 宏展开;async fn 那条路依赖的 Instrumented<F> 见 Instrument 与异步。
💬 关联讨论
发起讨论加载中...