bevy_fleet/
panic_handler.rs1use serde::{Deserialize, Serialize};
2use std::sync::{Arc, Mutex};
3
4type PanicFlushCallback = Arc<Mutex<Option<Box<dyn Fn() + Send + Sync>>>>;
5
6#[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
15static PANIC_COLLECTOR: once_cell::sync::Lazy<Arc<Mutex<Vec<PanicInfo>>>> =
17 once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
18
19static SESSION_ID: once_cell::sync::Lazy<Arc<Mutex<String>>> =
21 once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(String::from("unknown"))));
22
23static PANIC_FLUSH_CALLBACK: once_cell::sync::Lazy<PanicFlushCallback> =
25 once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(None)));
26
27pub fn set_session_id(session_id: String) {
29 if let Ok(mut id) = SESSION_ID.lock() {
30 *id = session_id;
31 }
32}
33
34pub 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
44pub 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 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 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 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 default_hook(panic_info);
103 }));
104}
105
106pub 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#[allow(dead_code)]
123pub fn clear_panics() {
124 if let Ok(mut collector) = PANIC_COLLECTOR.lock() {
125 collector.clear();
126 }
127}