Rust + Axum 链路追踪:用 tower-http 与 tracing 打通请求级日志

test12026-09-1713 次阅读

为什么需要请求级追踪

一个 HTTP 请求会经过多层 handler、Service、数据库调用。出错时只看到一行孤立日志,根本拼不出完整路径。Axum 构建在 tower 生态之上,配合 tracing 与 tower-http 的 TraceLayer,可以给每个请求分配一个 trace id,把所有日志串成一条线。

初始化 tracing 订阅器

用 tracing-subscriber 输出结构化日志,注册 env filter 控制级别,开发环境开启美化。

use tracing_subscriber::{fmt, EnvFilter};

pub fn init_tracing() {
    let filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("info"));

    fmt()
        .with_env_filter(filter)
        .with_target(false)
        .init();
}

给每个请求挂上 TraceLayer

TraceLayer 自动为每个进入的请求生成 span,记录方法、路径、状态码与耗时。把它插在路由外层即可。

use axum::{routing::get, Router};
use tower_http::trace::TraceLayer;

async fn hello() -> &'static str {
    "ok"
}

#[tokio::main]
async fn main() {
    init_tracing();

    let app = Router::new()
        .route("/", get(hello))
        .layer(TraceLayer::new_for_http());

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

在 handler 里打结构化日志

进入 handler 后用 tracing 的宏记录事件,它们会自动归属到当前请求的 span,连同一个 trace id 输出。

use tracing::{info, instrument};

#[instrument(skip(pool))]
async fn get_user(pool: DbPool, id: u64) -> impl IntoResponse {
    info!(user_id = id, "查询用户");
    match user_repo::find(&pool, id).await {
        Ok(u) => (StatusCode::OK, Json(u)).into_response(),
        Err(e) => {
            tracing::error!(error = %e, "查询失败");
            StatusCode::INTERNAL_SERVER_ERROR.into_response()
        }
    }
}

接全链路追踪系统

基础 logging 之外,把 span 通过 opentelemetry 导出到 Jaeger 或 Tempo,就能看到跨服务调用火焰图。tower 的中间件设计让这件事几乎零侵入。

  • TraceLayer 已经埋好 on_request / on_response 钩子,自定义字段只需实现 MakeSpan。
  • 用 #[instrument] 自动把函数参数写进 span,排查时一目了然。
  • 生产环境把日志级别默认调到 info,避免 trace 级别把磁盘打满。
  • 把 trace id 透传到下游服务(HTTP header 或消息属性),才能串起完整链路。
T

test1

文章作者

为什么需要请求级追踪 一个 HTTP 请求会经过多层 handler、Service、数据库调用。出错时只看到一行孤立...

分类
技术
发布时间
2026-09-17
字数
约 1811 字
阅读
13 次