15.4 异步日志
听
在异步 Web 服务中,传统的日志方式难以追踪跨任务的请求上下文。Rust 的 tracing 生态提供了一套结构化、高性能且支持异步上下文传播的日志与诊断框架。结合 tracing-subscriber,我们可以轻松实现带请求 ID、层级 span 和结构化字段的日志输出,极大提升调试和监控能力。
核心概念:Event 与 Span
- Event:表示一个瞬时事件,如“任务创建成功”;
- Span:表示一段有持续时间的操作,如“处理 /tasks POST 请求”,可嵌套并携带上下文。
在异步环境中,tracing 能自动将 span 与 tokio 任务关联,确保即使多个请求并发执行,日志仍能正确归属。
添加依赖
在 Cargo.toml 中添加:
[dependencies]
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-appender = "0.2" # 可选:用于文件输出
初始化全局订阅器
在 src/main.rs 的 main 函数开头初始化日志系统:
use tracing_subscriber::{EnvFilter, fmt};
fn init_tracing() {
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("info"));
fmt::Subscriber::builder()
.with_env_filter(filter)
.with_line_number(true)
.with_file(true)
.init();
}
此配置:
- 从环境变量
RUST_LOG读取日志级别(如RUST_LOG=debug); - 默认级别为
info; - 输出包含文件名和行号,便于定位。
调用:
#[tokio::main]
async fn main() {
init_tracing();
tracing::info!("Starting server on port 3000");
// ... 启动服务
}
在 Handler 中记录日志
使用 #[instrument] 属性宏自动为函数创建 span:
use tracing::instrument;
#[instrument(skip(service), fields(task.title = %payload.title))]
pub async fn create_task(
State(service): State<TaskService>,
Json(payload): Json<CreateTask>,
) -> Result<Json<Task>, AppError> {
tracing::debug!("Validating input");
let task = service.create_task(payload).await?;
tracing::info!(task.id = task.id, "Task created successfully");
Ok(Json(task))
}
skip(service)避免尝试格式化不可显示的服务对象;fields(...)在 span 创建时注入静态字段;tracing::info!等宏记录事件,并自动关联当前 span。
跨异步边界的上下文传播
由于 tracing 与 tokio 深度集成,即使 handler 内部调用多个异步函数,span 上下文也会自动传递:
#[instrument]
async fn save_to_db(task: CreateTask, pool: &PgPool) -> Result<Task, SqlxError> {
tracing::trace!("Executing INSERT query");
// SQLx 查询...
}
所有日志将嵌套在 create_task 的 span 下,形成清晰的调用树。
结构化日志输出
tracing 默认输出为人类可读格式,也可配置为 JSON(便于日志收集系统):
fmt::Subscriber::builder()
.json() // 启用 JSON 格式
.with_env_filter(filter)
.init();
示例 JSON 日志:
{
"timestamp": "2025-06-15T10:23:45Z",
"level": "INFO",
"fields": {
"message": "Task created successfully",
"task.id": 42
},
"target": "task_api::handlers::tasks"
}
高级用法:自定义中间件记录请求日志
可在 Axum 中间件中为每个请求创建顶层 span:
use axum::{
body::Body,
http::{Request, StatusCode},
response::Response,
middleware::Next,
};
use tracing::info_span;
use tower_http::trace::TraceLayer;
// 使用 tower_http 的 TraceLayer(基于 tracing)
let app = Router::new()
.route("/tasks", post(create_task))
.layer(
TraceLayer::new_for_http()
.make_span_with(|req: &Request<Body>| {
info_span!(
"http_request",
method = %req.method(),
uri = %req.uri(),
version = ?req.version()
)
})
);
这会为每个 HTTP 请求生成一个 span,并记录方法、URI 等元数据。
小结
通过 tracing 和 tracing-subscriber,我们为 Web 服务引入了现代化的可观测性基础。结构化日志不仅便于本地调试,也为生产环境的监控、告警和分布式追踪(结合 OpenTelemetry)铺平道路。合理使用 span 和 event,能让复杂异步系统的执行路径一目了然,显著降低运维成本。
#Rust 入门教程
分享于 5 天前
上一篇:15.3 数据库集成
下一篇:15.5 配置管理、错误处理与中间件