Rust in Everything
基于 tracing-attributes 0.1.31

#[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 行)解析参数。两章分别对应后两个文件:

  1. 展开成什么:同步函数先过一次级别判断,再创建 span 并 enter;async fn 的函数体变成 async move 块,交给 Instrument::instrument;返回 async move 块或 Box::pin(async move {}) 的普通函数(async-trait 生成的就是这种)靠 AsyncInfo::from_fn 按语法认出来;基本类型参数按值记录,其余按 Debug;span 被关掉时同步路径什么都不建,异步路径仍然要建一个空 span。
  2. 参数: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 与异步。

💬 关联讨论

发起讨论
加载中...