bevy_fleet/
panic_handler.rs

1use serde::{Deserialize, Serialize};
2use std::sync::{Arc, Mutex};
3
4type PanicFlushCallback = Arc<Mutex<Option<Box<dyn Fn() + Send + Sync>>>>;
5
6/// Panic information
7#[derive(Clone, Debug, Serialize, Deserialize)]
8pub struct PanicInfo {
9    pub message: String,
10    pub timestamp: u64,
11    pub location: Option<String>,
12    pub session_id: String,
13}
14
15/// Global panic collector
16static PANIC_COLLECTOR: once_cell::sync::Lazy<Arc<Mutex<Vec<PanicInfo>>>> =
17    once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
18
19/// Global session ID storage for panic handler
20static SESSION_ID: once_cell::sync::Lazy<Arc<Mutex<String>>> =
21    once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(String::from("unknown"))));
22
23/// Global panic flush callback storage
24static PANIC_FLUSH_CALLBACK: once_cell::sync::Lazy<PanicFlushCallback> =
25    once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(None)));
26
27/// Sets the session ID for panic reporting
28pub fn set_session_id(session_id: String) {
29    if let Ok(mut id) = SESSION_ID.lock() {
30        *id = session_id;
31    }
32}
33
34/// Sets a callback to flush panics immediately when they occur
35pub fn set_panic_flush_callback<F>(callback: F)
36where
37    F: Fn() + Send + Sync + 'static,
38{
39    if let Ok(mut cb) = PANIC_FLUSH_CALLBACK.lock() {
40        *cb = Some(Box::new(callback));
41    }
42}
43
44/// Sets up the panic handler to capture panics
45pub fn setup_panic_handler() {
46    let default_hook = std::panic::take_hook();
47
48    std::panic::set_hook(Box::new(move |panic_info| {
49        let message = if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
50            s.to_string()
51        } else if let Some(s) = panic_info.payload().downcast_ref::<String>() {
52            s.clone()
53        } else {
54            "Unknown panic".to_string()
55        };
56
57        let location = panic_info
58            .location()
59            .map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()));
60
61        // Get the current session ID
62        let session_id = SESSION_ID
63            .lock()
64            .ok()
65            .map(|id| id.clone())
66            .unwrap_or_else(|| String::from("unknown"));
67
68        let panic_data = PanicInfo {
69            message: message.clone(),
70            timestamp: std::time::SystemTime::now()
71                .duration_since(std::time::UNIX_EPOCH)
72                .unwrap_or_default()
73                .as_secs(),
74            location: location.clone(),
75            session_id: session_id.clone(),
76        };
77
78        // Store panic info for collection by the regular telemetry publisher
79        if let Ok(mut collector) = PANIC_COLLECTOR.lock() {
80            collector.push(panic_data);
81        }
82
83        eprintln!("Fleet: Panic captured - {}", message);
84        eprintln!("Fleet: Session ID - {}", session_id);
85        if let Some(loc) = location {
86            eprintln!("Fleet: Panic location - {}", loc);
87        }
88
89        // Trigger immediate flush of panic telemetry
90        if let Ok(cb_lock) = PANIC_FLUSH_CALLBACK.lock()
91            && let Some(ref callback) = *cb_lock
92        {
93            eprintln!("Fleet: Flushing panic telemetry...");
94            callback();
95            #[cfg(not(target_arch = "wasm32"))]
96            {
97                std::thread::sleep(std::time::Duration::from_millis(500));
98            }
99        }
100
101        // Call the default hook
102        default_hook(panic_info);
103    }));
104}
105
106/// Gets collected panic information
107pub fn get_panics() -> Vec<PanicInfo> {
108    PANIC_COLLECTOR
109        .lock()
110        .ok()
111        .and_then(|mut collector| {
112            if collector.is_empty() {
113                None
114            } else {
115                Some(std::mem::take(&mut *collector))
116            }
117        })
118        .unwrap_or_default()
119}
120
121/// Clears collected panic information
122#[allow(dead_code)]
123pub fn clear_panics() {
124    if let Ok(mut collector) = PANIC_COLLECTOR.lock() {
125        collector.clear();
126    }
127}