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 pub fn start_session(&self) {
130 with_client_impl! {{
131 self.inner.with_mut(|stack| {
132 let top = stack.top_mut();
133 if let Some(session) = crate::session::Session::from_stack(top) {
134 // When creating a *new* session, we make sure it is unique,
135 // as to no inherit *backwards* to any parents.
136 let mut scope = Arc::make_mut(&mut top.scope);
137 scope.session = Arc::new(std::sync::Mutex::new(Some(session)));
138 }
139 })
140 }}
141 }
142
143 /// End the current Release Health Session.
144 ///
145 /// See the global [`sentry::end_session`](crate::end_session) for more documentation.
146 pub fn end_session(&self) {
147 self.end_session_with_status(SessionStatus::Exited)
148 }
149
150 /// End the current Release Health Session with the given [`SessionStatus`].
151 ///
152 /// See the global [`end_session_with_status`](crate::end_session_with_status)
153 /// for more documentation.
154 pub fn end_session_with_status(&self, status: SessionStatus) {
155 with_client_impl! {{
156 self.inner.with_mut(|stack| {
157 let top = stack.top_mut();
158 // drop will close and enqueue the session
159 if let Some(mut session) = top.scope.session.lock().unwrap().take() {
160 session.close(status);
161 }
162 })
163 }}
164 }
165
166 /// Pushes a new scope.
167 ///
168 /// This returns a guard that when dropped will pop the scope again.
169 pub fn push_scope(&self) -> ScopeGuard {
170 with_client_impl! {{
171 self.inner.with_mut(|stack| {
172 stack.push();
173 ScopeGuard(Some((self.inner.stack.clone(), stack.depth())))
174 })
175 }}
176 }
177
178 /// Temporarily pushes a scope for a single call optionally reconfiguring it.
179 ///
180 /// See the global [`with_scope`](fn.with_scope.html)
181 /// for more documentation.
182 pub fn with_scope<C, F, R>(&self, scope_config: C, callback: F) -> R
183 where
184 C: FnOnce(&mut Scope),
185 F: FnOnce() -> R,
186 {
187 #[cfg(feature = "client")]
188 {
189 let _guard = self.push_scope();
190 self.configure_scope(scope_config);
191 callback()
192 }
193 #[cfg(not(feature = "client"))]
194 {
195 let _scope_config = scope_config;
196 callback()
197 }
198 }
199
200 /// Invokes a function that can modify the current scope.
201 ///
202 /// See the global [`configure_scope`](fn.configure_scope.html)
203 /// for more documentation.
204 pub fn configure_scope<F, R>(&self, f: F) -> R
205 where
206 R: Default,
207 F: FnOnce(&mut Scope) -> R,
208 {
209 with_client_impl! {{
210 let mut new_scope = self.with_current_scope(|scope| scope.clone());
211 let rv = f(&mut new_scope);
212 self.with_current_scope_mut(|ptr| *ptr = new_scope);
213 rv
214 }}
215 }
216
217 /// Adds a new breadcrumb to the current scope.
218 ///
219 /// See the global [`add_breadcrumb`](fn.add_breadcrumb.html)
220 /// for more documentation.
221 pub fn add_breadcrumb<B: IntoBreadcrumbs>(&self, breadcrumb: B) {
222 with_client_impl! {{
223 self.inner.with_mut(|stack| {
224 let top = stack.top_mut();
225 if let Some(ref client) = top.client {
226 let scope = Arc::make_mut(&mut top.scope);
227 let options = client.options();
228 let breadcrumbs = Arc::make_mut(&mut scope.breadcrumbs);
229 for breadcrumb in breadcrumb.into_breadcrumbs() {
230 let breadcrumb_opt = match options.before_breadcrumb {
231 Some(ref callback) => callback(breadcrumb),
232 None => Some(breadcrumb)
233 };
234 if let Some(breadcrumb) = breadcrumb_opt {
235 breadcrumbs.push_back(breadcrumb);
236 }
237 while breadcrumbs.len() > options.max_breadcrumbs {
238 breadcrumbs.pop_front();
239 }
240 }
241 }
242 })
243 }}
244 }
245}