Rust Axum如何实现错误日志自动捕获,无需手动重复记录?
解决Axum自动记录错误日志的方案
核心思路:统一错误处理+自动日志
无需在每个路由分支手动调用日志接口,通过统一错误转换层自动捕获错误并记录,同时满足Axum返回Response的要求。
步骤1:定义自定义错误类型
实现std::error::Error和IntoResponse trait,在IntoResponse的逻辑中自动写入错误日志:
use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; use std::fmt; #[derive(Debug)] enum AppError { DbError(sqlx::Error), ValidationError(String), // 根据业务需求扩展其他错误类型 } // 实现Display,输出友好的错误描述 impl fmt::Display for AppError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { AppError::DbError(e) => write!(f, "数据库错误: {}", e), AppError::ValidationError(msg) => write!(f, "参数验证失败: {}", msg), } } } // 实现Error trait,满足标准错误类型要求 impl std::error::Error for AppError {} // 实现IntoResponse,自动记录日志并转换为HTTP响应 impl IntoResponse for AppError { fn into_response(self) -> Response { // 自动记录错误,使用Display输出可读信息 error!("请求处理失败: {}", self); // 根据错误类型映射对应的HTTP状态码和响应内容 let (status, body) = match self { AppError::DbError(_) => (StatusCode::INTERNAL_SERVER_ERROR, "服务器内部错误".to_string()), AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, msg), }; (status, body).into_response() } }
步骤2:路由返回Result<Response, AppError>
路由处理器直接返回Result类型,Axum会自动调用IntoResponse完成错误转换与日志记录,无需手动处理:
use axum::extract::Query; use serde::Deserialize; #[derive(Deserialize)] struct UserQuery { id: u32, } // 路由无需手动写日志,错误会被统一处理 async fn get_user(Query(query): Query<UserQuery>) -> Result<Response, AppError> { let user = sqlx::query!("SELECT * FROM users WHERE id = $1", query.id) .fetch_one(&db_pool) .await .map_err(AppError::DbError)?; if user.is_deleted { return Err(AppError::ValidationError("用户已被删除".to_string())); } Ok(serde_json::to_string(&user).unwrap().into_response()) }
替代方案:用全局错误处理层适配已有路由
如果已有大量路由不想修改返回类型,可通过HandleErrorLayer配合tower::ServiceBuilder实现全局错误捕获:
use axum::{ error_handling::HandleErrorLayer, http::StatusCode, routing::get, Router, }; use tower::ServiceBuilder; // 统一错误处理函数,自动记录日志并返回HTTP响应 async fn handle_error(err: AppError) -> (StatusCode, String) { error!("请求处理失败: {}", err); match err { AppError::DbError(_) => (StatusCode::INTERNAL_SERVER_ERROR, "服务器内部错误".to_string()), AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, msg), } } // 构建路由时注入错误处理层 let app = Router::new() .route("/user", get(get_user)) .layer( ServiceBuilder::new() .layer(HandleErrorLayer::new(handle_error)) // 可添加其他中间件(如请求日志、超时控制等) .into_inner(), );
关于#[instrument(err)]与之前中间件的问题
#[instrument(err)]需要为每个路由单独添加宏,且仅能捕获Result类型的错误,无法感知已转换为Response的错误场景,不符合批量处理需求。- 你之前的中间件无法获取有效错误信息,是因为Axum的
Responsebody为流式结构(UnsyncBoxBody),读取后会消耗body导致无法返回给客户端。正确逻辑是在错误被包装为Response前完成捕获与记录,而非从最终响应中提取。
内容的提问来源于stack exchange,提问作者Thermatix
相关产品推荐
相关产品推荐

