Skip to main content

foundations_sentry/
panic.rs

1//! Sentry panic integration that records panic events without flushing immediately.
2
3use std::any::Any;
4use std::panic::{self, PanicHookInfo};
5use std::sync::Once;
6
7use sentry_core::protocol::{Event, Exception, Level, Mechanism};
8use sentry_core::{ClientOptions, Integration};
9
10use crate::backtrace::unresolved_stacktrace;
11
12/// A Sentry panic handler [`Integration`] that does not flush after each event.
13///
14/// This emits the same events as [`sentry_panic::PanicIntegration`] by using
15/// its public event construction API, but avoids flushing the events individually
16/// to reduce time spent in the panic hook.
17#[derive(Debug, Default)]
18pub struct NoFlushPanicIntegration {
19    inner: sentry_panic::PanicIntegration,
20}
21
22impl NoFlushPanicIntegration {
23    /// Creates a new no-flush panic integration.
24    pub fn new() -> Self {
25        Self::default()
26    }
27
28    /// Captures panic stacktraces without resolving symbols or loading debug information.
29    ///
30    /// Symbolication remains enabled unless this method is called. Server-side
31    /// symbolication requires loaded-image metadata from
32    /// `sentry_debug_images::DebugImagesIntegration` and access to matching debug files.
33    /// Previously installed panic hooks still run and may resolve their own stacktraces.
34    #[must_use]
35    pub fn with_unresolved_stacktraces(mut self) -> Self {
36        self.inner = self.inner.add_extractor(|info| {
37            Some(Event {
38                exception: vec![Exception {
39                    ty: "panic".into(),
40                    mechanism: Some(Mechanism {
41                        ty: "panic".into(),
42                        handled: Some(false),
43                        ..Default::default()
44                    }),
45                    value: Some(sentry_panic::message_from_panic_info(info).to_owned()),
46                    stacktrace: unresolved_stacktrace(),
47                    ..Default::default()
48                }]
49                .into(),
50                level: Level::Fatal,
51                ..Default::default()
52            })
53        });
54        self
55    }
56}
57
58static INIT: Once = Once::new();
59
60impl Integration for NoFlushPanicIntegration {
61    fn name(&self) -> &'static str {
62        self.inner.name()
63    }
64
65    fn setup(&self, cfg: &mut ClientOptions) {
66        // `cfg.integrations` is copied before `setup` is called, so we
67        // can't remove an upstream integration ourselves.
68        let upstream_integration: Option<&sentry_panic::PanicIntegration> = cfg
69            .integrations
70            .iter()
71            .find_map(|i| <dyn Any>::downcast_ref(i));
72
73        if let Some(integ) = upstream_integration {
74            panic!(
75                "Found an upstream `sentry_panic::PanicIntegration` while installing `NoFlushPanicIntegration`: {integ:?}. This defeats the purpose of NoFlushPanicIntegration and will cause duplicate events."
76            );
77        }
78
79        INIT.call_once(|| {
80            let next = panic::take_hook();
81            panic::set_hook(Box::new(move |info| {
82                panic_handler(info);
83                next(info)
84            }));
85        });
86    }
87}
88
89fn panic_handler(info: &PanicHookInfo) {
90    sentry_core::with_integration(|integration: &NoFlushPanicIntegration, hub| {
91        hub.capture_event(integration.inner.event_from_panic_info(info));
92        // no `client.flush()`!
93    });
94}