如何通过Rust的catch_unwind获取panic信息(含栈回溯内容)
Rust多线程环境下通过
catch_unwind获取panic栈回溯方案 方案可行性
你提到的set_hook+线程局部变量+catch_unwind的组合思路完全可行。全局panic hook可以捕获到完整的panic上下文和栈回溯,线程局部存储(TLS)天然隔离不同线程的数据,完全适配多线程并发场景,不会出现不同线程panic信息互相串的问题。
实现逻辑
- 利用线程局部变量存储当前线程的panic信息(包括载荷内容、栈回溯),线程之间数据完全隔离,无并发冲突
- 全局注册自定义panic hook,触发时将当前panic的完整信息写入当前线程的局部变量中
- 封装自定义的
catch_unwind包装方法,执行目标逻辑前清空当前线程局部变量的旧数据,捕获到panic后直接读取线程局部变量中存储的完整信息返回
注意事项
- 需保证程序运行时设置了环境变量
RUST_BACKTRACE=1或RUST_BACKTRACE=full,才能捕获到完整的栈回溯 - 如果原有逻辑已经注册了panic hook,需要在新的自定义hook中先调用原有hook,避免原有逻辑失效
- 被
catch_unwind包裹的闭包需要满足UnwindSafetrait约束
示例代码
use std::backtrace::Backtrace; use std::cell::RefCell; use std::panic; use std::thread; // 定义线程局部存储,存储当前线程的panic信息 thread_local! { static PANIC_INFO: RefCell<Option<(String, Backtrace)>> = RefCell::new(None); } fn init_panic_hook() { // 保存原有默认hook let default_hook = panic::take_hook(); panic::set_hook(Box::new(move |info| { // 先调用原有hook,保证默认的panic打印逻辑正常执行 default_hook(info); // 构造panic信息字符串 let payload = info.payload() .downcast_ref::<&str>() .unwrap_or(&"unknown panic payload") .to_string(); // 捕获当前栈回溯 let backtrace = Backtrace::capture(); // 写入当前线程的局部变量 PANIC_INFO.with(|cell| { *cell.borrow_mut() = Some((payload, backtrace)); }); })); } // 封装自定义catch_unwind,返回结果附带panic信息和栈回溯 fn catch_unwind_with_backtrace<F, R>(f: F) -> Result<R, (String, Backtrace)> where F: FnOnce() -> R + panic::UnwindSafe, { // 执行前先清空当前线程的旧panic信息 PANIC_INFO.with(|cell| { *cell.borrow_mut() = None; }); match panic::catch_unwind(f) { Ok(res) => Ok(res), Err(_) => { // 读取存储的panic信息 let panic_info = PANIC_INFO.with(|cell| cell.borrow_mut().take()) .unwrap_or_else(|| ("failed to get panic info".to_string(), Backtrace::disabled())); Err(panic_info) } } } // 多线程场景测试 fn main() { init_panic_hook(); // 启动10个并发线程测试 let mut handles = Vec::with_capacity(10); for i in 0..10 { handles.push(thread::spawn(move || { let res = catch_unwind_with_backtrace(|| { if i % 2 == 0 { panic!("test panic in thread {}", i); } i * 2 }); match res { Ok(v) => println!("thread {} exec success, result: {}", i, v), Err((msg, backtrace)) => { println!("thread {} panic, msg: {}", i, msg); println!("backtrace:\n{}", backtrace); } } })); } // 等待所有线程执行完成 for handle in handles { handle.join().unwrap(); } }
代码说明
示例代码兼容多线程并发场景,每个线程的panic信息只会存在自己的线程局部变量中,不会互相干扰;保留了Rust默认的panic打印逻辑,不会影响原有错误输出;捕获到的栈回溯和直接使用set_hook拿到的内容完全一致,包含完整的调用栈信息。
内容的提问来源于stack exchange,提问作者ch271828n
相关产品推荐
相关产品推荐

