1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! Native crash reporting for Relay.
//!
//! Use [`CrashHandler`] to configure and install a crash handler.

use std::fmt;
use std::path::Path;

#[cfg(unix)]
mod native {
    // Code generated by bindgen contains some warnings and also offends the linter. Ignore all of
    // that since they do not have a consequence on the functions used for relay-crash.
    #![allow(warnings, clippy::all)]

    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}

/// A transport function used to send envelopes to Sentry.
pub type Transport = fn(envelope: &[u8]);

/// Serializes a sentry_native envelope and passes the buffer to the [`Transport`] function.
#[cfg(unix)]
unsafe extern "C" fn transport_proxy(
    envelope: *const native::sentry_envelope_s,
    tx_pointer: *mut std::ffi::c_void,
) {
    if envelope.is_null() || tx_pointer.is_null() {
        return;
    }

    let mut len = 0;
    let buf = native::sentry_envelope_serialize(envelope, &mut len);

    if !buf.is_null() && len > 0 {
        let transport: Transport = std::mem::transmute(tx_pointer);
        transport(std::slice::from_raw_parts(buf as *const u8, len));
    }

    native::sentry_free(buf as _);
}

/// Captures process crashes and reports them to Sentry.
///
/// Internally, this uses the Breakpad client to capture crash signals and create minidumps. If no
/// DSN is configured, the crash handler is not initialized.
///
/// To send crashes to Sentry, configure a [`transport` function](Self::transport). Otherwise, the
/// crash reporter writes crashes to a local database folder, where they can be handled manually.
#[derive(Default)]
#[cfg_attr(not(unix), allow(dead_code))]
pub struct CrashHandler<'a> {
    dsn: &'a str,
    database: &'a str,
    transport: Option<Transport>,
    release: Option<&'a str>,
    environment: Option<&'a str>,
}

impl<'a> fmt::Debug for CrashHandler<'a> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let transport = match self.transport {
            Some(_) => "Some(fn)",
            None => "None",
        };

        f.debug_struct("CrashHandler")
            .field("dsn", &self.dsn)
            .field("database", &self.database)
            .field("transport", &format_args!("{transport}"))
            .field("release", &self.release)
            .field("environment", &self.environment)
            .finish()
    }
}

impl<'a> CrashHandler<'a> {
    /// Creates a new crash handler.
    ///
    /// Panics if there is are non UTF-8 characters in the path.
    pub fn new(dsn: &'a str, database: &'a Path) -> Self {
        Self {
            dsn,
            database: database.to_str().unwrap(),
            transport: None,
            release: None,
            environment: None,
        }
    }

    /// Set a transport function that sends data to Sentry.
    ///
    /// Instead of using the disabled built-in transport, the crash reporter uses this function to
    /// send envelopes to Sentry. Without this function, envelopes will not be sent and remain in
    /// the crash database folder for manual retrieval.
    pub fn transport(&mut self, transport: Transport) -> &mut Self {
        self.transport = Some(transport);
        self
    }

    /// Set the crash handler's Sentry release.
    ///
    /// Defaults to no release.
    pub fn release(&mut self, release: Option<&'a str>) -> &mut Self {
        self.release = release;
        self
    }

    /// Set the crash handler's Sentry environment.
    ///
    /// Defaults to no environment
    pub fn environment(&mut self, environment: Option<&'a str>) -> &mut Self {
        self.environment = environment;
        self
    }

    /// Installs the crash handler in the process if a Sentry DSN is set.
    #[cfg(unix)]
    pub fn install(&self) {
        use std::ffi::CString;

        unsafe {
            let options = native::sentry_options_new();

            let dsn_cstr = CString::new(self.dsn).unwrap();
            native::sentry_options_set_dsn(options, dsn_cstr.as_ptr());

            let db_cstr = CString::new(self.database).unwrap();
            native::sentry_options_set_database_path(options, db_cstr.as_ptr());

            if let Some(release) = self.release {
                let release_cstr = CString::new(release).unwrap();
                native::sentry_options_set_release(options, release_cstr.as_ptr());
            }

            if let Some(environment) = self.environment {
                let env_cstr = CString::new(environment).unwrap();
                native::sentry_options_set_environment(options, env_cstr.as_ptr());
            }

            if let Some(f) = self.transport {
                let tx = native::sentry_new_function_transport(Some(transport_proxy), f as _);
                native::sentry_options_set_transport(options, tx);
            }

            native::sentry_init(options);
        }
    }

    #[cfg(not(unix))]
    pub fn install(&self) {
        unimplemented!("crash handler not supported on this platform");
    }
}