use std::any::TypeId;
use std::borrow::Cow;
use std::fmt;
use std::panic::RefUnwindSafe;
use std::sync::Arc;
use std::sync::RwLock;
use std::time::Duration;
use rand::random;
use sentry_types::protocol::v7::SessionUpdate;
use crate::constants::SDK_INFO;
use crate::protocol::{ClientSdkInfo, Event};
use crate::session::SessionFlusher;
use crate::types::{Dsn, Uuid};
use crate::{ClientOptions, Envelope, Hub, Integration, Scope, SessionMode, Transport};
impl<T: Into<ClientOptions>> From<T> for Client {
fn from(o: T) -> Client {
Client::with_options(o.into())
}
}
pub(crate) type TransportArc = Arc<RwLock<Option<Arc<dyn Transport>>>>;
pub struct Client {
options: ClientOptions,
transport: TransportArc,
session_flusher: RwLock<Option<SessionFlusher>>,
integrations: Vec<(TypeId, Arc<dyn Integration>)>,
pub(crate) sdk_info: ClientSdkInfo,
}
impl fmt::Debug for Client {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Client")
.field("dsn", &self.dsn())
.field("options", &self.options)
.finish()
}
}
impl Clone for Client {
fn clone(&self) -> Client {
let transport = Arc::new(RwLock::new(self.transport.read().unwrap().clone()));
let session_flusher = RwLock::new(Some(SessionFlusher::new(
transport.clone(),
self.options.session_mode,
)));
Client {
options: self.options.clone(),
transport,
session_flusher,
integrations: self.integrations.clone(),
sdk_info: self.sdk_info.clone(),
}
}
}
impl Client {
pub fn from_config<O: Into<ClientOptions>>(opts: O) -> Client {
Client::with_options(opts.into())
}
pub fn with_options(mut options: ClientOptions) -> Client {
Hub::with(|_| {});
let create_transport = || {
options.dsn.as_ref()?;
let factory = options.transport.as_ref()?;
Some(factory.create_transport(&options))
};
let transport = Arc::new(RwLock::new(create_transport()));
let mut sdk_info = SDK_INFO.clone();
let integrations: Vec<_> = options
.integrations
.iter()
.map(|integration| (integration.as_ref().type_id(), integration.clone()))
.collect();
for (_, integration) in integrations.iter() {
integration.setup(&mut options);
sdk_info.integrations.push(integration.name().to_string());
}
let session_flusher = RwLock::new(Some(SessionFlusher::new(
transport.clone(),
options.session_mode,
)));
Client {
options,
transport,
session_flusher,
integrations,
sdk_info,
}
}
pub(crate) fn get_integration<I>(&self) -> Option<&I>
where
I: Integration,
{
let id = TypeId::of::<I>();
let integration = &self.integrations.iter().find(|(iid, _)| *iid == id)?.1;
integration.as_ref().as_any().downcast_ref()
}
fn prepare_event(
&self,
mut event: Event<'static>,
scope: Option<&Scope>,
) -> Option<Event<'static>> {
if event.event_id.is_nil() {
event.event_id = Uuid::new_v4();
}
if event.sdk.is_none() {
event.sdk = Some(Cow::Owned(self.sdk_info.clone()));
}
if let Some(scope) = scope {
event = match scope.apply_to_event(event) {
Some(event) => event,
None => return None,
};
}
for (_, integration) in self.integrations.iter() {
let id = event.event_id;
event = match integration.process_event(event, &self.options) {
Some(event) => event,
None => {
sentry_debug!("integration dropped event {:?}", id);
return None;
}
}
}
if event.release.is_none() {
event.release = self.options.release.clone();
}
if event.environment.is_none() {
event.environment = self.options.environment.clone();
}
if event.server_name.is_none() {
event.server_name = self.options.server_name.clone();
}
if &event.platform == "other" {
event.platform = "native".into();
}
if let Some(ref func) = self.options.before_send {
sentry_debug!("invoking before_send callback");
let id = event.event_id;
if let Some(processed_event) = func(event) {
event = processed_event;
} else {
sentry_debug!("before_send dropped event {:?}", id);
return None;
}
}
if let Some(scope) = scope {
scope.update_session_from_event(&event);
}
if !self.sample_should_send(self.options.sample_rate) {
None
} else {
Some(event)
}
}
pub fn options(&self) -> &ClientOptions {
&self.options
}
pub fn dsn(&self) -> Option<&Dsn> {
self.options.dsn.as_ref()
}
pub fn is_enabled(&self) -> bool {
self.options.dsn.is_some() && self.transport.read().unwrap().is_some()
}
pub fn capture_event(&self, event: Event<'static>, scope: Option<&Scope>) -> Uuid {
if let Some(ref transport) = *self.transport.read().unwrap() {
if let Some(event) = self.prepare_event(event, scope) {
let event_id = event.event_id;
let mut envelope: Envelope = event.into();
if self.options.session_mode == SessionMode::Application {
let session_item = scope.and_then(|scope| {
scope
.session
.lock()
.unwrap()
.as_mut()
.and_then(|session| session.create_envelope_item())
});
if let Some(session_item) = session_item {
envelope.add_item(session_item);
}
}
if let Some(scope) = scope {
for attachment in scope.attachments.iter().cloned() {
envelope.add_item(attachment);
}
}
transport.send_envelope(envelope);
return event_id;
}
}
Default::default()
}
pub fn send_envelope(&self, envelope: Envelope) {
if let Some(ref transport) = *self.transport.read().unwrap() {
transport.send_envelope(envelope);
}
}
pub(crate) fn enqueue_session(&self, session_update: SessionUpdate<'static>) {
if let Some(ref flusher) = *self.session_flusher.read().unwrap() {
flusher.enqueue(session_update);
}
}
pub fn flush(&self, timeout: Option<Duration>) -> bool {
if let Some(ref flusher) = *self.session_flusher.read().unwrap() {
flusher.flush();
}
if let Some(ref transport) = *self.transport.read().unwrap() {
transport.flush(timeout.unwrap_or(self.options.shutdown_timeout))
} else {
true
}
}
pub fn close(&self, timeout: Option<Duration>) -> bool {
drop(self.session_flusher.write().unwrap().take());
let transport_opt = self.transport.write().unwrap().take();
if let Some(transport) = transport_opt {
sentry_debug!("client close; request transport to shut down");
transport.shutdown(timeout.unwrap_or(self.options.shutdown_timeout))
} else {
sentry_debug!("client close; no transport to shut down");
true
}
}
pub fn sample_should_send(&self, rate: f32) -> bool {
if rate >= 1.0 {
true
} else {
random::<f32>() <= rate
}
}
}
impl RefUnwindSafe for Client {}