1#![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
29pub 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#[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
77pub 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 pub fn new() -> Self {
91 Self::default()
92 }
93
94 #[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 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 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}