【Rust GUI开发入门】编写一个本地音乐播放器(15. 记录运行日志)

本系列教程对应的代码已开源在 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);
    }));
    
    ...
}
相关推荐
DongLi012 天前
rustlings 学习笔记 -- exercises/05_vecs
rust
番茄灭世神3 天前
Rust学习笔记第2篇
rust·编程语言
shimly1234563 天前
(done) 速通 rustlings(20) 错误处理1 --- 不涉及Traits
rust
shimly1234563 天前
(done) 速通 rustlings(19) Option
rust
@atweiwei3 天前
rust所有权机制详解
开发语言·数据结构·后端·rust·内存·所有权
shimly1234563 天前
(done) 速通 rustlings(24) 错误处理2 --- 涉及Traits
rust
shimly1234563 天前
(done) 速通 rustlings(23) 特性 Traits
rust
shimly1234563 天前
(done) 速通 rustlings(17) 哈希表
rust
shimly1234563 天前
(done) 速通 rustlings(15) 字符串
rust
shimly1234564 天前
(done) 速通 rustlings(22) 泛型
rust