relay_crash/
lib.rs

1//! Native crash reporting for Relay.
2//!
3//! Use [`CrashHandler`] to configure and install a crash handler.
4
5use std::fmt;
6use std::path::Path;
7
8#[cfg(unix)]
9mod native {
10    // Code generated by bindgen contains some warnings and also offends the linter. Ignore all of
11    // that since they do not have a consequence on the functions used for relay-crash.
12    #![allow(warnings, clippy::all)]
13
14    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
15}
16
17/// A transport function used to send envelopes to Sentry.
18pub type Transport = fn(envelope: &[u8]);
19
20/// Serializes a sentry_native envelope and passes the buffer to the [`Transport`] function.
21#[cfg(unix)]
22unsafe extern "C" fn transport_proxy(
23    envelope: *const native::sentry_envelope_s,
24    tx_pointer: *mut std::ffi::c_void,
25) {
26    if envelope.is_null() || tx_pointer.is_null() {
27        return;
28    }
29
30    let mut len = 0;
31    let buf = unsafe { native::sentry_envelope_serialize(envelope, &mut len) };
32
33    if !buf.is_null() && len > 0 {
34        let transport: Transport = unsafe { std::mem::transmute(tx_pointer) };
35        transport(unsafe { std::slice::from_raw_parts(buf as *const u8, len) });
36    }
37
38    unsafe {
39        native::sentry_free(buf as _);
40    }
41}
42
43/// Captures process crashes and reports them to Sentry.
44///
45/// Internally, this uses the Breakpad client to capture crash signals and create minidumps. If no
46/// DSN is configured, the crash handler is not initialized.
47///
48/// To send crashes to Sentry, configure a [`transport` function](Self::transport). Otherwise, the
49/// crash reporter writes crashes to a local database folder, where they can be handled manually.
50#[derive(Default)]
51#[cfg_attr(not(unix), allow(dead_code))]
52pub struct CrashHandler<'a> {
53    dsn: &'a str,
54    database: &'a str,
55    transport: Option<Transport>,
56    release: Option<&'a str>,
57    environment: Option<&'a str>,
58}
59
60impl fmt::Debug for CrashHandler<'_> {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        let transport = match self.transport {
63            Some(_) => "Some(fn)",
64            None => "None",
65        };
66
67        f.debug_struct("CrashHandler")
68            .field("dsn", &self.dsn)
69            .field("database", &self.database)
70            .field("transport", &format_args!("{transport}"))
71            .field("release", &self.release)
72            .field("environment", &self.environment)
73            .finish()
74    }
75}
76
77impl<'a> CrashHandler<'a> {
78    /// Creates a new crash handler.
79    ///
80    /// Panics if there is are non UTF-8 characters in the path.
81    pub fn new(dsn: &'a str, database: &'a Path) -> Self {
82        Self {
83            dsn,
84            database: database.to_str().unwrap(),
85            transport: None,
86            release: None,
87            environment: None,
88        }
89    }
90
91    /// Set a transport function that sends data to Sentry.
92    ///
93    /// Instead of using the disabled built-in transport, the crash reporter uses this function to
94    /// send envelopes to Sentry. Without this function, envelopes will not be sent and remain in
95    /// the crash database folder for manual retrieval.
96    pub fn transport(&mut self, transport: Transport) -> &mut Self {
97        self.transport = Some(transport);
98        self
99    }
100
101    /// Set the crash handler's Sentry release.
102    ///
103    /// Defaults to no release.
104    pub fn release(&mut self, release: Option<&'a str>) -> &mut Self {
105        self.release = release;
106        self
107    }
108
109    /// Set the crash handler's Sentry environment.
110    ///
111    /// Defaults to no environment
112    pub fn environment(&mut self, environment: Option<&'a str>) -> &mut Self {
113        self.environment = environment;
114        self
115    }
116
117    /// Installs the crash handler in the process if a Sentry DSN is set.
118    #[cfg(unix)]
119    pub fn install(&self) {
120        use std::ffi::CString;
121
122        unsafe {
123            let options = native::sentry_options_new();
124
125            let dsn_cstr = CString::new(self.dsn).unwrap();
126            native::sentry_options_set_dsn(options, dsn_cstr.as_ptr());
127
128            let db_cstr = CString::new(self.database).unwrap();
129            native::sentry_options_set_database_path(options, db_cstr.as_ptr());
130
131            if let Some(release) = self.release {
132                let release_cstr = CString::new(release).unwrap();
133                native::sentry_options_set_release(options, release_cstr.as_ptr());
134            }
135
136            if let Some(environment) = self.environment {
137                let env_cstr = CString::new(environment).unwrap();
138                native::sentry_options_set_environment(options, env_cstr.as_ptr());
139            }
140
141            if let Some(f) = self.transport {
142                let tx = native::sentry_new_function_transport(Some(transport_proxy), f as _);
143                native::sentry_options_set_transport(options, tx);
144            }
145
146            native::sentry_init(options);
147        }
148    }
149
150    #[cfg(not(unix))]
151    pub fn install(&self) {
152        unimplemented!("crash handler not supported on this platform");
153    }
154}