sentry_contrib_breakpad/
breakpad_integration.rs

1use sentry_core::protocol as proto;
2use std::{path::Path, time::SystemTime};
3
4pub use breakpad_handler::InstallOptions;
5
6/// Monitors the current process for crashes, writing them to disk as minidumps
7/// and reporting the crash event to Sentry.
8pub struct BreakpadIntegration {
9    crash_handler: Option<breakpad_handler::BreakpadHandler>,
10}
11
12impl BreakpadIntegration {
13    /// Creates a new Breakpad Integration, note that only *one* can exist
14    /// in the application at a time!
15    pub fn new(
16        crash_dir: impl AsRef<Path>,
17        install_options: InstallOptions,
18        hub: std::sync::Arc<sentry_core::Hub>,
19    ) -> Result<Self, crate::Error> {
20        // The paths generated by breakpad are just guids with an extension so they
21        // are utf-8 safe, however, due to how we pass the path via metadata
22        // to the transport, we require that the root crash path is also utf-8
23        if crash_dir.as_ref().to_str().is_none() {
24            return Err(crate::Error::NonUtf8Path(crash_dir.as_ref().to_owned()));
25        }
26
27        // Ensure the directory exists, breakpad should do this when writing crashdumps
28        // anyway, but then again, it's C++ code, so I have low trust
29        std::fs::create_dir_all(&crash_dir)?;
30
31        let crash_hub = std::sync::Arc::downgrade(&hub);
32        let crash_handler = breakpad_handler::BreakpadHandler::attach(
33            &crash_dir,
34            install_options,
35            Box::new(move |minidump_path: std::path::PathBuf| {
36                if let Some(crash_hub) = crash_hub.upgrade() {
37                    // We **don't** do end_session_with_status as it just
38                    // immediately takes the session from the scope and sends it,
39                    // but we want to send the event, session update, and minidump
40                    // all in the same event
41                    // crash_hub.end_session_with_status(protocol::SessionStatus::Crashed);
42
43                    let mut extra = std::collections::BTreeMap::new();
44                    // We should never get here unless the path is valid utf-8, so this is fine
45                    extra.insert(
46                        "__breakpad_minidump_path".to_owned(),
47                        minidump_path
48                            .to_str()
49                            .expect("utf-8 path")
50                            .to_owned()
51                            .into(),
52                    );
53
54                    // Create an event for crash so that we can add all of the context
55                    // we can to it, the important information like stack traces/threads
56                    // modules/etc is contained in the minidump recorded by breakpad
57                    let event = proto::Event {
58                        level: proto::Level::Fatal,
59                        // We want to set the timestamp here since we aren't actually
60                        // going to send the crash directly, but rather the next time
61                        // this integration is initialized
62                        timestamp: SystemTime::now(),
63                        // This is the easiest way to indicate a session crash update
64                        // in the same envelope with the crash itself. :p
65                        exception: vec![proto::Exception {
66                            mechanism: Some(proto::Mechanism {
67                                handled: Some(false),
68                                ..Default::default()
69                            }),
70                            ..Default::default()
71                        }]
72                        .into(),
73                        extra,
74                        ..Default::default()
75                    };
76
77                    crash_hub.capture_event(event);
78
79                    if let Some(client) = crash_hub.client() {
80                        client.close(None);
81                    }
82                }
83            }),
84        )?;
85
86        let crash_dir = crash_dir.as_ref().to_owned();
87
88        Self::upload_minidumps(&crash_dir, &hub);
89
90        Ok(Self {
91            crash_handler: Some(crash_handler),
92        })
93    }
94
95    /// Called during startup to send any minidumps + metadata that have been
96    /// captured in previous sessions but (seem to) have not been sent yet
97    fn upload_minidumps(crash_dir: &Path, hub: &sentry_core::Hub) {
98        // Scan the directory the integration was initialized with to find any
99        // envelopes that have been serialized to disk and send + delete them
100        let rd = match std::fs::read_dir(crash_dir) {
101            Ok(rd) => rd,
102            Err(e) => {
103                debug_print!(
104                    "Unable to read crash directory '{}': {}",
105                    crash_dir.display(),
106                    e
107                );
108                return;
109            }
110        };
111
112        let client = match hub.client() {
113            Some(c) => c,
114            None => return,
115        };
116
117        // The minidumps are what we care about the most, but of course, the
118        // metadata that we (hopefully) were able to capture along with the crash
119        for entry in rd.filter_map(|e| e.ok()) {
120            if entry
121                .file_name()
122                .to_str()
123                .map_or(true, |s| !s.ends_with(".dmp"))
124            {
125                continue;
126            }
127
128            let mut minidump_path = entry.path();
129            minidump_path.set_extension("metadata");
130
131            let md = crate::shared::CrashMetadata::deserialize(&minidump_path);
132            if let Err(e) = std::fs::remove_file(&minidump_path) {
133                debug_print!("failed to remove {}: {}", minidump_path.display(), e);
134            }
135
136            minidump_path.set_extension("dmp");
137
138            let envelope = crate::shared::assemble_envelope(md, &minidump_path);
139            if let Err(e) = std::fs::remove_file(&minidump_path) {
140                debug_print!("failed to remove {}: {}", minidump_path.display(), e);
141            }
142
143            client.send_envelope(envelope);
144        }
145    }
146
147    #[inline]
148    pub fn inner_handler(&self) -> &Option<breakpad_handler::BreakpadHandler> {
149        &self.crash_handler
150    }
151}
152
153impl Drop for BreakpadIntegration {
154    fn drop(&mut self) {
155        let _ = self.crash_handler.take();
156    }
157}