【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);
    }));
    
    ...
}
相关推荐
传奇开心果编程1 小时前
【Rust入门知识点学与练】第11课:HashMap 键值对集合
开发语言·学习·rust
yume_sibai1 小时前
03-Rust 函数式编程特性(闭包 + Iterator + Option/Result + 链式调用)
开发语言·后端·rust
2601_962297483 小时前
一、Python GUI 的十字路口:免费凑活还是付费破局
python·gui·tkinter·customtkinter·bravurasdk
传奇开心果编程4 小时前
【Rust入门知识点学与练】第13课:枚举 Enum
开发语言·学习·rust
小灰灰搞电子4 小时前
Rust+Slint 实现点击水波纹扩散效果按钮源码分享
rust·slint·水波纹按钮
后台模板学习5 小时前
Rust vs Python 在用户认证模块中的实现:底层
开发语言·python·rust
传奇开心果编程15 小时前
【Rust入门知识点学与练】第4课:条件判断 if / else
开发语言·学习·rust
2601_9622946119 小时前
AI 基础设施的“去 Python 化“:Rust 与 C# 的两条替代路径
ai·rust·c·基础设施·去python化
传奇开心果编程19 小时前
【Rust入门知识点学与练】第10课:Option 和 Result(错误处理入门)
开发语言·学习·rust
传奇开心果编程21 小时前
【Rust入门知识点学与练】第3课:String 与 &str 的区别
开发语言·学习·rust