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#![deny(unsafe_code)]
20
21use std::panic::{self, PanicHookInfo};
22use std::sync::Once;
23
24use sentry_backtrace::current_stacktrace;
25use sentry_core::protocol::{Event, Exception, Level, Mechanism};
26use sentry_core::{ClientOptions, Integration};
27
28pub fn panic_handler(info: &PanicHookInfo<'_>) {
34 sentry_core::with_integration(|integration: &PanicIntegration, hub| {
35 hub.capture_event(integration.event_from_panic_info(info));
36 if let Some(client) = hub.client() {
37 client.flush(None);
38 }
39 });
40}
41
42type PanicExtractor = dyn Fn(&PanicHookInfo<'_>) -> Option<Event<'static>> + Send + Sync;
43
44#[derive(Default)]
46pub struct PanicIntegration {
47 extractors: Vec<Box<PanicExtractor>>,
48}
49
50impl std::fmt::Debug for PanicIntegration {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.debug_struct("PanicIntegration")
53 .field("extractors", &self.extractors.len())
54 .finish()
55 }
56}
57
58static INIT: Once = Once::new();
59
60impl Integration for PanicIntegration {
61 fn name(&self) -> &'static str {
62 "panic"
63 }
64
65 fn setup(&self, _cfg: &mut ClientOptions) {
66 INIT.call_once(|| {
67 let next = panic::take_hook();
68 panic::set_hook(Box::new(move |info| {
69 panic_handler(info);
70 next(info);
71 }));
72 });
73 }
74}
75
76pub fn message_from_panic_info<'a>(info: &'a PanicHookInfo<'_>) -> &'a str {
78 match info.payload().downcast_ref::<&'static str>() {
79 Some(s) => s,
80 None => match info.payload().downcast_ref::<String>() {
81 Some(s) => &s[..],
82 None => "Box<Any>",
83 },
84 }
85}
86
87impl PanicIntegration {
88 pub fn new() -> Self {
90 Self::default()
91 }
92
93 #[must_use]
95 pub fn add_extractor<F>(mut self, f: F) -> Self
96 where
97 F: Fn(&PanicHookInfo<'_>) -> Option<Event<'static>> + Send + Sync + 'static,
98 {
99 self.extractors.push(Box::new(f));
100 self
101 }
102
103 pub fn event_from_panic_info(&self, info: &PanicHookInfo<'_>) -> Event<'static> {
107 for extractor in &self.extractors {
108 if let Some(event) = extractor(info) {
109 return event;
110 }
111 }
112
113 let msg = message_from_panic_info(info);
118 Event {
119 exception: vec![Exception {
120 ty: "panic".into(),
121 mechanism: Some(Mechanism {
122 ty: "panic".into(),
123 handled: Some(false),
124 ..Default::default()
125 }),
126 value: Some(msg.to_string()),
127 stacktrace: current_stacktrace(),
128 ..Default::default()
129 }]
130 .into(),
131 level: Level::Fatal,
132 ..Default::default()
133 }
134 }
135}