人人都会AI编程

20.3 异常与崩溃处理

更新时间:2026-07-11

在 Tauri 应用中,异常和崩溃可能来自两个完全不同的世界:一是前端 WebView 中的 JavaScript 错误,二是 Rust 后端的 panic 或未捕获的异常。处理得好,应用可以优雅降级;处理不好,轻则白屏无响应,重则进程直接退出,用户一头雾水。下面从这两个方向分别说明。


前端异常处理

前端错误通常表现为 UI 渲染异常、接口调用失败或未处理的 Promise rejection。Tauri 本身不会捕获这些错误,所以需要开发者主动兜底。

1. 全局错误捕获
在入口 JavaScript 文件中注册全局错误处理,避免因单个组件崩溃导致整个页面不可用:

window.addEventListener('error', (event) => {
  console.error('全局错误:', event.error);
  // 可以在这里调用 Rust 命令上报错误日志
  // or显示友好的错误提示界面
});

window.addEventListener('unhandledrejection', (event) => {
  console.error('未处理的 Promise 拒绝:', event.reason);
  event.preventDefault(); // 阻止默认的控制台警告
});

如果使用 React 等框架,可以配合错误边界(Error Boundary)实现组件级崩溃隔离,即使某个组件报错,其他部分依然可操作。

2. 错误信息回传 Rust
很多时候你需要把前端的异常收集起来,写入日志文件或发送到远程服务器。通过 Tauri 的命令可以安全地实现:

import { invoke } from '@tauri-apps/api/core';

window.addEventListener('error', async (event) => {
  await invoke('log_frontend_error', { 
    message: event.error?.message || '未知错误',
    stack: event.error?.stack || ''
  });
});

对应的 Rust 命令:

#[tauri::command]
fn log_frontend_error(message: String, stack: String) {
    eprintln!("前端错误: {}\n{}", message, stack);
    // 写入文件或上报
}

3. 降级 UI 展示
在发生致命错误后,可以展示一个静态的“出错页面”,而不是让用户面对白屏。Tauri 允许加载本地 HTML 文件作为降级界面:

window.addEventListener('error', () => {
  // 简单替换 body 内容
  document.body.innerHTML = '<div style="padding:20px;text-align:center;"><h2>应用遇到错误,请重启</h2></div>';
});

Rust 后端异常处理

Rust 的 panics 如果未捕获,会直接终止整个进程,这比前端崩溃更严重。因此必须做好防护。

1. 设置全局 panic hook
在 Rust 入口(如 main 函数)中注册自定义的 panic 处理,让崩溃至少输出一个可追踪的记录:

use std::panic;

fn main() {
    // 设置 panic hook,避免完全无声地崩溃
    panic::set_hook(Box::new(|info| {
        let location = info.location().map(|l| format!("{}:{}", l.file(), l.line())).unwrap_or_default();
        let payload = info.payload();
        let msg = if let Some(s) = payload.downcast_ref::<&str>() {
            *s
        } else if let Some(s) = payload.downcast_ref::<String>() {
            s.as_str()
        } else {
            "未知 panic"
        };
        eprintln!("进程崩溃: {} in {}", msg, location);
        // 可尝试写入日志文件,再显示对话框通知用户
    }));

    // 其余 tauri::Builder 等初始化代码
}

注意:设置了 hook 后,默认的 panic 打印会被覆盖,但进程仍旧会退出。如果需要更高级的恢复,可以使用 std::panic::catch_unwind

2. 用 catch_unwind 保护关键调用
对于可能触发 panics 的第三方库或复杂逻辑,可以在命令里包裹一层,防止整个应用崩溃:

#[tauri::command]
fn risky_operation() -> Result<String, String> {
    std::panic::catch_unwind(|| {
        // 可能 panic 的代码
        Some("成功")
    })
    .map_err(|_| "操作异常".to_string())
    .and_then(|res| res.ok_or("未知错误".to_string()))
}

这样,即使内部 panic,命令也只是返回一个错误字符串给前端,而不会杀掉进程。

3. 日志记录与持久化
无论是 panic 还是普通错误,都应该持久化到本地文件,方便用户反馈时提供日志。可以使用 logsimplelog 等 crate:

use simplelog::*;
use std::fs::File;

fn setup_logger() {
    WriteLogger::init(
        LevelFilter::Info,
        Config::default(),
        File::create("app.log").unwrap(),
    ).ok();
}

然后在 panic hook 或关键位置调用 log::error! 记录详细上下文。


崩溃后的用户通知

当 Rust 进程意外终止时,通常伴随 WebView 窗口的消失。可以在启动时使用系统对话框提醒用户上次是否异常退出,或者在 panic hook 中调用系统的消息框(Windows 的 MessageBoxW、macOS 的 NSAlert)。但注意在 panic 状态下直接调用 UI 可能不安全,更好的做法是在重启时检查退出标记。

一个简单方法:正常退出时删除一个特定文件,panic 时不删除。下次启动时检测该文件是否存在,若存在则表示上一次是异常退出,可以给出提示。

// 启动时
if std::path::Path::new("crash_flag").exists() {
    // 弹出提示或者通知前端显示恢复界面
    std::fs::remove_file("crash_flag").ok();
}
// 正常退出时确保删除此文件
// 在 panic hook 中创建这个文件

一句话总结:前端兜底保证界面不白屏,Rust 兜底保证进程不莫名消失,再辅以统一的日志采集和友好提示,就能让 Tauri 应用在面对异常和崩溃时,依然给用户留下可靠的印象。