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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
use std::sync::Arc;
use std::time::Duration;
use uuid::Uuid;

use api::protocol::{Breadcrumb, Event};

// public api from other crates
pub use sentry_types::{Dsn, DsnParseError, ProjectId, ProjectIdParseError};
pub use sentry_types::protocol::v7 as protocol;
pub use sentry_types::protocol::v7::{Level, User};

// public exports from this crate
pub use client::Client;
#[cfg(feature = "with_client_implementation")]
pub use client::{init, ClientInitGuard, ClientOptions, IntoClientConfig};
pub use scope::{bind_client, current_client, push_scope, unbind_client, with_client_and_scope,
                Scope, ScopeGuard};

/// Captures an event on the currently active client if any.
///
/// The event must already be assembled.  Typically code would instead use
/// the utility methods like `capture_exception`.  The return value is the
/// event ID.  In case Sentry is disabled the return value will be the nil
/// UUID (`Uuid::nil`).
///
/// # Example
///
/// ```
/// use sentry::protocol::{Event, Level};
///
/// sentry::capture_event(Event {
///     message: Some("Hello World!".into()),
///     level: Level::Info,
///     ..Default::default()
/// });
/// ```
#[allow(unused_variables)]
pub fn capture_event(event: Event<'static>) -> Uuid {
    with_client_impl! {{
        with_client_and_scope(|client, scope| client.capture_event(event, Some(scope)))
    }}
}

/// Captures an error.
///
/// This attaches the current stacktrace automatically.
///
/// # Example
///
/// ```
/// sentry::capture_exception("MyError", Some("This went horribly wrong".into()));
/// ```
#[allow(unused_variables)]
pub fn capture_exception(ty: &str, value: Option<String>) -> Uuid {
    with_client_impl! {{
        use api::protocol::Exception;
        use utils::current_stacktrace;

        with_client_and_scope(|client, scope| {
            let event = Event {
                exceptions: vec![
                    Exception {
                        ty: ty.to_string(),
                        value: value,
                        stacktrace: current_stacktrace(),
                        ..Default::default()
                    },
                ],
                ..Default::default()
            };
            client.capture_event(event, Some(scope))
        })
    }}
}

/// Captures an arbitrary message.
#[allow(unused_variables)]
pub fn capture_message(msg: &str, level: Level) -> Uuid {
    with_client_impl! {{
        with_client_and_scope(|client, scope| {
            let event = Event {
                message: Some(msg.to_string()),
                level: level,
                ..Default::default()
            };
            client.capture_event(event, Some(scope))
        })
    }}
}

/// Records a breadcrumb by calling a function.
///
/// The total number of breadcrumbs that can be recorded are limited by the
/// configuration on the client.  This takes a callback because if the client
/// is not interested in breadcrumbs none will be recorded.
///
/// # Example
///
/// ```
/// use sentry::protocol::{Breadcrumb, Map};
///
/// sentry::add_breadcrumb(|| Breadcrumb {
///     ty: "http".into(),
///     category: Some("request".into()),
///     data: {
///         let mut map = Map::new();
///         map.insert("method".into(), "GET".into());
///         map.insert("url".into(), "https://example.com/".into());
///         map
///     },
///     ..Default::default()
/// });
/// ```
#[allow(unused_variables)]
pub fn add_breadcrumb<F: FnOnce() -> Breadcrumb>(f: F) {
    with_client_impl! {{
        use scope::with_client_and_scope_mut;
        with_client_and_scope_mut(|client, scope| {
            let limit = client.options().max_breadcrumbs;
            if limit > 0 {
                scope.breadcrumbs = scope.breadcrumbs.push_back(f());
                while scope.breadcrumbs.len() > limit {
                    if let Some((_, new)) = scope.breadcrumbs.pop_front() {
                        scope.breadcrumbs = new;
                    }
                }
            }
        })
    }}
}

/// Drain events that are not yet sent of the current client.
///
/// This calls into `drain_events` of the currently active client.  See that function
/// for more information.
#[allow(unused_variables)]
pub fn drain_events(timeout: Option<Duration>) {
    with_client_impl! {{
        with_client_and_scope(|client, _| {
            client.drain_events(timeout);
        });
    }}
}

/// Invokes a function that can modify the current scope.
///
/// The function is passed a mutable reference to the `Scope` so that modifications
/// can be performed.  Because there might currently not be a scope or client active
/// it's possible that the callback might not be called at all.  As a result of this
/// the return value of this closure must have a default that is returned in such
/// cases.
///
/// # Example
///
/// ```rust
/// sentry::configure_scope(|scope| {
///     scope.set_user(Some(sentry::User {
///         username: Some("john_doe".into()),
///         ..Default::default()
///     }));
/// });
/// ```
///
/// # Panics
///
/// While the scope is being configured accessing scope related functionality is
/// not permitted.  In this case a wide range of panics will be raised.  It's
/// unsafe to call into `sentry::bind_client` or similar functions from within
/// the callback as a result of this.
#[allow(unused_variables)]
pub fn configure_scope<F, R>(f: F) -> R
where
    R: Default,
    F: FnOnce(&mut Scope) -> R,
{
    with_client_impl! {{
        use scope::with_client_and_scope_mut;
        if let Some((new_scope, rv)) = with_client_and_scope(|_, scope| {
            let mut new_scope = scope.clone();
            let rv = f(&mut new_scope);
            Some((new_scope, rv))
        }) {
            with_client_and_scope_mut(|_, scope| *scope = new_scope);
            rv
        } else {
            Default::default()
        }
    }}
}

/// A callback based alternative to using `push_scope`.
///
/// This that might look a bit nicer and is more consistent with some other
/// language integerations.
///
/// # Example
///
/// ```rust
/// # macro_rules! panic { ($e:expr) => {} }
/// sentry::with_scope(|| {
///     sentry::configure_scope(|scope| {
///         scope.set_user(Some(sentry::User {
///             username: Some("john_doe".into()),
///             ..Default::default()
///         }));
///     });
///     panic!("Something went wrong!");
/// });
/// ```
#[allow(unused_variables)]
pub fn with_scope<F, R>(f: F) -> R
where
    F: FnOnce() -> R,
{
    let _guard = push_scope();
    f()
}

/// A handle to the current scope.
///
/// A scope handle is returned by the `sentry::scope_handle` function and can be used to
/// transfer a scope to another thread.  The handle can be cloned to be used in multiple
/// threads.
///
/// A scope handle also implements `Default` which returns a dummy scope handle that does
/// not do anything on bind.
#[derive(Default, Clone)]
pub struct ScopeHandle(Option<(Arc<Client>, Arc<Scope>)>);

/// Returns the handle to the current scope.
///
/// This can be used to propagate a scope to another thread easily.  The parent thread
/// retrieves a handle and the child thread binds it.  A handle can be cloned so that
/// it can be used in multiple threads.
///
/// # Example
///
/// ```
/// use std::thread;
///
/// sentry::configure_scope(|scope| {
///     scope.set_tag("task", "task-name");
/// });
/// let handle = sentry::scope_handle();
/// thread::spawn(move || {
///     handle.bind();
///     // ...
/// });
/// ```
pub fn scope_handle() -> ScopeHandle {
    with_client_impl! {{
        with_client_and_scope(|client, scope| {
            ScopeHandle(Some((client, Arc::new(scope.clone()))))
        })
    }}
}

impl ScopeHandle {
    /// Binds the scope behind the handle to the current scope.
    pub fn bind(self) {
        with_client_impl! {{
            use scope::with_client_and_scope_mut;
            if let Some((src_client, src_scope)) = self.0 {
                bind_client(src_client);
                with_client_and_scope_mut(|_, scope| {
                    *scope = Arc::try_unwrap(src_scope)
                        .unwrap_or_else(|arc| (*arc).clone());
                })
            }
        }}
    }
}