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}