flexi_logger 是字节开源的rust日志库。目前有log4rs、env_log 等库,综合比较下来,还是flexi_logger简单容易上手,而且自定义很方便,以及在效率方面感觉也会高,下篇文章我们来测试下。
下面来看下怎么使用
关注 vx golang技术实验室,获取更多好文
一、启动日志的三种方式
引入包
flexi_logger = { version = "0.25.3", features = ["specfile", "compress"] }
通过选择三个选项之一来指定您想要查看的日志输出进行初始化,并start()
立即调用:
-
在环境变量中提供日志规范
RUST_LOG
:Logger::try_with_env()?.start()?;
-
以编程方式提供日志规范:
Logger::try_with_str("info")?.start()?;
-
结合两个选项:
Logger::try_with_env_or_str("info")?.start()?;
之后,您只需使用日志箱中的日志宏即可。然后,那些与日志规范匹配的日志行将被写入默认输出通道 (stderr)。
二、日志输出通道
-
log_to_stderr
日志写入标准输入
-
Logger::log_to_stdout
日志写入标准输出。
-
Logger::log_to_file
日志写入文件。
FileSpec
有关文件名模式的详细信息,请参阅。您可以复制到 stdout 和 stderr,并且可以添加其他编写器。
-
Logger::log_to_writer
日志写入到提供的写入器。
您可以复制到 stdout 和 stderr,并且可以添加其他编写器。
-
Logger::log_to_file_and_writer
日志被写入文件,与 一样
Logger::log_to_file
,并且写入替代LogWriter
实现。您可以复制到 stdout 和 stderr,并且可以添加其他编写器。
-
或者
Logger::do_not_log
日志被处理,包括重复,但不写入任何目的地。
这非常有用,例如,对于在所有日志级别都处于活动状态的情况下运行应用程序测试,并且仍然避免大量日志文件等。此类测试可确保通常不活动的日志调用在激活时不会导致不需要的副作用(请注意,日志调用宏可能会阻止评估非活动日志调用的参数)。
或者,如果您希望将日志同时发送到 stdout 和 stderr,但不发送到其他地方,则使用此选项并将其与
Logger::duplicate_to_stdout
and结合使用Logger::duplicate_to_stderr
。
2.1 写入到文件
assert_eq!(
FileSpec::default()
.directory("/a/b/c")//输出的目录
.basename("foo")//输出的文件名
.suppress_timestamp()//是否添加时间
.suffix("bar"),//添加的后缀
.discriminant("Sample4711A") .//使用FileSpec::discriminant 您可以向日志文件名添加有区别的中缀。
FileSpec::try_from("/a/b/c/foo.bar").unwrap()//
);
$ ll log_files
total 16
-rw-r--r-- 1 xxx staff 70 Oct 27 16:47 foo.bar //使用suppress_timestamp
-rw-r--r-- 1 xxx staff 70 Oct 27 16:47 foo_2023-10-27_16-47-53.bar
-rw-r--r-- 1 xxx staff 70 Oct 27 17:01 foo_Sample4711A_2023-10-27_17-01-11.bar
如果给定路径的基本名称没有文件名,则会发生恐慌
三、写入模式
默认情况下,每个日志行都直接写入输出,而不进行缓冲。这允许实时查看新的日志行。
您有Logger::write_mode
一些选择来改变这种行为,例如
-
pub enum WriteMode {
Direct,
SupportCapture,
BufferAndFlush,
BufferAndFlushWith(usize, Duration),
BufferDontFlush,
BufferDontFlushWith(usize),
Async,
AsyncWith {
pool_capa: usize,
message_capa: usize,
flush_interval: Duration,
},
}
-
Direct
不缓冲(默认)。
每个日志行都直接写入输出,无需缓冲。这允许实时查看新的日志行,并且不需要额外的线程。
-
SupportCapture
不缓冲和支持cargo test
捕获。
很像Direct
,只是慢一点,并且允许 cargo test
捕获日志输出并仅在失败的测试时打印它。
- BufferAndFlush
BufferAndFlushWith
与默认容量 ( DEFAULT_BUFFER_CAPACITY
) 和默认间隔 ( )相同DEFAULT_FLUSH_INTERVAL
。
pub const DEFAULT_BUFFER_CAPACITY: usize = _; // 8_192usize 8k
pub const DEFAULT_FLUSH_INTERVAL: Duration;//1s
- BufferAndFlushWith(usize, Duration)
元组字段
0: usize
缓冲能力。
1: Duration
冲洗间隔。
以给定的缓冲区容量和刷新间隔进行缓冲和刷新。
和上面的一样,是指指定size和刷新时间
- BufferDontFlush
BufferDontFlushWith
与默认容量相同( DEFAULT_BUFFER_CAPACITY
)。达到8k刷新
- BufferDontFlushWith(usize)
元组字段
0: usize
缓冲能力。
具有给定缓冲区容量的缓冲区,但不刷新。
如果您想最大程度地减少 I/O 工作量并且不想创建额外的线程用于刷新并且不关心日志行是否出现延迟,这可能会很方便。
- Async
与 相同AsyncWith
,所有参数均使用默认值。
- AsyncWith
Fields
pool_capa: usize
消息缓冲区池的容量。
message_capa: usize
单个消息缓冲区的容量。
flush_interval: Duration
刷新输出的时间间隔。
随着Duration::ZERO冲洗被抑制。
日志行通过无界通道发送到输出线程,该线程执行 I/O,如果log_to_file()
选择的话,还执行轮换和清理。
使用缓冲输出来减少开销,并使用有界消息池来减少分配。日志输出按照给定的时间间隔定期刷新。
-
使用
WriteMode::BufferAndFlush
、 或WriteMode::BufferAndFlushWith
,您可以减少程序的 I/O 开销,从而提高整体性能,如果大量使用日志记录,这可能是相关的。此外,为了在日志行在输出通道中可见之前保持较短的最大等待时间,会创建一个额外的线程来定期刷新缓冲区。fn main() -> Result<(), Box<dyn std::error::Error>> { let _logger = Logger::try_with_str("info")? .log_to_file(FileSpec::default()) .write_mode(WriteMode::BufferAndFlush) .start()?; // ... do all your work ... Ok(()) }
-
使用
WriteMode::Async
或 时WriteMode::AsyncWith
,日志通过无界通道从应用程序线程发送到输出线程,该输出线程执行输出(以及轮换和清理,如果适用)。此外,输出被缓冲,并且使用有界消息池来减少分配,并使用刷新来避免长时间延迟。如果使用复制,则消息将同步写入stdout
或stderr
。fn main() -> Result<(), Box<dyn std::error::Error>> { let _logger = Logger::try_with_str("info")? .log_to_file(FileSpec::default()) .write_mode(WriteMode::Async) .start()?; // ... do all your work ... Ok(()) }
四、日志格式
4.1 自定义输出格式
use flexi_logger::{ DeferredNow, Record};
fn custom_format(
w: &mut dyn std::io::Write,
now: &mut DeferredNow,
record: &Record,
) -> Result<(), std::io::Error> {
write!(
w,
"[{}] [{}] {}: {}",
now.now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
record.module_path().unwrap_or("<unnamed>"),
record.args()
)
}
let _logger = Logger::try_with_str("debug, my::critical::module=trace").unwrap()
.log_to_file(
FileSpec::default().
directory("./log_files").
basename("foo").
// suppress_timestamp().
suffix("bar").
discriminant("Sample4711A")
)
.duplicate_to_stderr(Duplicate::Debug)// print warnings and errors also to the console
.write_mode(WriteMode::BufferAndFlush).
format(custom_format)//定义的输出格式
.start().
unwrap();
[2023-10-27 17:32:08] [DEBUG] my_test: s
[2023-10-27 17:32:08] [INFO] my_test: s
[2023-10-27 17:32:08] [WARN] my_test: s
[2023-10-27 17:32:08] [ERROR] my_test: s
4. 2 使用默认的日志格式
## opt_format
[2023-10-27 17:34:23.438382 +08:00] DEBUG [src/main.rs:50] s
[2023-10-27 17:34:23.439057 +08:00] INFO [src/main.rs:51] s
[2023-10-27 17:34:23.439078 +08:00] WARN [src/main.rs:52] s
[2023-10-27 17:34:23.439091 +08:00] ERROR [src/main.rs:53] s
## default_format
DEBUG [my_test] s
INFO [my_test] s
WARN [my_test] s
ERROR [my_test] s
## detailed_format
[2023-10-27 17:36:12.719699 +08:00] DEBUG [my_test] src/main.rs:50: s
[2023-10-27 17:36:12.719948 +08:00] INFO [my_test] src/main.rs:51: s
[2023-10-27 17:36:12.719964 +08:00] WARN [my_test] src/main.rs:52: s
[2023-10-27 17:36:12.719978 +08:00] ERROR [my_test] src/main.rs:53: s
## with_thread
[2023-10-27 17:36:41.542709 +08:00] T[main] DEBUG [src/main.rs:50] s
[2023-10-27 17:36:41.542968 +08:00] T[main] INFO [src/main.rs:51] s
[2023-10-27 17:36:41.542984 +08:00] T[main] WARN [src/main.rs:52] s
[2023-10-27 17:36:41.542997 +08:00] T[main] ERROR [src/main.rs:53] s
4.2 默认的彩色输出
## colored_default_format
## colored_detailed_format
## colored_opt_format
和上面的是一一对应的
4.4 日志文件截取
无论Logger::log_to_file
是否进行轮换, flexi_logger
默认情况下都会使用名称中带有时间戳的文件,例如 foo_2020-11-16_08-37-44.log
(对于名为 的程序foo
),这些文件对于每个程序启动来说都是非常唯一的。
这样FileSpec::suppress_timestamp
您就可以获得一个简单的固定文件名,例如foo.log
.
在这种情况下,重新启动程序将截断现有日志文件。
另外使用Logger::append
将每次新运行的日志附加到现有文件中。
Logger::try_with_str("info")? // Write all error, warn, and info messages
// use a simple filename without a timestamp
.log_to_file(
FileSpec::default().suppress_timestamp()
)
// do not truncate the log file when the program is restarted
.append()
.start()?;
通过轮换,日志始终会写入带有中缀的文件中rCURRENT
,例如foo_rCURRENT.log
。
Logger::rotate
采用三个定义其行为的枚举参数:
-
Criterion
Criterion::Age
当时钟切换到新的一天、小时、分钟或秒时,就会发生旋转Criterion::Size
当当前日志文件超过指定限制时发生轮转Criterion::AgeOrSize
当达到两个限制中的任何一个时,就会发生旋转
-
Naming
然后将当前文件重命名- 与
Naming::Timestamps
类似的东西foo_r2020-11-16_08-56-52.log
- 与
Naming::Numbers
类似的东西foo_r00000.log
并
rCURRENT
创建一个新文件。 - 与
-
Cleanup
定义是否以及如何避免无限期累积日志文件:- 您指定
Cleanup::KeepLogFiles
应保留的日志文件的数量;如果有更多,较旧的将被删除 - 您指定
Cleanup::KeepCompressedFiles
应保留的日志文件的数量,并且这些文件将被额外压缩 - 您可以
Cleanup::KeepLogAndCompressedFiles
指定应按原样保留的日志文件数量以及正在压缩的附加数量 - 如果
Cleanup::Never
不进行清理,所有文件都会保留。
- 您指定
案例
Logger::try_with_str("info")? // Write all error, warn, and info messages
.log_to_file(
FileSpec::default()
)
.rotate( // If the program runs long enough,
Criterion::Age(Age::Day), // - create a new file every day
Naming::Timestamps, // - let the rotated files have a timestamp in their name
Cleanup::KeepLogFiles(7), // - keep at most 7 log files
)
.start()?;
-rw-r--r-- 1 xxx staff 1508 Oct 27 18:46 foo_Sample4711A_rCURRENT.bar
五、代码方式启动
5.1 程序中设置
let _logger = Logger::try_with_str("debug, my::critical::module=trace").unwrap()
.log_to_file(
FileSpec::default().
directory("./log_files").
basename("foo").
suppress_timestamp().
suffix("bar").
discriminant("Sample4711A")
)
.duplicate_to_stderr(Duplicate::Debug)// print warnings and errors also to the console
.write_mode(WriteMode::BufferAndFlush).
format(colored_opt_format).
append()
.rotate( // If the program runs long enough,
Criterion::AgeOrSize(Age::Day,10000000000000), // - create a new file every day
Naming::Timestamps, // - let the rotated files have a timestamp in their name
Cleanup::KeepLogFiles(7), // - keep at most 7 log files
)
.start().
unwrap();
这里我们使用了配置字符串 "debug, my::critical::module=trace"
,表示全局默认日志级别为 debug
,而模块路径为 my::critical::module
的日志级别为 trace
。
5.2 文件设置
Logger::try_with_str("info").unwrap()
// ... logger configuration ...
.start_with_specfile("./server/config/logspec.toml")
.unwrap();
将创建包含以下内容的文件logspecification.toml
(如果尚不存在):
### Optional: Default log level
global_level = 'info'
### Optional: specify a regular expression to suppress all messages that don't match
#global_pattern = 'foo'
### Specific log levels per module are optionally defined in this section
[modules]
#'mod1' = 'warn'
#'mod2' = 'debug'
#'mod2::mod3' = 'trace'
随后,您可以根据需要编辑和修改该文件,在程序运行时,它将立即考虑您的更改。
目前仅支持 toml 文件,因此文件后缀必须是.toml
.
如果无法读取文件,则初始规范仍然有效。