ray/
panic.rs

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
9/// Install a panic hook that sends panic details to Ray.
10///
11/// The default panic hook is still called after Ray receives the panic payloads.
12pub 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}