Rust 日志系统实践总结
(上次更新:2018-12-20 新增【日志颜色】)基于log、env_logger、fern等的使用总结,详细配置建议参考官方说明。
给工程添加第三方日志库依赖
给Cargo.toml文件加上如下配置,log基本为Rust项目日志需求的标配库,env_logger提供了具体实现,类似策略模式:log定义操作,env_logger实现具体行为,很方便切换另一个实现了log所定义接口的库,比如daboross/fern。
[dependencies]
log = "0.4.0"
env_logger = "0.6.0"
env_log配置
下面描述我们项目对env_log所作的配置。问题:修改format导致终端执行时日志无颜色。
配置输出时间为本地时间
env_logger默认用0时区,而北京是东8区,每次日志输出都少8小时,时间没对上不方便分析日志。0时区举个例子:
INFO 2018-11-18T02:00:08Z: webgpu-native::registry: env_logger initialized.
下面给出env_logger输出本地时间的示例代码,参考了DCjanus/nabu,他用flexi_logger,略调整即可用于env_logger。加上更多自定义信息的关键是修改writeln!
宏。
#[macro_use]
extern crate log;
extern crate chrono;
extern crate env_logger;
fn init_log() {
use chrono::Local;
use std::io::Write;
let env = env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "trace");
env_logger::Builder::from_env(env)
.format(|buf, record| {
writeln!(
buf,
"{} {} [{}] {}",
Local::now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
record.module_path().unwrap_or("<unnamed>"),
&record.args()
)
})
.init();
info!("env_logger initialized.");
}
以上代码需在fn main()开始或lazy_static开始,否则刚开始部分日志不受新配置影响。 放在lazy_static的日志配置需要手动 激活,比如
// 定义
lazy_static! {
pub(crate) static ref HUB: Hub = {
init_log();
Hub::default()
};
}
// 在某个入口函数中先访问HUB,“强迫”它执行lazy_static代码块
fn entry_point() {
&*HUB; // “强迫”执行lazy_static代码块
HUB.some_method(); // 里面的info!()等可正常输出到文件或控制台
}
以东8区为例,执行显示:
// 修改前
[2018-11-18T02:00:08Z INFO webgpu-native::registry] env_logger initialized.
// 修改后
2018-11-18 09:27:43 INFO [webgpu-native::registry] env_logger initialized.
日志添加行号
writeln!(
buf,
"{} {} [{}:{}] {}",
Local::now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
record.module_path().unwrap_or("<unnamed>"),
record.line().unwrap_or(0),
&record.args()
)
执行显示:
2018-11-18 10:38:41 INFO [webgpu-native::registry:87] env_logger initialized.
日志添加文件名
writeln!(
buf,
"{} {} [{}:{}:{}] {}",
Local::now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
record.module_path().unwrap_or("<unnamed>"),
record.line().unwrap_or(0),
&record.args()
)
执行显示:
2018-11-18 10:38:48 INFO [webgpu-native::registry:webgpu-native/src/registry.rs:87] env_logger initialized.
日志级别左/右对齐
- 左对齐
writeln!( buf, "{:<5} {} [{}:{}] {}", record.level(), // same as previous content )
- 右对齐
writeln!( buf, "{:>5} {} [{}:{}] {}", record.level(), // same as previous content )
参考:What is the easiest way to pad a string with 0 to the left?
日志颜色
不修改format
,env_logger默认用不同颜色标识level日志。前面的修改导致这一特性“失效”,看着不直观,当然CLion等可以用Grep Console插件给日志上色,如果是Terminal中执行,那还得我们修改format加上颜色才行。以下内容参考env_logger-0.6.0/src/fmt/mod.rs的DefaultFormatter
源码,感谢 @齿轮哥 指导。
use std::io::Write;
let env = env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "trace");
let mut builder = env_logger::Builder::from_env(env);
println!("builder = {:?}", builder);
builder
.format(|buf, record| {
let level = { buf.default_styled_level(record.level()) };
write!(buf, "{}", format_args!("{:>5}", level));
writeln!(buf, " {}", &record.args())
})
.init();
过滤日志级别
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "trace");
filter_or()
可配置如下内置值过滤不同级别的日志,其实我们也可自行添加filter tag:
- "trace"
- "info"
- "debug"
- "warn"
- "error"
也可以在程序执行时传递命令行参数进行过滤:
$ RUST_LOG=info ./main
[2018-11-03T06:09:06Z INFO default] starting up
组合过滤条件
添加depX=Y,可同时过滤多个条件、以其中级别最高的为准,低于最高级都没法通过,比如下面将显示级别高于info、也高于debug级别的日志 = 要高于info级别,干脆设置成info就行了,真是多此一举。
RUST_LOG=info,dep1=debug ./main
动态过滤信息
在复杂项目中往往存在多个模块,作为其中一个模块的开发者,为了定位自己负责模块的问题,过滤掉其他模块的日志是很常见的需求,由于整体项目通常使用同一个日志库,逐行注释其他模块的日志输出显然是不可理的行为。另外,虽然控制台可以做过滤处理,多条件的过滤规则编写起来也有难度,而且可能日志查看系统不支持这种操作。其实,我们可以给前面一直在修改的format()
加上过滤逻辑,比如:
format(|buf, record| {
// special format for debug messages coming from our own crate.
if record.level() > log::LevelFilter::Info && record.target() == "my_module" {
write!(...)
} else if /* some condition */ {
write!(...)
} else if /* some condition 2*/ {
write!(...)
} else {
write!(...)
}
}
过滤逻辑的实现可参考fern/cmd-program.rs。
fern,env_logger的另一个选择
Simple, efficient logging for Rust
fern配置起来更直观(所下所示),目前我还没测试它与env_logger的性能差异。
// Configure logger at runtime
fern::Dispatch::new()
// Perform allocation-free log formatting
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
record.level(),
message
))
})
// Add blanket level filter -
.level(log::LevelFilter::Debug)
// - and per-module overrides
.level_for("hyper", log::LevelFilter::Info)
// Output to stdout, files, and other Dispatch configurations
.chain(std::io::stdout())
.chain(fern::log_file("output.log")?)
// Apply globally
.apply()?;
// and log using log crate macros!
info!("helllo, world!");
提高日志性能
todo
转载自:https://juejin.cn/post/6844903718459015175