示例说明
span 的字段不必在创建时全部赋值——声明为 Empty 的占位字段可以在逻辑推进到一半时再 record 补值,风险等级这类推断结果就有了归属。同步代码没有 await 点,enter 守卫是进入 span 的唯一方式,guard 的生命周期就是 span 的作用域。follows_from 则表达非父子因果:异步链路上松散关联的两个 span 由此链接。
技术要点
- Empty 字段支持运行期补值
- enter 守卫圈定同步代码范围
- follows_from 建立因果而非父子
代码解读
charge 的 instrument 属性声明三个字段其中 risk 预留为空:函数内按金额阈值 record 高低风险;卡号只输出后四位防泄漏。sync_section 用守卫进入 span 后循环里持续更新行数字段。main 的第二段演示异步上下文的 instrument 组合与手动因果链接。
使用建议
敏感字段截断或哈希后再入 span。字段数量克制防序列化开销。守卫与 await 混用会导致 span 跨任务错乱——异步只用 instrument。
代码示例
use tracing::{field::Empty, info_span, Instrument as _, Span};
#[tracing::instrument(name = "charge", fields(order_id, amount_cents, risk = Empty))]
async fn charge(card_token: &str, order_id: u64, amount: u64) -> Result<(), String> {
let span = Span::current();
// 运行中途才能确定的字段:record 到已建 span 上
span.record("amount_cents", amount);
if amount > 1_000_000 {
span.record("risk", "high");
return Err("amount exceeds single-charge limit".into());
}
span.record("risk", "low");
// 模拟网关调用:子 span 自动嵌套
tracing::info!(card_last4 = &card_token[(card_token.len() - 4)..], "contacting gateway");
Ok(())
}
fn sync_section() {
let span = info_span!("sync_transform", rows = 0);
let _guard = span.enter();
for i in 0..3 {
// 同步代码里 span 字段随循环更新
Span::current().record("rows", i + 1);
}
tracing::info!("transform complete");
}
#[tokio::main]
async fn main() {
let outcome = charge("tok_4242424242424242", 991, 25_000).await;
println!("charge result: {:?}", outcome);
let parent = info_span!("request", id = 7);
async {
sync_section();
let child = info_span!("db_query");
child.follows_from(parent.clone());
let _g = child.enter();
tracing::info!("query issued under causality link");
}
.instrument(parent)
.await;
}