示例说明
tower 生态把中间件抽象成 layer 叠加:SetRequestIdLayer 在最外层为每个请求生成 UUID 并写入头,后续所有层都能读到同一个 ID——日志、tracing span、响应头三处共享。自定义中间件用 from_fn 包装:next.run 之前的代码是请求阶段、之后是响应阶段,Instant 计时横跨两段。
技术要点
- layer 顺序决定洋葱嵌套次序
- 请求 ID 层放最外全局可用
- from_fn 免手写 Service trait
代码解读
timing_middleware 先取请求头里的 ID(或占位符),调用下游后用 info! 输出六组结构化字段;5xx 追加 warn 级告警便于告警规则捕获。TraceLayer 的 make_span_with 定制 span 携带请求 ID——tracing 与日志共用同一标识。两个路由分别演示正常与错误路径。
使用建议
layer 从下往上包裹,注册顺序要与依赖关系对齐。慢请求阈值告警可在中间件里追加。生产日志走 JSON 格式对接采集器。
代码示例
// logging middleware tower layer for axum
use axum::{
body::Body,
extract::Request,
http::{HeaderValue, StatusCode},
middleware::{self, Next},
response::Response,
routing::get,
Router,
};
use std::time::Instant;
use tower_http::request_id::{MakeRequestUuid, SetRequestIdLayer, XRequestIdName};
use tower_http::trace::TraceLayer;
use tracing::{info, info_span};
async fn timing_middleware(request: Request, next: Next) -> Response {
let started = Instant::now();
let request_id = request
.headers()
.get("x-request-id")
.and_then(|v| v.to_str().ok())
.unwrap_or("-")
.to_string();
let method = request.method().clone();
let path = request.uri().path().to_string();
let response = next.run(request).await;
info!(
request_id = %request_id,
method = %method,
path = %path,
status = %response.status().as_u16(),
elapsed_ms = started.elapsed().as_millis() as u64,
"request completed"
);
if response.status().is_server_error() {
tracing::warn!(path = %path, "server error served");
}
response
}
async fn handler() -> StatusCode {
StatusCode::OK
}
async fn fail() -> StatusCode {
StatusCode::BAD_GATEWAY
}
#[tokio::main]
async fn main() {
let app: Router = Router::new()
.route("/ok", get(handler))
.route("/boom", get(fail))
.layer(middleware::from_fn(timing_middleware))
.layer(TraceLayer::new_for_http().make_span_with(|request: &Request<Body>| {
let request_id = request
.headers()
.get(XRequestIdName::default())
.cloned()
.unwrap_or_else(|| HeaderValue::from_static("-"));
info_span!("http", request_id = ?request_id)
}))
.layer(SetRequestIdLayer::new(
XRequestIdName::default(),
MakeRequestUuid,
));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}