use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use once_cell::sync::OnceCell;
use tokio::sync::Mutex;
use uuid::Uuid;
use crate::config::RumConfig;
use crate::context::{RumContext, RumContextSnapshot};
use crate::error::{Result, RumError};
use crate::event::{CustomEvent, CustomException, CustomLog, CustomResource, RumEvent, ViewEvent};
use crate::exporter::ExporterHandle;
static GLOBAL: OnceCell<Rum> = OnceCell::new();
#[derive(Clone)]
pub struct Rum {
pub(crate) inner: Arc<RumInner>,
}
pub(crate) struct RumInner {
pub config: RumConfig,
pub context: Mutex<RumContext>,
pub exporter: ExporterHandle,
pub shutdown: AtomicBool,
pub instance_id: String,
}
impl Rum {
pub async fn init(config: RumConfig) -> Result<Self> {
let exporter = ExporterHandle::start(config.clone());
let context = RumContext::new();
let initial_context = context.initial_view_snapshot();
let rum = Self {
inner: Arc::new(RumInner {
config,
context: Mutex::new(context),
exporter,
shutdown: AtomicBool::new(false),
instance_id: Uuid::new_v4().hyphenated().to_string(),
}),
};
rum.enqueue_event(RumEvent::View(ViewEvent::initial(initial_context)))?;
Ok(rum)
}
pub async fn flush(&self) -> Result<()> {
self.inner.exporter.flush().await
}
pub async fn shutdown(&self) -> Result<()> {
if self.inner.shutdown.swap(true, Ordering::SeqCst) {
return Ok(());
}
self.inner.exporter.shutdown().await
}
pub async fn report_custom_event(&self, event: CustomEvent) -> Result<()> {
self.ensure_running()?;
let context = self.snapshot_context().await;
self.enqueue_event(RumEvent::Custom(event.into_event(context)))
}
pub async fn report_custom_log(&self, log: CustomLog) -> Result<()> {
self.ensure_running()?;
let context = self.snapshot_context().await;
self.enqueue_event(RumEvent::Custom(log.into_event(context)))
}
pub async fn report_custom_exception(&self, exception: CustomException) -> Result<()> {
self.ensure_running()?;
let context = self.snapshot_context().await;
self.enqueue_event(RumEvent::Exception(exception.into_event(context)))
}
pub async fn report_custom_resource(&self, resource: CustomResource) -> Result<()> {
self.ensure_running()?;
let context = self.snapshot_context().await;
self.enqueue_event(RumEvent::Resource(Box::new(
resource.into_resource_event(context, self.config()),
)))
}
pub(crate) fn enqueue_event(&self, event: RumEvent) -> Result<()> {
if self.is_shutdown() {
return Err(RumError::Shutdown);
}
self.inner.exporter.try_enqueue(event)
}
pub(crate) async fn snapshot_context(&self) -> RumContextSnapshot {
self.inner.context.lock().await.snapshot()
}
pub(crate) fn config(&self) -> &RumConfig {
&self.inner.config
}
pub(crate) fn instance_id(&self) -> &str {
&self.inner.instance_id
}
pub(crate) fn is_shutdown(&self) -> bool {
self.inner.shutdown.load(Ordering::SeqCst)
}
fn ensure_running(&self) -> Result<()> {
if self.is_shutdown() {
return Err(RumError::Shutdown);
}
Ok(())
}
#[cfg(feature = "reqwest")]
pub fn wrap_reqwest(
&self,
client: reqwest::Client,
) -> Result<crate::instrumentation::reqwest::RumReqwestClient> {
Ok(crate::instrumentation::reqwest::RumReqwestClient::new(
client,
Some(self.clone()),
))
}
}
pub async fn init_as_global(config: RumConfig) -> Result<()> {
if GLOBAL.get().is_some() {
return Err(RumError::AlreadyInitialized);
}
let rum = Rum::init(config).await?;
GLOBAL.set(rum).map_err(|_| RumError::AlreadyInitialized)
}
pub fn global() -> Result<Rum> {
GLOBAL.get().cloned().ok_or(RumError::NotInitialized)
}
pub async fn flush() -> Result<()> {
global()?.flush().await
}
pub async fn shutdown() -> Result<()> {
global()?.shutdown().await
}
pub async fn report_custom_event(event: CustomEvent) -> Result<()> {
global()?.report_custom_event(event).await
}
pub async fn report_custom_log(log: CustomLog) -> Result<()> {
global()?.report_custom_log(log).await
}
pub async fn report_custom_exception(exception: CustomException) -> Result<()> {
global()?.report_custom_exception(exception).await
}
pub async fn report_custom_resource(resource: CustomResource) -> Result<()> {
global()?.report_custom_resource(resource).await
}