use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
type PanicFlushCallback = Arc<Mutex<Option<Box<dyn Fn() + Send + Sync>>>>;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PanicInfo {
pub message: String,
pub timestamp: u64,
pub location: Option<String>,
pub session_id: String,
}
static PANIC_COLLECTOR: once_cell::sync::Lazy<Arc<Mutex<Vec<PanicInfo>>>> =
once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
static SESSION_ID: once_cell::sync::Lazy<Arc<Mutex<String>>> =
once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(String::from("unknown"))));
static PANIC_FLUSH_CALLBACK: once_cell::sync::Lazy<PanicFlushCallback> =
once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(None)));
pub fn set_session_id(session_id: String) {
if let Ok(mut id) = SESSION_ID.lock() {
*id = session_id;
}
}
pub fn set_panic_flush_callback<F>(callback: F)
where
F: Fn() + Send + Sync + 'static,
{
if let Ok(mut cb) = PANIC_FLUSH_CALLBACK.lock() {
*cb = Some(Box::new(callback));
}
}
pub fn setup_panic_handler() {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
let message = if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = panic_info.payload().downcast_ref::<String>() {
s.clone()
} else {
"Unknown panic".to_string()
};
let location = panic_info
.location()
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()));
let session_id = SESSION_ID
.lock()
.ok()
.map(|id| id.clone())
.unwrap_or_else(|| String::from("unknown"));
let panic_data = PanicInfo {
message: message.clone(),
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
location: location.clone(),
session_id: session_id.clone(),
};
if let Ok(mut collector) = PANIC_COLLECTOR.lock() {
collector.push(panic_data);
}
eprintln!("Fleet: Panic captured - {}", message);
eprintln!("Fleet: Session ID - {}", session_id);
if let Some(loc) = location {
eprintln!("Fleet: Panic location - {}", loc);
}
if let Ok(cb_lock) = PANIC_FLUSH_CALLBACK.lock()
&& let Some(ref callback) = *cb_lock
{
eprintln!("Fleet: Flushing panic telemetry...");
callback();
#[cfg(not(target_arch = "wasm32"))]
{
std::thread::sleep(std::time::Duration::from_millis(500));
}
}
default_hook(panic_info);
}));
}
pub fn get_panics() -> Vec<PanicInfo> {
PANIC_COLLECTOR
.lock()
.ok()
.and_then(|mut collector| {
if collector.is_empty() {
None
} else {
Some(std::mem::take(&mut *collector))
}
})
.unwrap_or_default()
}
#[allow(dead_code)]
pub fn clear_panics() {
if let Ok(mut collector) = PANIC_COLLECTOR.lock() {
collector.clear();
}
}