sentry_core/
hub.rs

1// NOTE: Most of the methods are noops without the `client` feature, and this will
2// silence all the "unused variable" warnings related to fn arguments.
3#![allow(unused)]
4
5use std::sync::{Arc, RwLock};
6
7use crate::protocol::{Event, Level, SessionStatus};
8use crate::types::Uuid;
9use crate::{Integration, IntoBreadcrumbs, Scope, ScopeGuard};
10
11/// The central object that can manage scopes and clients.
12///
13/// This can be used to capture events and manage the scope.  This object is [`Send`] and
14/// [`Sync`] so it can be used from multiple threads if needed.
15///
16/// Each thread has its own thread-local ( see [`Hub::current`]) hub, which is
17/// automatically derived from the main hub ([`Hub::main`]).
18///
19/// In most situations, developers do not need to interface with the hub directly.  Instead
20/// toplevel convenience functions are exposed that will automatically dispatch
21/// to the thread-local ([`Hub::current`]) hub.  In some situations, this might not be
22/// possible, in which case it might become necessary to manually work with the
23/// hub.  See the main [`crate`] docs for some common use-cases and pitfalls
24/// related to parallel, concurrent or async code.
25///
26/// Hubs that are wrapped in [`Arc`]s can be bound to the current thread with
27/// the `run` static method.
28///
29/// Most common operations:
30///
31/// * [`Hub::new`]: creates a brand new hub
32/// * [`Hub::current`]: returns the thread local hub
33/// * [`Hub::with`]: invoke a callback with the thread local hub
34/// * [`Hub::with_active`]: like `Hub::with` but does not invoke the callback if
35///   the client is not in a supported state or not bound
36/// * [`Hub::new_from_top`]: creates a new hub with just the top scope of another hub.
37#[derive(Debug)]
38pub struct Hub {
39    #[cfg(feature = "client")]
40    pub(crate) inner: crate::hub_impl::HubImpl,
41    pub(crate) last_event_id: RwLock<Option<Uuid>>,
42}
43
44impl Hub {
45    /// Like [`Hub::with`] but only calls the function if a client is bound.
46    ///
47    /// This is useful for integrations that want to do efficiently nothing if there is no
48    /// client bound.  Additionally this internally ensures that the client can be safely
49    /// synchronized.  This prevents accidental recursive calls into the client.
50    pub fn with_active<F, R>(f: F) -> R
51    where
52        F: FnOnce(&Arc<Hub>) -> R,
53        R: Default,
54    {
55        with_client_impl! {{
56            Hub::with(|hub| {
57                if hub.is_active_and_usage_safe() {
58                    f(hub)
59                } else {
60                    Default::default()
61                }
62            })
63        }}
64    }
65
66    /// Looks up an integration on the hub.
67    ///
68    /// Calls the given function with the requested integration instance when it
69    /// is active on the currently active client.
70    ///
71    /// See the global [`capture_event`](fn.capture_event.html)
72    /// for more documentation.
73    pub fn with_integration<I, F, R>(&self, f: F) -> R
74    where
75        I: Integration,
76        F: FnOnce(&I) -> R,
77        R: Default,
78    {
79        with_client_impl! {{
80            if let Some(client) = self.client() {
81                if let Some(integration) = client.get_integration::<I>() {
82                    return f(integration);
83                }
84            }
85            Default::default()
86        }}
87    }
88
89    /// Returns the last event id.
90    pub fn last_event_id(&self) -> Option<Uuid> {
91        *self.last_event_id.read().unwrap()
92    }
93
94    /// Sends the event to the current client with the current scope.
95    ///
96    /// In case no client is bound this does nothing instead.
97    ///
98    /// See the global [`capture_event`](fn.capture_event.html)
99    /// for more documentation.
100    pub fn capture_event(&self, event: Event<'static>) -> Uuid {
101        with_client_impl! {{
102            let top = self.inner.with(|stack| stack.top().clone());
103            let Some(ref client) = top.client else { return Default::default() };
104            let event_id = client.capture_event(event, Some(&top.scope));
105            *self.last_event_id.write().unwrap() = Some(event_id);
106            event_id
107        }}
108    }
109
110    /// Captures an arbitrary message.
111    ///
112    /// See the global [`capture_message`](fn.capture_message.html)
113    /// for more documentation.
114    pub fn capture_message(&self, msg: &str, level: Level) -> Uuid {
115        with_client_impl! {{
116            let event = Event {
117                message: Some(msg.to_string()),
118                level,
119                ..Default::default()
120            };
121            self.capture_event(event)
122        }}
123    }
124
125    /// Start a new session for Release Health.
126    ///
127    /// See the global [`start_session`](fn.start_session.html)
128    /// for more documentation.
129    #[cfg(feature = "release-health")]
130    pub fn start_session(&self) {
131        with_client_impl! {{
132            self.inner.with_mut(|stack| {
133                let top = stack.top_mut();
134                if let Some(session) = crate::session::Session::from_stack(top) {
135                    // When creating a *new* session, we make sure it is unique,
136                    // as to no inherit *backwards* to any parents.
137                    let mut scope = Arc::make_mut(&mut top.scope);
138                    scope.session = Arc::new(std::sync::Mutex::new(Some(session)));
139                }
140            })
141        }}
142    }
143
144    /// End the current Release Health Session.
145    ///
146    /// See the global [`sentry::end_session`](crate::end_session) for more documentation.
147    #[cfg(feature = "release-health")]
148    pub fn end_session(&self) {
149        self.end_session_with_status(SessionStatus::Exited)
150    }
151
152    /// End the current Release Health Session with the given [`SessionStatus`].
153    ///
154    /// See the global [`end_session_with_status`](crate::end_session_with_status)
155    /// for more documentation.
156    #[cfg(feature = "release-health")]
157    pub fn end_session_with_status(&self, status: SessionStatus) {
158        with_client_impl! {{
159            self.inner.with_mut(|stack| {
160                let top = stack.top_mut();
161                // drop will close and enqueue the session
162                if let Some(mut session) = top.scope.session.lock().unwrap().take() {
163                    session.close(status);
164                }
165            })
166        }}
167    }
168
169    /// Pushes a new scope.
170    ///
171    /// This returns a guard that when dropped will pop the scope again.
172    pub fn push_scope(&self) -> ScopeGuard {
173        with_client_impl! {{
174            self.inner.with_mut(|stack| {
175                stack.push();
176                ScopeGuard(Some((self.inner.stack.clone(), stack.depth())))
177            })
178        }}
179    }
180
181    /// Temporarily pushes a scope for a single call optionally reconfiguring it.
182    ///
183    /// See the global [`with_scope`](fn.with_scope.html)
184    /// for more documentation.
185    pub fn with_scope<C, F, R>(&self, scope_config: C, callback: F) -> R
186    where
187        C: FnOnce(&mut Scope),
188        F: FnOnce() -> R,
189    {
190        #[cfg(feature = "client")]
191        {
192            let _guard = self.push_scope();
193            self.configure_scope(scope_config);
194            callback()
195        }
196        #[cfg(not(feature = "client"))]
197        {
198            let _scope_config = scope_config;
199            callback()
200        }
201    }
202
203    /// Invokes a function that can modify the current scope.
204    ///
205    /// See the global [`configure_scope`](fn.configure_scope.html)
206    /// for more documentation.
207    pub fn configure_scope<F, R>(&self, f: F) -> R
208    where
209        R: Default,
210        F: FnOnce(&mut Scope) -> R,
211    {
212        with_client_impl! {{
213            let mut new_scope = self.with_current_scope(|scope| scope.clone());
214            let rv = f(&mut new_scope);
215            self.with_current_scope_mut(|ptr| *ptr = new_scope);
216            rv
217        }}
218    }
219
220    /// Adds a new breadcrumb to the current scope.
221    ///
222    /// See the global [`add_breadcrumb`](fn.add_breadcrumb.html)
223    /// for more documentation.
224    pub fn add_breadcrumb<B: IntoBreadcrumbs>(&self, breadcrumb: B) {
225        with_client_impl! {{
226            self.inner.with_mut(|stack| {
227                let top = stack.top_mut();
228                if let Some(ref client) = top.client {
229                    let scope = Arc::make_mut(&mut top.scope);
230                    let options = client.options();
231                    let breadcrumbs = Arc::make_mut(&mut scope.breadcrumbs);
232                    for breadcrumb in breadcrumb.into_breadcrumbs() {
233                        let breadcrumb_opt = match options.before_breadcrumb {
234                            Some(ref callback) => callback(breadcrumb),
235                            None => Some(breadcrumb)
236                        };
237                        if let Some(breadcrumb) = breadcrumb_opt {
238                            breadcrumbs.push_back(breadcrumb);
239                        }
240                        while breadcrumbs.len() > options.max_breadcrumbs {
241                            breadcrumbs.pop_front();
242                        }
243                    }
244                }
245            })
246        }}
247    }
248}