1use crate::{Client, Origin, Ray};
2use std::cell::Cell;
3use std::panic::{self, PanicHookInfo};
4
5thread_local! {
6 static IN_HOOK: Cell<bool> = const { Cell::new(false) };
7}
8
9pub fn panic_hook() {
13 let previous = panic::take_hook();
14 panic::set_hook(Box::new(move |info| {
15 send_panic_to_ray(info);
16 previous(info);
17 }));
18}
19
20fn send_panic_to_ray(info: &PanicHookInfo<'_>) {
21 IN_HOOK.with(|flag| {
22 if flag.replace(true) {
23 return;
24 }
25
26 struct Reset<'a>(&'a Cell<bool>);
27 impl Drop for Reset<'_> {
28 fn drop(&mut self) {
29 self.0.set(false);
30 }
31 }
32
33 let _reset = Reset(flag);
34
35 let client = Client::global();
36 let origin = panic_origin(info, &client);
37 let message = panic_message(info);
38
39 let ray = maybe_disable(Ray::with_client(client.clone(), origin.clone()));
40 ray.text(message).label("panic").red();
41
42 let trace = maybe_disable(Ray::with_client(client, origin));
43 trace.trace().label("panic trace");
44 });
45}
46
47fn panic_message(info: &PanicHookInfo<'_>) -> String {
48 let payload = info
49 .payload()
50 .downcast_ref::<&str>()
51 .map(|s| (*s).to_string())
52 .or_else(|| info.payload().downcast_ref::<String>().cloned())
53 .unwrap_or_else(|| "panic".to_string());
54
55 if let Some(location) = info.location() {
56 format!(
57 "panic at {}:{}\n{}",
58 location.file(),
59 location.line(),
60 payload
61 )
62 } else {
63 payload
64 }
65}
66
67fn panic_origin(info: &PanicHookInfo<'_>, client: &std::sync::Arc<Client>) -> Origin {
68 let config = client.config();
69 let (file, line) = info
70 .location()
71 .map(|loc| (loc.file(), loc.line()))
72 .unwrap_or(("<unknown>", 0));
73 let module_path = "panic";
74 Origin::from_callsite_with_base(
75 file,
76 line,
77 module_path,
78 config.canonicalize_paths,
79 client.cwd(),
80 )
81}
82
83fn maybe_disable(ray: Ray) -> Ray {
84 #[cfg(all(feature = "debug-macros", not(debug_assertions)))]
85 let ray = ray.disable();
86 ray
87}