← 中文资源

简体中文版本

Tracing Span 结构化注记

为 tracing span 添加结构化注记:字段运行中途 record 补值、同步代码用 enter 守卫圈定范围、follows_from 建立因果关联。

阅读英文原文

示例说明

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;
}