17个版本
0.2.14 | 2024年5月23日 |
---|---|
0.2.12 | 2023年12月21日 |
0.2.11 | 2023年11月30日 |
0.2.7 | 2023年6月14日 |
0.2.1 | 2022年11月25日 |
#108 in 调试
316 每月下载量
用于 logo
73KB
1K SLoC
ftlog
日志受磁盘I/O和管道系统调用的影响。在低延迟至关重要的场景中(例如,高频交易),连续的日志调用可能成为瓶颈。
ftlog
通过将消息发送到专用的日志线程并在主/工作线程中进行尽可能少的计算来缓解这个瓶颈。
ftlog
可以将主/工作线程中的日志性能提高几倍。有关详细信息,请参阅性能。
使用方法
将以下内容添加到您的 Cargo.toml
ftlog = "0.2"
在 main
函数开始时配置和初始化 ftlog
// ftlog re-export `log`'s macros, so no need to add `log` to dependencies
use ftlog::appender::FileAppender;
use ftlog::{debug, trace};
use log::{error, info, warn};
// minimal configuration with default setting
// When drops, the guard calls and waits `flush` to logger.
// With guard that share the lifetime of `main` fn, there is no need to manually call flush at the end of `main` fn.
let _guard = ftlog::builder().try_init().unwrap();
trace!("Hello world!");
debug!("Hello world!");
info!("Hello world!");
warn!("Hello world!");
error!("Hello world!");
更复杂但功能丰富的使用
use ftlog::{
appender::{Duration, FileAppender, Period},
FtLogFormatter, LevelFilter,
};
let time_format = time::format_description::parse_owned::<1>(
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:6]",
)
.unwrap();
// configurate logger
let _guard = ftlog::builder()
// global max log level
.max_log_level(LevelFilter::Info)
// custom timestamp format
.time_format(time_format)
// set global log formatter
.format(FtLogFormatter)
// use bounded channel to avoid large memory comsumption when overwhelmed with logs
// Set `false` to tell ftlog to discard excessive logs.
// Set `true` to block log call to wait for log thread.
// here is the default settings
.bounded(100_000, false) // .unbounded()
// define root appender, pass anything that is Write and Send
// omit `Builder::root` will write to stderr
.root(
FileAppender::builder()
.path("./current.log")
.rotate(Period::Day)
.expire(Duration::days(7))
.build(),
)
// timezone of log message timestamp, use local by default
// .local_timezone()
// or use fiexed timezone for better throughput, since retrieving timezone is a time consuming operation
// this does not affect worker threads (that call log), but can boost log thread performance (higher throughput).
.fixed_timezone(time::UtcOffset::current_local_offset().unwrap())
// level filter for root appender
.root_log_level(LevelFilter::Warn)
// write logs in ftlog::appender to "./ftlog-appender.log" instead of "./current.log"
.filter("ftlog::appender", "ftlog-appender", LevelFilter::Error)
.appender("ftlog-appender", FileAppender::new("ftlog-appender.log"))
.try_init()
.expect("logger build or set failed");
有关更多示例(例如,自定义格式),请参阅 ./examples
默认日志格式
2022-04-08 19:20:48.190+08 298ms INFO main src/ftlog.rs:14 我的日志信息
这里的 298ms
表示日志调用(例如 log::info!("msg")
)和日志线程中实际打印之间的延迟。通常为0ms。
较大的延迟表明日志线程可能因为过多的日志消息而被阻塞。
2022-04-10 21:27:15.996+08 0ms 2 INFO main src/main.rs:29 limit running3 !
上面的数字 2 表示丢弃了多少条日志消息。仅在单个日志调用的日志频率有限的情况下显示(例如,log::info!(limit=3000i64;"msg")
)。
随机丢弃日志
使用 random_drop
或 drop
指定随机丢弃日志的概率。默认不丢弃任何消息。
log::info!(random_drop=0.1f32;"Random log 10% of log calls, keeps 90%");
log::info!(drop=0.99f32;"Random drop 99% of log calls, keeps 1%");
当将日志消息格式化为字符串的成本过高时,这可能会很有帮助。
当同时指定了 random_drop
和 limit
时,ftlog 将在随机丢弃消息后限制日志。
log::info!(drop=0.99f32,limit=1000;
"Drop 99% messages. The survived 1% messages are limit to at least 1000ms between adjacent log messages output"
);
自定义时间戳格式
ftlog
依赖于 time
crate 进行时间戳的格式化。要使用自定义时间格式,首先构建一个有效的时间格式描述,然后将它通过 ftlog::time_format(&mut self)
传递给 ftlog 构建器。
如果在格式化时间戳时发生错误,ftlog
将回退到 RFC3339 时间格式。
示例
let format = time::format_description::parse_owned::<1>(
"[year]/[month]/[day] [hour]:[minute]:[second].[subsecond digits:6]",
)
.unwrap();
let _guard = ftlog::builder().time_format(format).try_init().unwrap();
log::info!("Log with custom timestamp format");
// Output:
// 2023/06/14 11:13:26.160840 0ms INFO main [main.rs:3] Log with custom timestamp format
带间隔的日志
ftlog
允许限制单个日志调用的写入频率。
如果在 3000ms 内多次调用上述行,则只记录一次,并添加一个数字来反映丢弃的日志消息数量。
每个日志调用都有一个独立的间隔,因此我们可以为不同的日志调用设置不同的间隔。内部,ftlog
通过(模块名称,文件名称,代码行)的组合记录最后打印时间。
示例
info!(limit=3000i64; "limit running {}s !", 3);
上述特定日志调用的最小间隔是 3000ms。
2022-04-10 21:27:10.996+08 0ms 0 INFO main [src/main.rs:29] limit running 3s !
2022-04-10 21:27:15.996+08 0ms 2 INFO main [src/main.rs:29] limit running 3s !
上面的数字 2 显示了自上次日志以来丢弃了多少条日志消息。
日志轮换
ftlog
支持本地时区的日志轮换。可用的轮换周期有
- 分钟
Period::Minute
- 小时
Period::Hour
- 天
Period::Day
- 月
Period::Month
- 年
Period::Year
日志轮换配置在 FileAppender
中,时间戳附加到文件名末尾
use ftlog::appender::{FileAppender, Period};
let logger = ftlog::builder()
.root(
FileAppender::builder()
.path("./mylog.log")
.rotate(Period::Minute)
.build(),
)
.build()
.unwrap();
let _guard = logger.init().unwrap();
如果日志文件配置为按分钟分割,日志文件名格式为 mylog-{MMMM}{YY}{DD}{hh}{mm}.log
。按天分割时,日志文件名类似 mylog-{MMMM}{YY}{DD}.log
。
日志文件名示例
$ ls
# by minute
current-20221026T1351.log
# by hour
current-20221026T13.log
# by day
current-20221026.log
# by month
current-202210.log
# by year
current-2022.log
# omitting extension (e.g. "./log") will add datetime to the end of log filename
log-20221026T1351
清理旧日志
启用日志轮转后,可以使用 FileAppender::rotate_with_expire
方法清理旧日志以释放磁盘空间,或在构建器中使用时设置 expire(Duration)
。
ftlog
首先找到由 ftlog
生成的文件,然后根据最后修改时间清理旧日志。通过文件名和添加的日期时间匹配来查找生成的日志。
注意:任何匹配该模式的文件都将被删除。
use ftlog::{appender::{Period, FileAppender, Duration}};
// clean files named like `current-\d{8}T\d{4}.log`.
// files like `another-\d{8}T\d{4}.log` or `current-\d{8}T\d{4}` will not be deleted, since the filenames' stem do not match.
// files like `current-\d{8}.log` will remains either, since the rotation durations do not match.
// Rotate every day, clean stale logs that were modified 7 days ago on each rotation
let appender = FileAppender::rotate_with_expire("./current.log", Period::Day, Duration::days(7));
let logger = ftlog::builder()
.root(appender)
.build()
.unwrap();
let _guard = logger.init().unwrap();
特性
-
tsc 使用 TSC 作为时钟源以实现更高的性能而不会损失精度。
TSC 在某些条件下提供了获取当前时间的最准确和最经济的方法
- CPU 频率必须保持恒定
- 必须具有 x86/x86_64 架构的 CPU,因为 TSC 是 x86/x86_64 特定的寄存器。
- 永远不要挂起
当前特性还要求构建目标 必须是 LINUX。否则将回退到一个速度快但精度较低的实现。
时区
出于性能考虑,时区在日志器构建时检测一次,并在以后的每个日志消息中使用它。这部分是由于时区检测的成本高昂,部分是由于 Linux 中多线程程序中底层系统调用的不安全性。
还建议使用 UTC 来进一步避免将时间戳转换为时区。
性能
Rust:1.67.0-nightly
消息类型 | Apple M1 Pro, 3.2GHz | AMD EPYC 7T83, 3.2GHz | |
---|---|---|---|
ftlog |
静态字符串 | 75 ns/iter | 385 ns/iter |
ftlog |
使用 i32 | 106 ns/iter | 491 ns/iter |
env_logger 输出到文件 |
静态字符串 | 1,674 ns/iter | 1,142 ns/iter |
env_logger 输出到文件 |
使用 i32 | 1,681 ns/iter | 1,179 ns/iter |
env_logger 使用 BufWriter 输出到文件 |
静态字符串 | 279 ns/iter | 550 ns/iter |
env_logger 使用 BufWriter 输出到文件 |
使用 i32 | 278 ns/iter | 565 ns/iter |
许可证:MIT OR Apache-2.0
依赖关系
~3–4MB
~70K SLoC