use std::cell::RefCell;
use std::sync::{Arc, Mutex, RwLock};
use once_cell::sync::Lazy;
use uuid::Uuid;
use crate::client::Client;
use crate::diagnostics::Diagnostics;
use crate::protocol::{Breadcrumb, ErrorEvent, SessionStatus, User};
use crate::scope::Scope;
use crate::session::Session;
static MAIN_HUB: Lazy<Arc<Hub>> = Lazy::new(|| Arc::new(Hub::new(None, Scope::new(100))));
thread_local! {
static CURRENT_HUB: RefCell<Option<Arc<Hub>>> = const { RefCell::new(None) };
}
static LAST_EVENT_ID: Lazy<Mutex<Option<Uuid>>> = Lazy::new(|| Mutex::new(None));
struct HubInner {
client: Option<Arc<Client>>,
scopes: Vec<Scope>,
session: Option<Session>,
}
pub struct Hub {
inner: RwLock<HubInner>,
}
pub struct ScopeGuard {
hub: Arc<Hub>,
}
impl Drop for ScopeGuard {
fn drop(&mut self) {
if let Ok(mut inner) = self.hub.inner.write() {
if inner.scopes.len() > 1 {
inner.scopes.pop();
}
}
}
}
impl Hub {
pub fn new(client: Option<Arc<Client>>, scope: Scope) -> Hub {
Hub {
inner: RwLock::new(HubInner {
client,
scopes: vec![scope],
session: None,
}),
}
}
pub fn new_from_top(other: &Hub) -> Arc<Hub> {
let inner = other.inner.read().expect("hub poisoned");
let top = inner
.scopes
.last()
.cloned()
.unwrap_or_else(|| Scope::new(100));
Arc::new(Hub::new(inner.client.clone(), top))
}
pub fn main() -> Arc<Hub> {
MAIN_HUB.clone()
}
pub fn current() -> Arc<Hub> {
CURRENT_HUB
.with(|c| c.borrow().clone())
.unwrap_or_else(Hub::main)
}
pub fn run<F, R>(hub: Arc<Hub>, f: F) -> R
where
F: FnOnce() -> R,
{
let prev = CURRENT_HUB.with(|c| c.borrow_mut().replace(hub));
let result = f();
CURRENT_HUB.with(|c| {
*c.borrow_mut() = prev;
});
result
}
pub fn with<F, R>(f: F) -> R
where
F: FnOnce(Arc<Hub>) -> R,
{
f(Hub::current())
}
pub fn bind_client(&self, client: Option<Arc<Client>>) {
if let Ok(mut inner) = self.inner.write() {
inner.client = client;
}
}
pub fn client(&self) -> Option<Arc<Client>> {
self.inner.read().ok().and_then(|i| i.client.clone())
}
pub fn is_enabled(&self) -> bool {
self.client().map(|c| c.is_enabled()).unwrap_or(false)
}
pub fn configure_scope<F>(&self, f: F)
where
F: FnOnce(&mut Scope),
{
if let Ok(mut inner) = self.inner.write() {
if let Some(top) = inner.scopes.last_mut() {
f(top);
}
}
}
pub fn with_scope<C, F, R>(&self, scope_fn: C, body: F) -> R
where
C: FnOnce(&mut Scope),
F: FnOnce() -> R,
{
{
let mut inner = self.inner.write().expect("hub poisoned");
let mut top = inner
.scopes
.last()
.cloned()
.unwrap_or_else(|| Scope::new(100));
scope_fn(&mut top);
inner.scopes.push(top);
}
let result = body();
if let Ok(mut inner) = self.inner.write() {
if inner.scopes.len() > 1 {
inner.scopes.pop();
}
}
result
}
pub fn push_scope(self: &Arc<Self>) -> ScopeGuard {
if let Ok(mut inner) = self.inner.write() {
let top = inner
.scopes
.last()
.cloned()
.unwrap_or_else(|| Scope::new(100));
inner.scopes.push(top);
}
ScopeGuard { hub: self.clone() }
}
pub fn add_breadcrumb(&self, breadcrumb: Breadcrumb) {
let client = self.client();
let processed = match &client {
Some(c) => c.process_breadcrumb(breadcrumb),
None => Some(breadcrumb),
};
if let Some(b) = processed {
self.configure_scope(|scope| scope.add_breadcrumb(b));
}
}
pub fn capture_event(&self, event: ErrorEvent) -> Uuid {
let client = match self.client() {
Some(c) => c,
None => return event.event_id,
};
let is_error_level = event
.level
.as_deref()
.map(|l| l == "error" || l == "fatal")
.unwrap_or(true);
let event_id = {
let inner = self.inner.read().expect("hub poisoned");
let scope = inner
.scopes
.last()
.cloned()
.unwrap_or_else(|| Scope::new(100));
let mut event = event;
if event.session_id.is_none() {
if let Some(session) = &inner.session {
event.session_id = Some(session.id().to_string());
}
}
client.capture_event(event, &scope)
};
if is_error_level {
if let Ok(mut inner) = self.inner.write() {
if let Some(session) = inner.session.as_mut() {
session.mark_errored();
}
}
}
if let Ok(mut last) = LAST_EVENT_ID.lock() {
*last = Some(event_id);
}
event_id
}
pub fn capture_error(&self, error: &dyn std::error::Error) -> Uuid {
let event = crate::event::event_from_error(error, self.client().as_deref());
self.capture_event(event)
}
pub fn capture_message(&self, message: &str, level: crate::protocol::Level) -> Uuid {
let event = crate::event::event_from_message(message, level, self.client().as_deref());
self.capture_event(event)
}
pub fn start_session(&self) {
let Some(client) = self.client() else {
return;
};
let mut inner = match self.inner.write() {
Ok(i) => i,
Err(_) => return,
};
let user = inner.scopes.last().and_then(|s| s.user.clone());
if let Some(session) = Session::start(client.options(), user.as_ref()) {
client.send_session_start(&session.to_start());
inner.session = Some(session);
}
}
pub fn end_session(&self) {
self.end_session_with_status_internal(None);
}
pub fn end_session_with_status(&self, status: SessionStatus) {
self.end_session_with_status_internal(Some(status));
}
fn end_session_with_status_internal(&self, status: Option<SessionStatus>) {
let Some(client) = self.client() else {
return;
};
let session = {
let mut inner = match self.inner.write() {
Ok(i) => i,
Err(_) => return,
};
inner.session.take()
};
if let Some(session) = session {
client.send_session_end(&session.to_end(status));
}
}
pub fn mark_session_crashed(&self) {
if let Ok(mut inner) = self.inner.write() {
if let Some(session) = inner.session.as_mut() {
session.mark_crashed();
}
}
}
pub fn set_user(&self, user: Option<User>) {
self.configure_scope(|scope| scope.set_user(user.clone()));
}
pub fn current_trace_context(&self) -> crate::propagation::TraceContext {
self.inner
.read()
.ok()
.and_then(|i| i.scopes.last().map(|s| s.trace_context()))
.unwrap_or_default()
}
pub fn flush(&self, timeout: std::time::Duration) -> bool {
match self.client() {
Some(c) => c.flush(timeout),
None => true,
}
}
pub fn get_diagnostics(&self) -> Diagnostics {
let mut diagnostics = self
.client()
.map(|client| client.get_diagnostics())
.unwrap_or_else(|| Diagnostics {
disabled: true,
sanitizer_redaction_count: crate::scrub::redaction_count(),
..Diagnostics::default()
});
if let Ok(inner) = self.inner.read() {
if let Some(scope) = inner.scopes.last() {
diagnostics.active_trace_count = if scope.trace_id().is_some() { 1 } else { 0 };
diagnostics.active_span_count = if scope.span_id().is_some() { 1 } else { 0 };
diagnostics.breadcrumb_count = scope.breadcrumbs.len() as u64;
}
}
diagnostics
}
}
pub fn last_event_id() -> Option<Uuid> {
LAST_EVENT_ID.lock().ok().and_then(|g| *g)
}
pub(crate) fn bind_main_client(client: Arc<Client>) {
Hub::main().bind_client(Some(client));
}