Rust日志记录 - tracing
tracing 是 Rust 生态中用于结构化日志记录与分布式追踪的现代化框架,由 Tokio 团队开发维护,扩展了传统日志功能,提供了关于时序性和因果关系的额外信息。
添加依赖
cargo add tracing # 核心框架
cargo add tracing-subscriber --features env-filter,fmt # 日志订阅与处理
cargo add tracing-appender # 文件输出与轮转
常用特性说明:
env-filter:通过环境变量配置日志过滤规则fmt:提供格式化输出(文本 / JSON)json:支持 JSON 格式输出chrono:使用更友好的时间格式
初始化 Subscriber
Subscriber 是 tracing 的日志处理器,负责收集和输出日志数据。你可以把它理解为日志的"输出目的地"。常见的 Subscriber 包括将日志写入标准输出、保存到文件、发送到日志收集系统如 ELK Stack 或 Jaeger。
Subscriber 负责格式化 Span 和 Event 数据,并决定哪些日志应该被记录(根据日志级别过滤)。
示例,默认初始化:
use tracing::Level;
use tracing_subscriber;
fn main() {
// 初始化 Subscriber,输出到 stdout,
// 从RUST_LOG 环境变量读取日志过滤级别,如未设置,默认为 ERROR 级别
tracing_subscriber::fmt::init();
}
示例,记录日志到文件,按日轮转,同时也输出到控制台。
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
fn main() {
let _guard = init_logging()
// 测试记录
tracing::info!("系统启动,开始记录日志到文件...");
for i in 0..5 {
tracing::debug!(id = i, "正在处理任务");
}
}
fn init_logging() -> tracing_appender::non_blocking::WorkerGuard {
// 1. 设置日志文件存放目录和文件名前缀
let directory = "logs";
let file_name_prefix = "app.log";
// 2. 配置每日滚动 (Daily Rotation)
let file_appender = tracing_appender::rolling::daily(directory, file_name_prefix);
// 3. 构造非阻塞写入器 (Non-blocking writer)
// guard 必须在 main 中持有,如果它被丢弃,日志缓冲区的内容可能无法写入文件
let (non_blocking_writer, guard) = tracing_appender::non_blocking(file_appender);
// 4. 将文件输出与控制台输出结合
tracing_subscriber::registry()
// 过滤器:从环境变量 RUST_LOG 读取,默认为 info
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
// 终端输出层
.with(fmt::layer().with_writer(std::io::stdout))
// 文件输出层(这里可以设置是否需要 ANSI 颜色,存文件建议关掉)
.with(
fmt::layer()
.with_ansi(false)
.with_writer(non_blocking_writer)
)
.init();
return guard;
}
记录Event
Event 代表一个具体的时刻点事件。使用 event! 宏记录事件。
use tracing::{event, Level};
event!(Level::INFO, "something has happened!");
tracing提供了一组与event!类似但已指定了Level 参数的宏,与 log 库中的宏同名:
trace!("跟踪信息:变量 x = {}", x);
debug!("调试信息:计算结果 = {}", result);
info!("普通信息:处理请求 {}", request_id);
warn!("警告信息:缓存命中率较低");
error!("错误信息:数据库连接失败");
结构化日志(Structured Logging)
tracing 的强大之处在于你可以像写 JSON 一样记录变量,而不是拼接字符串。这对于后期在 ELK 或 Datadog 中检索极其方便。
let user = "Alice";
let user_id = 42;
// 推荐做法:使用字段名
info!(user = %user, id = ?user_id, "用户登录成功");
// 注释:
// % 表示调用 Display 格式化
// ? 表示调用 Debug 格式化
创建Span
在异步代码中,相关的事件和日志行会相互混合,使得追踪逻辑流程变得困难。为此,tracing 引入了 span 的概念。与代表一个时间点的日志行不同,span 代表一个有开始和结束的时间段。span 内的 Event将关联到此 span,使得事件可以追溯到其产生的上下文环境。
use tracing::{info, span, Level};
use tracing_subscriber;
fn main() {
// 1. 初始化默认订阅者
tracing_subscriber::fmt()
.with_max_level(Level::INFO)
.init();
// 3. 创建并进入一个 Span
let main_span = span!(Level::INFO, "my_span");
let _enter = main_span.enter(); // 进入 Span,在此之后的日志都会带上该 Span 的信息
info!("这条日志是在 Span 内部产生的");
} // _enter 离开作用域,Span 自动关闭
#[instrument]
#[instrument] 是 tracing 提供的一个过程宏,可以自动为函数创建 Span。使用这个宏,你无需手动编写 Span 的创建和进入代码。
use tracing::{info, instrument};
#[instrument] // 自动创建一个名为 "calculate" 的 Span,并记录 a 和 b 的值
async fn calculate(a: u32, b: u32) -> u32 {
info!("计算中...");
a + b
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
calculate(10, 20).await;
}
#[instrument] 常用参数:
level:设置 span 级别(默认INFO)name:自定义 span 名称skip/skip_all:跳过某些参数记录fields:添加额外结构化字段ret:记录函数返回值
本文来自博客园,作者:星墨,转载请注明原文链接:https://www.cnblogs.com/yada/p/19869152

浙公网安备 33010602011771号