ckb_sentry_core/
hub.rs

1#![allow(unused)]
2
3use std::cell::{Cell, UnsafeCell};
4use std::error::Error;
5use std::mem::drop;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::{Arc, Mutex, PoisonError, RwLock, TryLockError};
8use std::thread;
9use std::time::Duration;
10
11use crate::protocol::{Breadcrumb, Event, Level, SessionStatus};
12use crate::types::Uuid;
13use crate::{event_from_error, Integration, IntoBreadcrumbs, Scope, ScopeGuard};
14#[cfg(feature = "client")]
15use crate::{scope::Stack, session::Session, Client, Envelope};
16
17#[cfg(feature = "client")]
18lazy_static::lazy_static! {
19    static ref PROCESS_HUB: (Arc<Hub>, thread::ThreadId) = (
20        Arc::new(Hub::new(None, Arc::new(Default::default()))),
21        thread::current().id()
22    );
23}
24
25#[cfg(feature = "client")]
26thread_local! {
27    static THREAD_HUB: UnsafeCell<Arc<Hub>> = UnsafeCell::new(
28        Arc::new(Hub::new_from_top(&PROCESS_HUB.0)));
29    static USE_PROCESS_HUB: Cell<bool> = Cell::new(PROCESS_HUB.1 == thread::current().id());
30}
31
32#[cfg(feature = "client")]
33#[derive(Debug)]
34pub(crate) struct HubImpl {
35    stack: Arc<RwLock<Stack>>,
36}
37
38#[cfg(feature = "client")]
39impl HubImpl {
40    pub(crate) fn with<F: FnOnce(&Stack) -> R, R>(&self, f: F) -> R {
41        let guard = self.stack.read().unwrap_or_else(PoisonError::into_inner);
42        f(&*guard)
43    }
44
45    fn with_mut<F: FnOnce(&mut Stack) -> R, R>(&self, f: F) -> R {
46        let mut guard = self.stack.write().unwrap_or_else(PoisonError::into_inner);
47        f(&mut *guard)
48    }
49
50    fn is_active_and_usage_safe(&self) -> bool {
51        let guard = match self.stack.read() {
52            Err(err) => err.into_inner(),
53            Ok(guard) => guard,
54        };
55
56        guard
57            .top()
58            .client
59            .as_ref()
60            .map_or(false, |c| c.is_enabled())
61    }
62}
63
64/// The central object that can manages scopes and clients.
65///
66/// This can be used to capture events and manage the scope.  This object is
67/// internally synchronized so it can be used from multiple threads if needed.
68///
69/// Each thread has its own thread-local (`Hub::current()`) hub, which is
70/// automatically derived from the main hub (`Hub::main()`).
71///
72/// In most situations developers do not need to interface with the hub directly.  Instead
73/// toplevel convenience functions are expose that will automatically dispatch
74/// to the thread-local (`Hub::current()`) hub.  In some situations this might not be
75/// possible in which case it might become necessary to manually work with the
76/// hub.  This is for instance the case when working with async code.
77///
78/// Hubs that are wrapped in `Arc`s can be bound to the current thread with
79/// the `run` static method.
80///
81/// Most common operations:
82///
83/// * `Hub::new`: creates a brand new hub
84/// * `Hub::current`: returns the thread local hub
85/// * `Hub::with`: invoke a callback with the thread local hub
86/// * `Hub::with_active`: like `Hub::with` but does not invoke the callback if
87///   the client is not in a supported state or not bound
88/// * `Hub::new_from_top`: creates a new hub with just the top scope of another hub.
89#[derive(Debug)]
90pub struct Hub {
91    #[cfg(feature = "client")]
92    pub(crate) inner: HubImpl,
93    last_event_id: RwLock<Option<Uuid>>,
94}
95
96impl Hub {
97    /// Creates a new hub from the given client and scope.
98    #[cfg(feature = "client")]
99    pub fn new(client: Option<Arc<Client>>, scope: Arc<Scope>) -> Hub {
100        Hub {
101            inner: HubImpl {
102                stack: Arc::new(RwLock::new(Stack::from_client_and_scope(client, scope))),
103            },
104            last_event_id: RwLock::new(None),
105        }
106    }
107
108    /// Creates a new hub based on the top scope of the given hub.
109    #[cfg(feature = "client")]
110    pub fn new_from_top<H: AsRef<Hub>>(other: H) -> Hub {
111        let hub = other.as_ref();
112        hub.inner.with(|stack| {
113            let top = stack.top();
114            Hub::new(top.client.clone(), top.scope.clone())
115        })
116    }
117
118    /// Returns the current hub.
119    ///
120    /// By default each thread gets a different thread local hub.  If an
121    /// atomically reference counted hub is available it can override this
122    /// one here by calling `Hub::run` with a closure.
123    ///
124    /// This method is unavailable if the client implementation is disabled.
125    /// When using the minimal API set use `Hub::with_active` instead.
126    #[cfg(feature = "client")]
127    pub fn current() -> Arc<Hub> {
128        Hub::with(Arc::clone)
129    }
130
131    /// Returns the main thread's hub.
132    ///
133    /// This is similar to `current` but instead of picking the current
134    /// thread's hub it returns the main thread's hub instead.
135    #[cfg(feature = "client")]
136    pub fn main() -> Arc<Hub> {
137        PROCESS_HUB.0.clone()
138    }
139
140    /// Invokes the callback with the default hub.
141    ///
142    /// This is a slightly more efficient version than `Hub::current()` and
143    /// also unavailable in minimal mode.
144    #[cfg(feature = "client")]
145    pub fn with<F, R>(f: F) -> R
146    where
147        F: FnOnce(&Arc<Hub>) -> R,
148    {
149        if USE_PROCESS_HUB.with(Cell::get) {
150            f(&PROCESS_HUB.0)
151        } else {
152            // not on safety: this is safe because even though we change the Arc
153            // by temorary binding we guarantee that the original Arc stays alive.
154            // For more information see: run
155            THREAD_HUB.with(|stack| unsafe {
156                let ptr = stack.get();
157                f(&*ptr)
158            })
159        }
160    }
161
162    /// Like `Hub::with` but only calls the function if a client is bound.
163    ///
164    /// This is useful for integrations that want to do efficiently nothing if there is no
165    /// client bound.  Additionally this internally ensures that the client can be safely
166    /// synchronized.  This prevents accidental recursive calls into the client.
167    pub fn with_active<F, R>(f: F) -> R
168    where
169        F: FnOnce(&Arc<Hub>) -> R,
170        R: Default,
171    {
172        with_client_impl! {{
173            Hub::with(|hub| {
174                if hub.is_active_and_usage_safe() {
175                    f(hub)
176                } else {
177                    Default::default()
178                }
179            })
180        }}
181    }
182
183    /// Binds a hub to the current thread for the duration of the call.
184    #[cfg(feature = "client")]
185    pub fn run<F: FnOnce() -> R, R>(hub: Arc<Hub>, f: F) -> R {
186        let mut restore_process_hub = false;
187        let did_switch = THREAD_HUB.with(|ctx| unsafe {
188            let ptr = ctx.get();
189            if &**ptr as *const _ == &*hub as *const _ {
190                None
191            } else {
192                USE_PROCESS_HUB.with(|x| {
193                    if x.get() {
194                        restore_process_hub = true;
195                        x.set(false);
196                    }
197                });
198                let old = (*ptr).clone();
199                *ptr = hub.clone();
200                Some(old)
201            }
202        });
203
204        match did_switch {
205            None => {
206                // None means no switch happened.  We can invoke the function
207                // just like that, no changes necessary.
208                f()
209            }
210            Some(old_hub) => {
211                use std::panic;
212
213                // this is for the case where we just switched the hub.  This
214                // means we need to catch the panic, restore the
215                // old context and resume the panic if needed.
216                let rv = panic::catch_unwind(panic::AssertUnwindSafe(f));
217                THREAD_HUB.with(|ctx| unsafe { *ctx.get() = old_hub });
218                if restore_process_hub {
219                    USE_PROCESS_HUB.with(|x| x.set(true));
220                }
221                match rv {
222                    Err(err) => panic::resume_unwind(err),
223                    Ok(rv) => rv,
224                }
225            }
226        }
227    }
228
229    /// Looks up an integration on the hub.
230    ///
231    /// Calls the given function with the requested integration instance when it
232    /// is active on the currently active client.
233    ///
234    /// See the global [`capture_event`](fn.capture_event.html)
235    /// for more documentation.
236    pub fn with_integration<I, F, R>(&self, f: F) -> R
237    where
238        I: Integration,
239        F: FnOnce(&I) -> R,
240        R: Default,
241    {
242        with_client_impl! {{
243            if let Some(client) = self.client() {
244                if let Some(integration) = client.get_integration::<I>() {
245                    return f(integration);
246                }
247            }
248            Default::default()
249        }}
250    }
251
252    /// Returns the last event id.
253    pub fn last_event_id(&self) -> Option<Uuid> {
254        *self.last_event_id.read().unwrap()
255    }
256
257    /// Sends the event to the current client with the current scope.
258    ///
259    /// In case no client is bound this does nothing instead.
260    ///
261    /// See the global [`capture_event`](fn.capture_event.html)
262    /// for more documentation.
263    pub fn capture_event(&self, event: Event<'static>) -> Uuid {
264        with_client_impl! {{
265            self.inner.with(|stack| {
266                let top = stack.top();
267                if let Some(ref client) = top.client {
268                    let event_id = client.capture_event(event, Some(&top.scope));
269                    *self.last_event_id.write().unwrap() = Some(event_id);
270                    event_id
271                } else {
272                    Default::default()
273                }
274            })
275        }}
276    }
277
278    /// Captures an arbitrary message.
279    ///
280    /// See the global [`capture_message`](fn.capture_message.html)
281    /// for more documentation.
282    pub fn capture_message(&self, msg: &str, level: Level) -> Uuid {
283        with_client_impl! {{
284            self.inner.with(|stack| {
285                let top = stack.top();
286                if let Some(ref client) = top.client {
287                    let mut event = Event {
288                        message: Some(msg.to_string()),
289                        level,
290                        ..Default::default()
291                    };
292                    self.capture_event(event)
293                } else {
294                    Uuid::nil()
295                }
296            })
297        }}
298    }
299
300    /// Returns the currently bound client.
301    #[cfg(feature = "client")]
302    pub fn client(&self) -> Option<Arc<Client>> {
303        self.inner.with(|stack| stack.top().client.clone())
304    }
305
306    /// Binds a new client to the hub.
307    #[cfg(feature = "client")]
308    pub fn bind_client(&self, client: Option<Arc<Client>>) {
309        self.inner.with_mut(|stack| {
310            stack.top_mut().client = client;
311        })
312    }
313
314    /// Start a new session for Release Health.
315    ///
316    /// See the global [`start_session`](fn.start_session.html)
317    /// for more documentation.
318    pub fn start_session(&self) {
319        with_client_impl! {{
320            self.inner.with_mut(|stack| {
321                let top = stack.top_mut();
322                if let Some(session) = Session::from_stack(top) {
323                    // When creating a *new* session, we make sure it is unique,
324                    // as to no inherit *backwards* to any parents.
325                    let mut scope = Arc::make_mut(&mut top.scope);
326                    scope.session = Arc::new(Mutex::new(Some(session)));
327                }
328            })
329        }}
330    }
331
332    /// End the current Release Health Session.
333    ///
334    /// See the global [`end_session`](crate::end_session_with)
335    /// for more documentation.
336    pub fn end_session(&self) {
337        self.end_session_with_status(SessionStatus::Exited)
338    }
339    /// End the current Release Health Session with the given [`SessionStatus`].
340    ///
341    /// See the global [`end_session_with_status`](crate::end_session_with_status)
342    /// for more documentation.
343    pub fn end_session_with_status(&self, status: SessionStatus) {
344        with_client_impl! {{
345            self.inner.with_mut(|stack| {
346                let top = stack.top_mut();
347                // drop will close and enqueue the session
348                if let Some(mut session) = top.scope.session.lock().unwrap().take() {
349                    session.close(status);
350                }
351            })
352        }}
353    }
354
355    /// Pushes a new scope.
356    ///
357    /// This returns a guard that when dropped will pop the scope again.
358    pub fn push_scope(&self) -> ScopeGuard {
359        with_client_impl! {{
360            self.inner.with_mut(|stack| {
361                stack.push();
362                ScopeGuard(Some((self.inner.stack.clone(), stack.depth())))
363            })
364        }}
365    }
366
367    /// Temporarily pushes a scope for a single call optionally reconfiguring it.
368    ///
369    /// See the global [`with_scope`](fn.with_scope.html)
370    /// for more documentation.
371    pub fn with_scope<C, F, R>(&self, scope_config: C, callback: F) -> R
372    where
373        C: FnOnce(&mut Scope),
374        F: FnOnce() -> R,
375    {
376        #[cfg(feature = "client")]
377        {
378            let _guard = self.push_scope();
379            self.configure_scope(scope_config);
380            callback()
381        }
382        #[cfg(not(feature = "client"))]
383        {
384            let _scope_config = scope_config;
385            callback()
386        }
387    }
388
389    /// Invokes a function that can modify the current scope.
390    ///
391    /// See the global [`configure_scope`](fn.configure_scope.html)
392    /// for more documentation.
393    pub fn configure_scope<F, R>(&self, f: F) -> R
394    where
395        R: Default,
396        F: FnOnce(&mut Scope) -> R,
397    {
398        with_client_impl! {{
399            let mut new_scope = self.with_current_scope(|scope| scope.clone());
400            let rv = f(&mut new_scope);
401            self.with_current_scope_mut(|ptr| *ptr = new_scope);
402            rv
403        }}
404    }
405
406    /// Adds a new breadcrumb to the current scope.
407    ///
408    /// See the global [`add_breadcrumb`](fn.add_breadcrumb.html)
409    /// for more documentation.
410    pub fn add_breadcrumb<B: IntoBreadcrumbs>(&self, breadcrumb: B) {
411        with_client_impl! {{
412            self.inner.with_mut(|stack| {
413                let top = stack.top_mut();
414                if let Some(ref client) = top.client {
415                    let scope = Arc::make_mut(&mut top.scope);
416                    let options = client.options();
417                    for breadcrumb in breadcrumb.into_breadcrumbs() {
418                        let breadcrumb_opt = match options.before_breadcrumb {
419                            Some(ref callback) => callback(breadcrumb),
420                            None => Some(breadcrumb)
421                        };
422                        if let Some(breadcrumb) = breadcrumb_opt {
423                            scope.breadcrumbs.push_back(breadcrumb);
424                        }
425                        while scope.breadcrumbs.len() > options.max_breadcrumbs {
426                            scope.breadcrumbs.pop_front();
427                        }
428                    }
429                }
430            })
431        }}
432    }
433
434    #[cfg(feature = "client")]
435    pub(crate) fn is_active_and_usage_safe(&self) -> bool {
436        self.inner.is_active_and_usage_safe()
437    }
438
439    #[cfg(feature = "client")]
440    pub(crate) fn with_current_scope<F: FnOnce(&Scope) -> R, R>(&self, f: F) -> R {
441        self.inner.with(|stack| f(&stack.top().scope))
442    }
443
444    #[cfg(feature = "client")]
445    pub(crate) fn with_current_scope_mut<F: FnOnce(&mut Scope) -> R, R>(&self, f: F) -> R {
446        self.inner
447            .with_mut(|stack| f(Arc::make_mut(&mut stack.top_mut().scope)))
448    }
449}