人人都会AI编程

20.5 线上问题排查:错误栈、崩溃日志、用户行为回溯

更新时间:2026-07-11

线上问题排查是桌面应用稳定性的最后一道防线。Tauri 的前端与 Rust 后端分属两个进程,排查时需要分别覆盖 JavaScript 错误、Rust 崩溃以及用户操作线索。以下是一套轻量且实用的方案。


1. 前端错误栈:统一捕获与上报

在前端,所有未处理的错误和 Promise 拒绝都应被捕获。在项目入口文件中注册全局监听:

window.addEventListener('error', (event) => {
  const { message, filename, lineno, colno, error } = event;
  // 通过 IPC 发送给 Rust 后端记录或上报
  window.__TAURI_INTERNALS__.invoke('log_frontend_error', {
    message,
    stack: error?.stack,
    location: `${filename}:${lineno}:${colno}`,
    timestamp: Date.now()
  });
});

window.addEventListener('unhandledrejection', (event) => {
  window.__TAURI_INTERNALS__.invoke('log_frontend_error', {
    message: event.reason?.message || String(event.reason),
    stack: event.reason?.stack,
    timestamp: Date.now()
  });
});

在 Rust 后端定义一个对应的命令,将错误写入本地日志文件或上报至远程服务。

真实建议:线上环境务必过滤掉 Script error. 这类跨域受限的错误(可通过 CORS 头解决)。同时避免在循环或高频场景中上报,设置本地缓冲区与间隔发送。


2. Rust 崩溃日志:拦截 panic,留下完整现场

Rust 的 panic! 或 Option/Result 的 unwrap 会导致进程崩溃。可以通过设置自定义 panic hook 捕获堆栈并持久化。

main 函数的最顶端(tauri::Builder 之前)设置:

use std::panic;
use std::fs::OpenOptions;
use std::io::Write;

panic::set_hook(Box::new(|info| {
    let backtrace = std::backtrace::Backtrace::force_capture();
    let msg = format!(
        "Panic occurred at {}\nPayload: {:?}\nBacktrace:\n{}",
        info.location().map_or("unknown".into(), |l| l.to_string()),
        info.payload().downcast_ref::<&str>().unwrap_or(&"None"),
        backtrace
    );

    // 紧急写入崩溃日志文件
    if let Ok(mut f) = OpenOptions::new().create(true).append(true).open("crash.log") {
        let _ = writeln!(f, "{}", msg);
    }

    // 继续默认行为(打印到 stderr,生成核心转储等)
    std::panic::default_hook(info);
}));

需要 Cargo.toml 中开启 backtrace 特性(通常默认开启),并在构建 profile 里保留调试符号,否则堆栈只有地址。

平台特有的崩溃转储

  • Windows:默认生成 WER 报告,但也可在代码中调用 mini_dumper 等 crate 生成 minidump。
  • macOS:系统 Crash Reporter 会记录,保留 .dSYM 文件方便符号化。
  • Linux:设置 RUST_BACKTRACE=fullcoredumpctl 查看核转储。

与 Sentry 等服务的集成
引入 sentrysentry-tauri 插件,崩溃时自动上报上下文、面包屑和文件信息,省去自建管道。对团队来说这是最快成熟的路径。


3. 用户行为回溯:重现问题的“录像带”

知道错误发生前用户做了什么是排查的半壁江山。可以从三个层面收集:

(1)前端操作日志
监听关键交互(路由跳转、按钮点击、表单提交等),通过 invoke 异步传递给 Rust,Rust 追加到循环日志文件。注意脱敏:密码、令牌等字段必须裁剪后再记录。

(2)Rust IPC 命令日志
在自定义命令入口处,使用中间件式封装记录每次调用:

#[tauri::command]
fn my_command(param: String) -> Result<String, String> {
    log_operation("my_command", &param); // 记录参数(脱敏后)
    // 实际逻辑...
}

fn log_operation(cmd: &str, params: &str) {
    if let Ok(mut f) = OpenOptions::new().append(true).open("operation.log") {
        let _ = writeln!(f, "[{}] {} {}", chrono::Utc::now().format("%H:%M:%S"), cmd, params);
    }
}

(3)会话回放(可选)
如果需要直观复现,可使用 rrweb 在前端录制 DOM 快照与操作,上传至服务端。Tauri 可将录制的数据保存到临时目录,仅在用户授权后上传。不过这会增加体积与性能开销,适合严重错误场景。


4. 最小可行方案(上线直接用)

开发阶段

  • 前端:控制台保留所有错误,console.trace() 辅助。
  • Rust:RUST_BACKTRACE=1 环境变量启用完整堆栈。

生产环境

  • 前端错误通过 invoke 写入本地 logs/frontend-error.log
  • Rust panic hook 写入 logs/crash.log
  • 额外加入 log crate + env_logger,配合 RUST_LOG=trace 按需开启调试日志。
  • 若需远程上报,统一使用一个 report_event 命令,在闲时批量上传,失败则暂存本地。

隐私与安全
所有本地日志都应存储在应用专属目录(tauri::api::path::app_log_dir),避免泄露用户隐私。上报前对 IP、路径、输入内容做模糊化处理。


通过这套组合,即使没有复杂监控系统,你也能快速定位“用户崩溃了,我们不知道发生了什么”的尴尬局面,让线上问题不再神秘。