1use std::any::Any;
2use std::io::{self, Write};
3use std::panic;
4use std::sync::Once;
5use tokio::task::JoinHandle;
6use tracing::{error, info};
7use tracing_appender::non_blocking::WorkerGuard;
8use tracing_subscriber::Registry;
9use tracing_subscriber::{
10 filter::{EnvFilter, LevelFilter},
11 fmt,
12 prelude::*,
13 util::SubscriberInitExt,
14};
15
16#[cfg(debug_assertions)]
17use better_panic::{Settings, Verbosity};
18
19#[cfg(not(debug_assertions))]
20use human_panic::setup_panic;
21
22static INIT: Once = Once::new();
23static mut LOG_GUARD: Option<WorkerGuard> = None;
24
25pub fn setup_panic_handler() {
35 INIT.call_once(|| {
36 let env_filter = EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into());
38 let console_layer = fmt::Layer::new().with_writer(io::stderr);
39
40 let subscriber = Registry::default().with(env_filter).with(console_layer);
41
42 let log_file_path = "logs".to_string(); let file_appender = tracing_appender::rolling::daily(log_file_path, "application.log");
46 let (non_blocking_appender, guard) = tracing_appender::non_blocking(file_appender);
47 unsafe {
48 LOG_GUARD = Some(guard);
49 }
50 let file_layer = fmt::Layer::new().with_writer(non_blocking_appender).json();
51 subscriber.with(file_layer).init();
52
53 #[cfg(debug_assertions)]
54 {
55 Settings::auto()
57 .most_recent_first(false)
58 .lineno_suffix(true)
59 .verbosity(Verbosity::Full)
60 .install();
61 info!("Panic handler configured for DEBUG (better_panic).");
62 }
63
64 #[cfg(not(debug_assertions))]
65 {
66 setup_panic!();
68 info!("Panic handler configured for RELEASE (human_panic).");
69 }
70
71 let original_hook = panic::take_hook();
73 panic::set_hook(Box::new(move |panic_info| {
74 use crossterm::execute;
77 use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode};
78 let _ = disable_raw_mode();
79 let _ = execute!(io::stdout(), LeaveAlternateScreen);
80 let _ = io::stdout().flush();
81
82 let backtrace = std::backtrace::Backtrace::force_capture();
84 let backtrace_str = format!("{}", backtrace);
85
86 let location = panic_info.location().map_or("Unknown".to_string(), |l| {
88 format!("{}:{}:{}", l.file(), l.line(), l.column())
89 });
90
91 let payload = panic_info
92 .payload()
93 .downcast_ref::<&str>()
94 .map(|s| s.to_string())
95 .or_else(|| panic_info.payload().downcast_ref::<String>().cloned())
96 .unwrap_or_else(|| "<unknown>".to_string());
97
98 error!(
100 target: "panic_handler",
101 location = %location,
102 payload = %payload,
103 backtrace = %backtrace_str,
104 "Application panicked"
105 );
106
107 original_hook(panic_info);
109 let _ = io::stderr().flush();
110 }));
111 });
112}
113
114pub fn spawn_catch_panic<F>(future: F) -> JoinHandle<F::Output>
118where
119 F: std::future::Future + Send + 'static,
120 F::Output: Send + 'static,
121{
122 tokio::spawn(async move {
123 let result = panic::catch_unwind(std::panic::AssertUnwindSafe(|| future));
124 match result {
125 Ok(output_future) => output_future.await,
126 Err(e) => {
127 panic::resume_unwind(e);
129 }
130 }
131 })
132}
133
134pub fn catch_panic<T, F>(f: F) -> Result<T, Box<dyn Any + Send + 'static>>
148where
149 F: FnOnce() -> T + std::panic::UnwindSafe,
150{
151 std::panic::catch_unwind(f)
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use std::sync::{Arc, Mutex};
158 use std::thread;
159 use std::time::Duration;
160 use tokio::time::timeout;
161
162 #[test]
163 fn test_catch_panic_success() {
164 let result = catch_panic(|| 42);
165 assert!(result.is_ok());
166 assert_eq!(result.unwrap(), 42);
167 }
168
169 #[test]
170 fn test_catch_panic_with_panic() {
171 let result = catch_panic(|| panic!("test panic"));
172 assert!(result.is_err());
173 }
174
175 #[test]
176 fn test_catch_panic_with_string_panic() {
177 let result = catch_panic(|| panic!("string panic message"));
178 assert!(result.is_err());
179
180 let panic_payload = result.unwrap_err();
182 let panic_str = panic_payload.downcast_ref::<&str>();
183 assert!(panic_str.is_some());
184 assert_eq!(*panic_str.unwrap(), "string panic message");
185 }
186
187 #[test]
188 fn test_catch_panic_with_custom_type() {
189 #[derive(Debug, PartialEq)]
190 struct CustomError(i32);
191
192 let result = catch_panic(|| {
193 std::panic::panic_any(CustomError(123));
194 });
195
196 assert!(result.is_err());
197 let panic_payload = result.unwrap_err();
198 let custom_error = panic_payload.downcast_ref::<CustomError>();
199 assert!(custom_error.is_some());
200 assert_eq!(*custom_error.unwrap(), CustomError(123));
201 }
202
203 #[test]
204 fn test_catch_panic_with_closure_capture() {
205 let value = 100;
206 let result = catch_panic(|| value * 2);
207 assert!(result.is_ok());
208 assert_eq!(result.unwrap(), 200);
209 }
210
211 #[tokio::test]
212 async fn test_spawn_catch_panic_success() {
213 let handle = spawn_catch_panic(async { 42 });
214 let result = handle.await;
215 assert!(result.is_ok());
216 assert_eq!(result.unwrap(), 42);
217 }
218
219 #[tokio::test]
220 async fn test_spawn_catch_panic_with_async_work() {
221 let handle = spawn_catch_panic(async {
222 tokio::time::sleep(Duration::from_millis(10)).await;
223 "async result"
224 });
225
226 let result = timeout(Duration::from_secs(1), handle).await;
227 assert!(result.is_ok());
228 let join_result = result.unwrap();
229 assert!(join_result.is_ok());
230 assert_eq!(join_result.unwrap(), "async result");
231 }
232
233 #[tokio::test]
234 async fn test_spawn_catch_panic_with_panic() {
235 let handle = spawn_catch_panic(async {
236 panic!("async panic");
237 });
238
239 let result = handle.await;
241 assert!(result.is_err());
244 }
245
246 #[test]
247 fn test_setup_panic_handler_idempotent() {
248 setup_panic_handler();
250 setup_panic_handler();
251 setup_panic_handler();
252
253 }
255
256 #[test]
257 fn test_setup_panic_handler_thread_safety() {
258 let handles: Vec<_> = (0..10)
259 .map(|_| {
260 thread::spawn(|| {
261 setup_panic_handler();
262 })
263 })
264 .collect();
265
266 for handle in handles {
267 handle.join().unwrap();
268 }
269
270 }
272
273 #[test]
274 fn test_catch_panic_return_types() {
275 let string_result = catch_panic(|| "hello".to_string());
277 assert!(string_result.is_ok());
278 assert_eq!(string_result.unwrap(), "hello");
279
280 let vec_result = catch_panic(|| vec![1, 2, 3]);
281 assert!(vec_result.is_ok());
282 assert_eq!(vec_result.unwrap(), vec![1, 2, 3]);
283
284 let option_result = catch_panic(|| Some(42));
285 assert!(option_result.is_ok());
286 assert_eq!(option_result.unwrap(), Some(42));
287 }
288
289 #[tokio::test]
290 async fn test_spawn_catch_panic_concurrent() {
291 let handles: Vec<_> = (0..5)
292 .map(|i| {
293 spawn_catch_panic(async move {
294 tokio::time::sleep(Duration::from_millis(10)).await;
295 i * 2
296 })
297 })
298 .collect();
299
300 let mut results = Vec::new();
301 for handle in handles {
302 let result = handle.await;
303 assert!(result.is_ok());
304 results.push(result.unwrap());
305 }
306
307 results.sort();
308 assert_eq!(results, vec![0, 2, 4, 6, 8]);
309 }
310
311 #[test]
312 fn test_catch_panic_with_mutable_data() {
313 let mut counter = 0;
314 let result = catch_panic(std::panic::AssertUnwindSafe(|| {
315 counter += 1;
316 counter
317 }));
318
319 assert!(result.is_ok());
320 assert_eq!(result.unwrap(), 1);
321 }
322
323 #[tokio::test]
324 async fn test_spawn_catch_panic_with_shared_state() {
325 let counter = Arc::new(Mutex::new(0));
326 let counter_clone = counter.clone();
327
328 let handle = spawn_catch_panic(async move {
329 let mut count = counter_clone.lock().unwrap();
330 *count += 1;
331 *count
332 });
333
334 let result = handle.await;
335 assert!(result.is_ok());
336 assert_eq!(result.unwrap(), 1);
337
338 let final_count = *counter.lock().unwrap();
339 assert_eq!(final_count, 1);
340 }
341
342 #[test]
343 fn test_panic_handler_module_exports() {
344 setup_panic_handler();
346
347 let result = catch_panic(|| 42);
348 assert!(result.is_ok());
349
350 let _spawn_fn_exists = spawn_catch_panic::<std::future::Ready<i32>>;
353
354 }
356}