Rust 日志处理实战:从 tracing 到文件输出一步步优化
·
4min
·
Paxon Qiao
Table of Contents
Rust 日志处理实战:从 tracing 到文件输出一步步优化
在 Rust 开发中,日志是调试和监控的重要工具。tracing 和 tracing-subscriber 提供了灵活而强大的日志处理能力,适用于从简单控制台输出到复杂分布式系统的需求。本文将通过一个 Axum 项目示例,带你逐步掌握日志的配置、优化以及文件输出,帮助你在实践中养成良好的日志习惯。
本文以一个基于 Axum 的 Rust 项目为例,介绍了如何使用 tracing 和 tracing-subscriber 实现日志记录。从基础的控制台输出开始,逐步优化代码以支持更详细的日志信息、异步任务耗时统计,最终实现日志文件滚动保存。通过四次迭代,我们展示了如何在实际开发中配置日志级别、添加文件输出并实时查看日志,适合 Rust 开发者参考。
Rust 日志处理
日志处理:tracing
、tracing-subscriber
tracing
:记录各种日志tracing-subscriber
:输出日志open-telemetry*
:和 open-telemetry 生态互动- 养成良好的
tracing/metrics
习惯
实操
项目目录结构
rust-ecosystem-learning on main is 📦 0.1.0 via 🦀 1.85.0 via 🅒 base
➜ tree . -L 6 -I 'target|coverage|coverage_report|docs'
.
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── _typos.toml
├── cliff.toml
├── deny.toml
├── examples
│ ├── axum_tracing.rs
│ └── err.rs
├── rust-toolchain.toml
├── src
│ └── lib.rs
└── test.http
3 directories, 14 files
安装依赖
➜ cargo add axum --features http2 --features query --features tracing --dev
➜ cargo add tokio --features rt --features rt-multi-thread --features macros --dev
➜ cargo add tracing
➜ cargo add tracing-subscriber --features env-filter
➜ cargo add tracing-appender
axum_tracing.rs
文件
use axum::{routing::get, Router};
use tokio::net::TcpListener;
use tracing::{info, instrument, level_filters::LevelFilter};
use tracing_subscriber::{
fmt::{self, format::FmtSpan},
layer::SubscriberExt,
util::SubscriberInitExt,
Layer,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let console = fmt::Layer::new()
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.pretty()
.with_filter(LevelFilter::INFO);
tracing_subscriber::registry().with(console).init();
let addr = "0.0.0.0:8080";
let app = Router::new().route("/", get(index_handler));
let listener = TcpListener::bind(addr).await?;
info!("listening on {}", addr);
axum::serve(listener, app.into_make_service()).await?;
Ok(())
}
#[instrument]
async fn index_handler() -> &'static str {
"Hello, world!"
}
第一次运行
cargo run --example axum_tracing
Finished `dev` profile [unoptimized + debuginfo] target(s) in 13.11s
Running `target/debug/examples/axum_tracing`
2025-03-17T09:01:17.202968Z INFO axum_tracing: listening on 0.0.0.0:8080
at examples/axum_tracing.rs:23
2025-03-17T09:13:39.480085Z INFO axum_tracing: new
at examples/axum_tracing.rs:28
in axum_tracing::index_handler
2025-03-17T09:13:39.480445Z INFO axum_tracing: close, time.busy: 5.67µs, time.idle: 368µs
at examples/axum_tracing.rs:28
in axum_tracing::index_handler
Request 发送请求
### index handler
GET http://localhost:8080/ HTTP/1.1
Response 响应
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-length: 13
connection: close
date: Mon, 17 Mar 2025 09:13:39 GMT
Hello, world!
优化一 axum_tracing.rs
文件
use std::time::Duration;
use axum::{routing::get, Router};
use tokio::{net::TcpListener, time::sleep};
use tracing::{info, instrument, level_filters::LevelFilter, warn};
use tracing_subscriber::{
fmt::{self, format::FmtSpan},
layer::SubscriberExt,
util::SubscriberInitExt,
Layer,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let console = fmt::Layer::new()
.with_span_events(FmtSpan::CLOSE)
.pretty()
.with_filter(LevelFilter::INFO);
tracing_subscriber::registry().with(console).init();
let addr = "0.0.0.0:8080";
let app = Router::new().route("/", get(index_handler));
let listener = TcpListener::bind(addr).await?;
info!("listening on {}", addr);
axum::serve(listener, app.into_make_service()).await?;
Ok(())
}
#[instrument]
async fn index_handler() -> &'static str {
sleep(Duration::from_millis(10)).await;
let ret = long_task().await;
info!(http.status = 200, "index handler completed");
ret
}
#[instrument]
async fn long_task() -> &'static str {
let dur = 112;
sleep(Duration::from_millis(dur)).await;
warn!(app.task_duration = dur, "long task done");
"long task done"
}
第二次运行
优化二 axum_tracing.rs
文件
use std::time::Duration;
use axum::{routing::get, Router};
use tokio::{
net::TcpListener,
time::{sleep, Instant},
};
use tracing::{info, instrument, level_filters::LevelFilter, warn};
use tracing_subscriber::{
fmt::{self, format::FmtSpan},
layer::SubscriberExt,
util::SubscriberInitExt,
Layer,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let console = fmt::Layer::new()
.with_span_events(FmtSpan::CLOSE)
.pretty()
.with_filter(LevelFilter::INFO);
tracing_subscriber::registry().with(console).init();
let addr = "0.0.0.0:8080";
let app = Router::new().route("/", get(index_handler));
let listener = TcpListener::bind(addr).await?;
info!("listening on {}", addr);
axum::serve(listener, app.into_make_service()).await?;
Ok(())
}
#[instrument]
async fn index_handler() -> &'static str {
sleep(Duration::from_millis(10)).await;
let ret = long_task().await;
info!(http.status = 200, "index handler completed");
ret
}
#[instrument]
async fn long_task() -> &'static str {
let start = Instant::now();
sleep(Duration::from_millis(112)).await;
let elapsed = start.elapsed().as_millis() as u64;
warn!(app.task_duration = elapsed, "long task done");
"long task done"
}
第三次运行
创建目录
➜ mkdir /tmp/logs
优化三 axum_tracing.rs
文件
use std::time::Duration;
use axum::{routing::get, Router};
use tokio::{
net::TcpListener,
time::{sleep, Instant},
};
use tracing::{debug, info, instrument, level_filters::LevelFilter, warn};
use tracing_subscriber::{
fmt::{self, format::FmtSpan},
layer::SubscriberExt,
util::SubscriberInitExt,
Layer,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let file_appender = tracing_appender::rolling::daily("/tmp/logs", "ecosystem.log");
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
let console = fmt::Layer::new()
.with_span_events(FmtSpan::CLOSE)
.pretty()
.with_filter(LevelFilter::DEBUG);
let file = fmt::Layer::new()
.with_span_events(FmtSpan::CLOSE)
.with_writer(non_blocking)
.with_filter(LevelFilter::INFO);
tracing_subscriber::registry()
.with(console)
.with(file)
.init();
let addr = "0.0.0.0:8080";
let app = Router::new().route("/", get(index_handler));
let listener = TcpListener::bind(addr).await?;
info!("listening on {}", addr);
axum::serve(listener, app.into_make_service()).await?;
Ok(())
}
#[instrument]
async fn index_handler() -> &'static str {
debug!("index handler started");
sleep(Duration::from_millis(10)).await;
let ret = long_task().await;
info!(http.status = 200, "index handler completed");
ret
}
#[instrument]
async fn long_task() -> &'static str {
let start = Instant::now();
sleep(Duration::from_millis(112)).await;
let elapsed = start.elapsed().as_millis() as u64;
warn!(app.task_duration = elapsed, "long task done");
"long task done"
}
第四次运行
查看日志
实时查看日志
tail -f 是一个在 Linux/Unix 系统中常用的命令,用于实时查看文件的末尾内容。它可以在文件被追加内容时,实时显示这些内容,而不需要重新加载整个文件。
总结
通过本文的实战演练,我们从简单的日志输出开始,逐步优化到支持耗时统计和文件记录,展示了 tracing 生态的强大功能。无论是调试还是生产环境,合理配置日志都能提升开发效率和系统可观测性。希望这篇文章能为你的 Rust 项目提供实用参考,助你在日志处理上更进一步!
参考
- https://github.com/tokio-rs/tracing
- https://course.rs/logs/tracing.html
- https://crates.io/crates/tracing-subscriber
- https://docs.rs/tracing-subscriber/latest/tracing_subscriber/index.html
- https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html
- https://github.com/tokio-rs/tracing/tree/master/tracing-appender