【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);
    }));
    
    ...
}
相关推荐
superman超哥11 小时前
Rust 枚举与结构体定义:类型系统的代数基石
rust·类型系统·rust枚举与结果体定义·代数基石
superman超哥12 小时前
Rust 基本数据类型:类型安全的底层探索
开发语言·rust·rust基本数据类型·rust底层探索·类型安全
苏近之13 小时前
Rust 中实现定时任务管理
后端·架构·rust
该用户已不存在13 小时前
7个构建高性能后端的 Rust 必备库
后端·rust
superman超哥18 小时前
Rust impl 块的组织方式:模块化设计的艺术
开发语言·后端·rust·模块化设计·rust impl块·impl块
superman超哥19 小时前
Rust 表达式与语句的区别:函数式思维与控制流设计
开发语言·后端·rust·rust表达式·rust语句·函数式思维·控制流设计
superman超哥20 小时前
Rust Trait 定义与实现:类型系统的多态基石
开发语言·rust·类型系统·rust trait·定义与实现·多态基石
superman超哥20 小时前
Rust 方法与关联函数:所有权语义下的行为设计
开发语言·rust·rust底层探索·rust方法与关联函数·所有权语义下的行为设计
superman超哥20 小时前
Rust 复合类型:元组与数组的内存布局与性能优化
开发语言·后端·性能优化·rust·内存布局·rust复合类型·元组与数组
superman超哥21 小时前
Rust 函数定义与参数传递:所有权系统下的设计艺术
开发语言·rust·设计艺术·rust函数定义·rust参数传递