【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);
    }));
    
    ...
}
相关推荐
h***8561 小时前
Rust在Web中的前端开发
开发语言·前端·rust
Rust语言中文社区2 小时前
【Rust日报】 walrus:分布式消息流平台,比 Kafka 快
开发语言·分布式·后端·rust·kafka
武子康3 小时前
AI研究-133 Java vs Kotlin/Go/Rust/Python/Node:2025 详细对比分析 定位与取舍指南
java·javascript·python·golang·rust·kotlin·node
mit6.8245 小时前
C 语言仓库引入 Rust: MCUboot 为例
开发语言·rust
星释5 小时前
Rust 练习册 99:让数字开口说话
开发语言·后端·rust
我发在否5 小时前
Rust > 牛客OJ在线编程常见输入输出练习场
算法·rust
y***54886 小时前
Rust在嵌入式中的实时操作系统
开发语言·后端·rust
苦难之路6 小时前
rCore1
rust
x***B4117 小时前
Rust unsafe代码规范
开发语言·rust·代码规范
alwaysrun7 小时前
Rust多线程编程之Thread与Channel
rust·channel·bus·mpsc·crossbeam