2次发布
0.3.1 | 2024年2月16日 |
---|---|
0.3.0 | 2024年2月14日 |
#401 in 调试
每月47次下载
在hitdns中使用
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(),
)
// Do not convert to local timezone for timestamp, this does not affect worker thread,
// but can boost log thread performance (higher throughput).
.utc()
// 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}T{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
生成的文件,并通过最后修改时间清理过期的日志。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–4.5MB
~72K SLoC