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 次
