【Rust】使用日志记录利器flexi_logger
Flexi_logger简介
flexi_logger
是一个功能强大且灵活的日志记录库,用于Rust语言的应用程序。它提供了丰富的配置选项和功能,适用于各种日志记录需求,从简单的控制台输出到复杂的文件日志管理。以下是对flexi_logger
的一些关键功能和特性的简介:
主要功能
- 多种日志目标:支持将日志输出到控制台、文件或同时输出到多个目标
- 日志文件滚动:支持基于文件大小或时间的日志文件滚动,可以按大小、时间(每天、每小时等)进行切分
- 日志文件清理:可以配置保留的日志文件数量,自动删除过期的日志文件
- 日志格式自定义:支持自定义日志格式,可以包含时间戳、日志级别、目标模块等信息
- 日志级别过滤:支持设置不同的日志级别(如
info
、warn
、error
等),只记录满足级别要求的日志消息 - 异步日志:支持异步写日志,提高性能
- 日志复制:可以将日志复制到标准输出(stdout)或标准错误(stderr)
使用示例
创建项目
cargo new rs_demo
Cargo.toml
[package]
name = "rs_demo"
version = "0.1.0"
edition = "2021"
[dependencies]
log = "0.4"
flexi_logger = "0.22"
time = "0.3"
main.rs
use log::{trace, debug, info, warn, error};
use flexi_logger::{Duplicate, FileSpec, Logger, WriteMode, Criterion, Naming, Cleanup, detailed_format};
// 定义一个结构体 `Rectangle`
struct Rectangle {
width: u32,
height: u32,
}
// 为 `Rectangle` 实现方法
impl Rectangle {
// 关联函数(类似于静态方法)
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
// 实例方法
fn area(&self) -> u32 {
self.width * self.height
}
// 实例方法
fn perimeter(&self) -> u32 {
2 * (self.width + self.height)
}
// 修改实例的方法
fn set_width(&mut self, width: u32) {
self.width = width;
}
}
fn main() {
// 初始化日志记录,配置输出到文件,设置文件大小限制和滚动日志
let logger = Logger::try_with_str("trace")
.unwrap()
.log_to_file(
FileSpec::default()
.directory("logs") // 设置日志文件目录
.basename("app") // 设置日志文件前缀
.suffix("log") // 设置日志文件后缀
)
.rotate(
Criterion::Size(10_000_000), // 设置日志文件大小限制为 10 MB
Naming::Numbers, // 使用数字序号进行文件命名
Cleanup::KeepLogFiles(3), // 保留最近的 3 个日志文件
)
.write_mode(WriteMode::BufferAndFlush) // 设置日志写入模式
.duplicate_to_stderr(Duplicate::Warn) // 将警告级别的日志复制到标准错误输出
.format_for_files(detailed_format) // 使用详细格式,包含时间戳
.start()
.unwrap();
// 记录不同级别的日志消息
trace!("This is a trace message.");
debug!("This is a debug message.");
info!("This is an info message.");
warn!("This is a warning message.");
error!("This is an error message.");
// 使用关联函数创建一个 `Rectangle`
let mut rect = Rectangle::new(30, 50);
// 调用实例方法
info!("The area of the rectangle is {} square pixels.", rect.area());
info!("The perimeter of the rectangle is {} pixels.", rect.perimeter());
// 修改实例的字段
rect.set_width(40);
info!("After resizing, the area of the rectangle is {} square pixels.", rect.area());
// 强制刷新日志缓冲区
logger.flush();
}
这里的配置包括:
- 日志文件目录:日志文件将被保存到logs目录中
- 日志文件前缀:日志文件将以app作为前缀
- 日志文件后缀:日志文件将以.log作为后缀
- 日志文件滚动策略:当日志文件大小达到10 MB时,日志文件将滚动,新的日志文件将使用时间戳命名
- 日志文件保留策略:只保留最近的3个日志文件
- 日志写入模式:使用缓冲并在写入时刷新
- 日志复制到标准错误输出:将警告级别的日志消息复制到标准错误输出
- 日志格式:使用详细格式,包含时间戳
在Rust的log库中,不同的日志宏可以记录不同级别的日志消息。这些日志级别分别是:
- trace!:最详细的日志信息,通常用于跟踪程序的细节流程
- debug!:用于调试时的信息,比 trace! 略少,但仍然非常详细
- info!:一般信息,不含调试细节,适合普通运行时的信息
- warn!:警告信息,表明某些事情可能会出问题,但程序可以继续运行
- error!:错误信息,表明发生了严重的问题,程序可能无法继续运行
日志文件数据:
[2024-07-17 21:08:07.689886 +08:00] TRACE [rs_demo] src/main.rs:55: This is a trace message.
[2024-07-17 21:08:07.691157 +08:00] DEBUG [rs_demo] src/main.rs:56: This is a debug message.
[2024-07-17 21:08:07.691170 +08:00] INFO [rs_demo] src/main.rs:57: This is an info message.
[2024-07-17 21:08:07.697314 +08:00] WARN [rs_demo] src/main.rs:58: This is a warning message.
[2024-07-17 21:08:07.707785 +08:00] ERROR [rs_demo] src/main.rs:59: This is an error message.
[2024-07-17 21:08:07.707804 +08:00] INFO [rs_demo] src/main.rs:65: The area of the rectangle is 1500 square pixels.
[2024-07-17 21:08:07.707809 +08:00] INFO [rs_demo] src/main.rs:66: The perimeter of the rectangle is 160 pixels.
[2024-07-17 21:08:07.707814 +08:00] INFO [rs_demo] src/main.rs:70: After resizing, the area of the rectangle is 2000 square pixels.
自定义日志的输出格式
使用format_for_files函数可以用于自定义日志的输出格式。定义一个名为custom_format的函数,并将其传递给format_for_files以使用自定义格式记录日志
custom_format中:
- w:一个实现了Write trait的可写对象,日志消息将被写入这个对象
- record:一个Record对象,包含了日志记录的详细信息
use log::{info};
use flexi_logger::{DeferredNow, Duplicate, FileSpec, Logger, Record, WriteMode, Criterion, Naming, Cleanup};
use std::io::Write;
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
// 定义一个结构体 `Rectangle`
struct Rectangle {
width: u32,
height: u32,
}
// 为 `Rectangle` 实现方法
impl Rectangle {
// 关联函数(类似于静态方法)
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
// 实例方法
fn area(&self) -> u32 {
self.width * self.height
}
// 实例方法
fn perimeter(&self) -> u32 {
2 * (self.width + self.height)
}
// 修改实例的方法
fn set_width(&mut self, width: u32) {
self.width = width;
}
}
fn custom_format(
w: &mut dyn Write,
_now: &mut DeferredNow,
record: &Record,
) -> std::io::Result<()> {
let now = OffsetDateTime::now_utc().format(&Rfc3339).unwrap();
write!(
w,
"{} [{}] - {} - {}",
now,
record.level(),
record.target(),
record.args()
)
}
fn main() {
// 初始化日志记录,配置输出到文件,设置文件大小限制和滚动日志
let logger = Logger::try_with_str("info")
.unwrap()
.log_to_file(
FileSpec::default()
.directory("logs") // 设置日志文件目录
.basename("app") // 设置日志文件前缀
.suffix("log") // 设置日志文件后缀
)
.rotate(
Criterion::Size(10_000_000), // 设置日志文件大小限制为 10 MB
Naming::Numbers, // 使用数字序号进行文件命名
Cleanup::KeepLogFiles(3), // 保留最近的 3 个日志文件
)
.write_mode(WriteMode::BufferAndFlush) // 设置日志写入模式
.duplicate_to_stderr(Duplicate::Warn) // 将警告级别的日志复制到标准错误输出
.format_for_files(custom_format) // 使用自定义格式
.start()
.unwrap();
// 记录不同级别的日志消息
info!("This is an info message.");
// 使用关联函数创建一个 `Rectangle`
let mut rect = Rectangle::new(30, 50);
// 调用实例方法
info!("The area of the rectangle is {} square pixels.", rect.area());
info!("The perimeter of the rectangle is {} pixels.", rect.perimeter());
// 修改实例的字段
rect.set_width(40);
info!("After resizing, the area of the rectangle is {} square pixels.", rect.area());
// 强制刷新日志缓冲区
logger.flush();
}
2024-07-17T12:28:34.701362Z [INFO] - rs_demo - This is an info message.
2024-07-17T12:28:34.7024068Z [INFO] - rs_demo - The area of the rectangle is 1500 square pixels.
2024-07-17T12:28:34.7024153Z [INFO] - rs_demo - The perimeter of the rectangle is 160 pixels.
2024-07-17T12:28:34.7024181Z [INFO] - rs_demo - After resizing, the area of the rectangle is 2000 square pixels.
验证日志文件大小和保留日志文件个数
下面通过一个无限循环不断地写入日志消息,从而测试日志文件的滚动和清理功能。这种设置在实际应用中可能用于持续记录应用程序的运行状态或调试信息
use log::info;
use flexi_logger::{Logger, FileSpec, Criterion, Naming, Cleanup, WriteMode, Duplicate, detailed_format};
use std::thread;
use std::time::Duration;
fn main() {
// 初始化日志记录,配置输出到文件,设置文件大小限制和滚动日志
let _logger = Logger::try_with_str("info")
.unwrap()
.log_to_file(
FileSpec::default()
.directory("logs") // 设置日志文件目录
.basename("app") // 设置日志文件前缀
.suffix("log") // 设置日志文件后缀
)
.rotate(
Criterion::Size(5_000), // 设置日志文件大小限制为 5 KB
Naming::Timestamps, // 使用时间戳进行文件命名
Cleanup::KeepLogFiles(3), // 保留最近的 3 个日志文件
)
.write_mode(WriteMode::BufferAndFlush) // 设置日志写入模式
.duplicate_to_stderr(Duplicate::Warn) // 将警告级别的日志复制到标准错误输出
.format_for_files(detailed_format) // 使用详细格式,包含时间戳
.start()
.unwrap();
// 无限循环,不断写入日志
loop {
info!("This is an info message.");
// 模拟一些工作,等待一段时间
thread::sleep(Duration::from_secs(1));
}
}
运行程序数分钟,查看日志文件夹,文件夹目录结构如下,“rCURRENT.log”代表当前程序正在写入的日志文件,剩余的三个日志文件为程序保留的三个日志文件,日志文件都含有时间戳,并且不会随着程序运行的时间延长而新增文件个数
├─logs
│ app_r2024-07-17_20-44-24.log
│ app_r2024-07-17_20-45-19.log
│ app_r2024-07-17_20-46-15.log
│ app_rCURRENT.log
日志输出到控制台和文件
使用duplicate_to_stdout(Duplicate::All)方法,将所有级别的日志复制到标准输出(控制台)
.duplicate_to_stdout(Duplicate::All) // 将所有级别的日志复制到标准输出
这样,控制台能看到所有输出到日志文件的日志信息
按时间切分日志文件
Criterion::Age表示按时间切分日志文件,flexi_logger::Age::Day指定了按每天切分日志文件。这样,每天会生成一个新的日志文件
Criterion::Age(flexi_logger::Age::Day), // 设置日志文件按日切分