use std::{
panic::{self, PanicHookInfo},
path::PathBuf,
sync::{
OnceLock,
atomic::{AtomicBool, Ordering},
},
};
static PANIC_HANDLER_INSTALLED: AtomicBool = AtomicBool::new(false);
pub type RecoveryCallback = Box<dyn Fn(&PanicHookInfo<'_>) + Send + Sync>;
static RECOVERY_CALLBACK: OnceLock<RecoveryCallback> = OnceLock::new();
#[derive(Debug, Default)]
pub struct DebugContext {
pub server_logs: Option<String>,
pub client_dump_paths: Vec<PathBuf>,
}
pub type DebugContextCallback = Box<dyn Fn() -> DebugContext + Send + Sync>;
static DEBUG_CONTEXT_CALLBACK: OnceLock<DebugContextCallback> = OnceLock::new();
pub fn set_debug_context_callback(callback: DebugContextCallback) {
let _ = DEBUG_CONTEXT_CALLBACK.set(callback);
}
pub fn install_panic_handler() {
if PANIC_HANDLER_INSTALLED.swap(true, Ordering::SeqCst) {
return; }
let default_hook = panic::take_hook();
panic::set_hook(Box::new(
#[cfg_attr(coverage_nightly, coverage(off))]
move |info| {
let mut report = super::report::generate_crash_report(info);
if let Some(callback) = DEBUG_CONTEXT_CALLBACK.get() {
let ctx = callback();
report.server_logs = ctx.server_logs;
report.client_dump_paths = ctx.client_dump_paths;
}
if let Some(callback) = RECOVERY_CALLBACK.get() {
callback(info);
}
crate::pr_err!("PANIC: {}", report.summary());
if let Err(e) = report.write_to_file() {
eprintln!("Failed to write crash report: {e}");
}
default_hook(info);
},
));
}
pub fn set_recovery_callback(callback: RecoveryCallback) {
let _ = RECOVERY_CALLBACK.set(callback);
}
#[must_use]
pub fn is_handler_installed() -> bool {
PANIC_HANDLER_INSTALLED.load(Ordering::SeqCst)
}