sentry_panic/
lib.rs

1//! The Sentry Panic handler integration.
2//!
3//! The `PanicIntegration`, which is enabled by default in `sentry`, installs a
4//! panic handler that will automatically dispatch all errors to Sentry that
5//! are caused by a panic.
6//! Additionally, panics are forwarded to the previously registered panic hook.
7//!
8//! # Configuration
9//!
10//! The panic integration can be configured with an additional extractor, which
11//! might optionally create a sentry `Event` out of a `PanicInfo`.
12//!
13//! ```
14//! let integration = sentry_panic::PanicIntegration::default().add_extractor(|info| None);
15//! ```
16
17#![doc(html_favicon_url = "https://sentry-brand.storage.googleapis.com/favicon.ico")]
18#![doc(html_logo_url = "https://sentry-brand.storage.googleapis.com/sentry-glyph-black.png")]
19#![warn(missing_docs)]
20#![deny(unsafe_code)]
21
22#[allow(deprecated)] // `PanicHookInfo` is only available in Rust 1.81+.
23use std::panic::{self, PanicInfo};
24use std::sync::Once;
25
26use sentry_backtrace::current_stacktrace;
27use sentry_core::protocol::{Event, Exception, Level, Mechanism};
28use sentry_core::{ClientOptions, Integration};
29
30/// A panic handler that sends to Sentry.
31///
32/// This panic handler reports panics to Sentry. It also attempts to prevent
33/// double faults in some cases where it's known to be unsafe to invoke the
34/// Sentry panic handler.
35#[allow(deprecated)] // `PanicHookInfo` is only available in Rust 1.81+.
36pub fn panic_handler(info: &PanicInfo<'_>) {
37    sentry_core::with_integration(|integration: &PanicIntegration, hub| {
38        hub.capture_event(integration.event_from_panic_info(info));
39        if let Some(client) = hub.client() {
40            client.flush(None);
41        }
42    });
43}
44
45#[allow(deprecated)] // `PanicHookInfo` is only available in Rust 1.81+.
46type PanicExtractor = dyn Fn(&PanicInfo<'_>) -> Option<Event<'static>> + Send + Sync;
47
48/// The Sentry Panic handler Integration.
49#[derive(Default)]
50pub struct PanicIntegration {
51    extractors: Vec<Box<PanicExtractor>>,
52}
53
54impl std::fmt::Debug for PanicIntegration {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        f.debug_struct("PanicIntegration")
57            .field("extractors", &self.extractors.len())
58            .finish()
59    }
60}
61
62static INIT: Once = Once::new();
63
64impl Integration for PanicIntegration {
65    fn name(&self) -> &'static str {
66        "panic"
67    }
68
69    fn setup(&self, _cfg: &mut ClientOptions) {
70        INIT.call_once(|| {
71            let next = panic::take_hook();
72            panic::set_hook(Box::new(move |info| {
73                panic_handler(info);
74                next(info);
75            }));
76        });
77    }
78}
79
80/// Extract the message of a panic.
81#[allow(deprecated)] // `PanicHookInfo` is only available in Rust 1.81+.
82pub fn message_from_panic_info<'a>(info: &'a PanicInfo<'_>) -> &'a str {
83    match info.payload().downcast_ref::<&'static str>() {
84        Some(s) => s,
85        None => match info.payload().downcast_ref::<String>() {
86            Some(s) => &s[..],
87            None => "Box<Any>",
88        },
89    }
90}
91
92impl PanicIntegration {
93    /// Creates a new Panic Integration.
94    pub fn new() -> Self {
95        Self::default()
96    }
97
98    /// Registers a new extractor.
99    #[must_use]
100    #[allow(deprecated)] // `PanicHookInfo` is only available in Rust 1.81+.
101    pub fn add_extractor<F>(mut self, f: F) -> Self
102    where
103        F: Fn(&PanicInfo<'_>) -> Option<Event<'static>> + Send + Sync + 'static,
104    {
105        self.extractors.push(Box::new(f));
106        self
107    }
108
109    /// Creates an event from the given panic info.
110    ///
111    /// The stacktrace is calculated from the current frame.
112    #[allow(deprecated)] // `PanicHookInfo` is only available in Rust 1.81+.
113    pub fn event_from_panic_info(&self, info: &PanicInfo<'_>) -> Event<'static> {
114        for extractor in &self.extractors {
115            if let Some(event) = extractor(info) {
116                return event;
117            }
118        }
119
120        // TODO: We would ideally want to downcast to `std::error:Error` here
121        // and use `event_from_error`, but that way we won‘t get meaningful
122        // backtraces yet.
123
124        let msg = message_from_panic_info(info);
125        Event {
126            exception: vec![Exception {
127                ty: "panic".into(),
128                mechanism: Some(Mechanism {
129                    ty: "panic".into(),
130                    handled: Some(false),
131                    ..Default::default()
132                }),
133                value: Some(msg.to_string()),
134                stacktrace: current_stacktrace(),
135                ..Default::default()
136            }]
137            .into(),
138            level: Level::Fatal,
139            ..Default::default()
140        }
141    }
142}