Skip to main content

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 `PanicHookInfo`.
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
22use std::panic::{self, PanicHookInfo};
23use std::sync::Once;
24
25use sentry_backtrace::current_stacktrace;
26use sentry_core::protocol::{Event, Exception, Level, Mechanism};
27use sentry_core::{ClientOptions, Integration};
28
29/// A panic handler that sends to Sentry.
30///
31/// This panic handler reports panics to Sentry. It also attempts to prevent
32/// double faults in some cases where it's known to be unsafe to invoke the
33/// Sentry panic handler.
34pub fn panic_handler(info: &PanicHookInfo<'_>) {
35    sentry_core::with_integration(|integration: &PanicIntegration, hub| {
36        hub.capture_event(integration.event_from_panic_info(info));
37        if let Some(client) = hub.client() {
38            client.flush(None);
39        }
40    });
41}
42
43type PanicExtractor = dyn Fn(&PanicHookInfo<'_>) -> Option<Event<'static>> + Send + Sync;
44
45/// The Sentry Panic handler Integration.
46#[derive(Default)]
47pub struct PanicIntegration {
48    extractors: Vec<Box<PanicExtractor>>,
49}
50
51impl std::fmt::Debug for PanicIntegration {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.debug_struct("PanicIntegration")
54            .field("extractors", &self.extractors.len())
55            .finish()
56    }
57}
58
59static INIT: Once = Once::new();
60
61impl Integration for PanicIntegration {
62    fn name(&self) -> &'static str {
63        "panic"
64    }
65
66    fn setup(&self, _cfg: &mut ClientOptions) {
67        INIT.call_once(|| {
68            let next = panic::take_hook();
69            panic::set_hook(Box::new(move |info| {
70                panic_handler(info);
71                next(info);
72            }));
73        });
74    }
75}
76
77/// Extract the message of a panic.
78pub fn message_from_panic_info<'a>(info: &'a PanicHookInfo<'_>) -> &'a str {
79    match info.payload().downcast_ref::<&'static str>() {
80        Some(s) => s,
81        None => match info.payload().downcast_ref::<String>() {
82            Some(s) => &s[..],
83            None => "Box<Any>",
84        },
85    }
86}
87
88impl PanicIntegration {
89    /// Creates a new Panic Integration.
90    pub fn new() -> Self {
91        Self::default()
92    }
93
94    /// Registers a new extractor.
95    #[must_use]
96    pub fn add_extractor<F>(mut self, f: F) -> Self
97    where
98        F: Fn(&PanicHookInfo<'_>) -> Option<Event<'static>> + Send + Sync + 'static,
99    {
100        self.extractors.push(Box::new(f));
101        self
102    }
103
104    /// Creates an event from the given panic info.
105    ///
106    /// The stacktrace is calculated from the current frame.
107    pub fn event_from_panic_info(&self, info: &PanicHookInfo<'_>) -> Event<'static> {
108        for extractor in &self.extractors {
109            if let Some(event) = extractor(info) {
110                return event;
111            }
112        }
113
114        // TODO: We would ideally want to downcast to `std::error:Error` here
115        // and use `event_from_error`, but that way we won‘t get meaningful
116        // backtraces yet.
117
118        let msg = message_from_panic_info(info);
119        Event {
120            exception: vec![Exception {
121                ty: "panic".into(),
122                mechanism: Some(Mechanism {
123                    ty: "panic".into(),
124                    handled: Some(false),
125                    ..Default::default()
126                }),
127                value: Some(msg.to_string()),
128                stacktrace: current_stacktrace(),
129                ..Default::default()
130            }]
131            .into(),
132            level: Level::Fatal,
133            ..Default::default()
134        }
135    }
136}