本系列教程对应的代码已开源在 Github zeedle
本篇文章介绍如何将音乐播放器的运行情况持久化记录到日志文件中,这对分析程序意外情况出现的原因很有帮助!
使用最常用的env_logger
,将其添加到Cargo.toml
中:
toml
env_logger = "0.11.8"
将日志同时输出到控制台和文件
与最普通的单输出不同,我们希望同时将日志输出到控制台
和日志文件
中,这是因为:
- 在调试程序的时候,将日志输出到
控制台
较为简便和迅速,打开日志文件进行观察太费时间了 - 程序打包分发之后,以
release
模式运行,我们不希望出现一个黑框控制台
,所以只能持久化到文件中,出现BUG时打包日志文件反馈给开发者
env_logger
不直接支持多目标输出,需要自己定义输出Target
:
rust
use std::{
fs,
io::{self, Write},
path::{Path, PathBuf},
};
use env_logger::Target;
use log::LevelFilter;
struct MultiWriter {
console: Box<dyn Write + Send>,
file: Box<dyn Write + Send>,
}
impl Write for MultiWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.console.write(buf)?;
self.file.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.console.flush()?;
self.file.flush()
}
}
fn get_log_path() -> PathBuf {
let f_name = ".zeedle.log";
if let Some(mut p) = home::home_dir() {
p.push(f_name);
p
} else {
PathBuf::from(f_name)
}
}
pub fn init_default_logger(path: Option<impl AsRef<Path>>) {
let log_path = if let Some(p) = path {
p.as_ref().to_path_buf()
} else {
get_log_path()
};
if log_path.exists() {
if fs::metadata(&log_path).unwrap().len() > 1024 * 1024 * 10 {
fs::remove_file(&log_path).expect("Failed to remove old log file");
}
}
let log_file = fs::OpenOptions::new()
.create(true)
.write(true)
.append(true)
.open(&log_path)
.expect("can't open this file!");
let log_target = Box::new(MultiWriter {
console: Box::new(io::stdout()),
file: Box::new(log_file),
});
env_logger::builder()
.format(move |buf, record| {
writeln!(
buf,
"[{} | {} | {}:{}] --> {}",
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
record.file().unwrap_or("unknown"),
record.line().unwrap_or(0),
record.args()
)
})
.filter(None, LevelFilter::Info) // 设置日志级别为Info
.target(Target::Pipe(log_target))
.init();
}
上述MultiWriter
即为自己定义的日志输出Target
,在输出时同时向控制台和指定文件写入日志消息,在初始化时指定此Target
即可。
重定向Panic
消息输出
对于GUI程序,在release
模式下控制台不可见,如果不定向Panic
消息,在GUI窗口异常退出时,将无法记录Panic
的发生原因,给开发者修复问题增加难度。好在,Rust直接允许指定发生Panic
时自定义回调函数:
rust
fn main() {
...
logger::init_default_logger(None::<PathBuf>);
// when panics happen, auto port errors to log
std::panic::set_hook(Box::new(|info| {
log::error!("{}", info);
}));
...
}