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 crate::constants::SDK_INFO;
use crate::protocol::{ClientSdkInfo, Event};
use crate::types::{Dsn, Uuid};
use crate::{ClientOptions, Envelope, Hub, Integration, Scope, Transport};
impl<T: Into<ClientOptions>> From<T> for Client {
fn from(o: T) -> Client {
Client::with_options(o.into())
}
}
pub struct Client {
options: ClientOptions,
transport: RwLock<Option<Arc<dyn Transport>>>,
integrations: Vec<(TypeId, Arc<dyn Integration>)>,
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 {
Client {
options: self.options.clone(),
transport: RwLock::new(self.transport.read().unwrap().clone()),
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 = 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());
}
Client {
options,
transport,
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 let Some(scope) = scope {
scope.update_session_from_event(&event);
}
if !self.sample_should_send() {
return None;
}
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;
func(event).or_else(move || {
sentry_debug!("before_send dropped event {:?}", id);
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();
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);
}
transport.send_envelope(envelope);
return event_id;
}
}
Default::default()
}
pub(crate) fn capture_envelope(&self, envelope: Envelope) {
if let Some(ref transport) = *self.transport.read().unwrap() {
transport.send_envelope(envelope);
}
}
pub fn close(&self, timeout: Option<Duration>) -> bool {
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
}
}
fn sample_should_send(&self) -> bool {
let rate = self.options.sample_rate;
if rate >= 1.0 {
true
} else {
random::<f32>() <= rate
}
}
}
impl RefUnwindSafe for Client {}