use std::borrow::Cow;
use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use crate::constants::USER_AGENT;
use crate::performance::{TracesSampler, TransactionContext};
use crate::protocol::{Breadcrumb, Event, Log, Metric};
use crate::types::Dsn;
use crate::{Integration, IntoDsn, TransportFactory};
pub type BeforeCallback<T> = Arc<dyn Fn(T) -> Option<T> + Send + Sync>;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SessionMode {
Application,
Request,
}
#[derive(Clone, Copy, PartialEq)]
pub enum MaxRequestBodySize {
None,
Small,
Medium,
Always,
Explicit(usize),
}
impl MaxRequestBodySize {
pub fn is_within_size_limit(&self, content_length: usize) -> bool {
match self {
MaxRequestBodySize::None => false,
MaxRequestBodySize::Small => content_length <= 1_000,
MaxRequestBodySize::Medium => content_length <= 10_000,
MaxRequestBodySize::Always => true,
MaxRequestBodySize::Explicit(size) => content_length <= *size,
}
}
}
#[derive(Clone)]
#[must_use = "ClientOptions must be passed to sentry::init to have any effect"]
pub struct ClientOptions {
pub dsn: Option<Dsn>,
pub debug: bool,
pub release: Option<Cow<'static, str>>,
pub environment: Option<Cow<'static, str>>,
pub sample_rate: f32,
pub traces_sample_rate: f32,
pub traces_sampler: Option<Arc<TracesSampler>>,
pub max_breadcrumbs: usize,
pub attach_stacktrace: bool,
pub send_default_pii: bool,
pub server_name: Option<Cow<'static, str>>,
pub in_app_include: Vec<&'static str>,
pub in_app_exclude: Vec<&'static str>,
pub integrations: Vec<Arc<dyn Integration>>,
pub default_integrations: bool,
pub before_send: Option<BeforeCallback<Event<'static>>>,
pub before_breadcrumb: Option<BeforeCallback<Breadcrumb>>,
pub before_send_log: Option<BeforeCallback<Log>>,
pub transport: Option<Arc<dyn TransportFactory>>,
pub http_proxy: Option<Cow<'static, str>>,
pub https_proxy: Option<Cow<'static, str>>,
pub shutdown_timeout: Duration,
pub max_request_body_size: MaxRequestBodySize,
pub enable_logs: bool,
pub enable_metrics: bool,
pub before_send_metric: Option<BeforeCallback<Metric>>,
pub accept_invalid_certs: bool,
pub auto_session_tracking: bool,
pub session_mode: SessionMode,
pub user_agent: Cow<'static, str>,
}
impl ClientOptions {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn dsn(self, dsn: &str) -> Self {
let dsn = Some(dsn.parse().expect("invalid value for DSN"));
Self { dsn, ..self }
}
#[inline]
pub fn debug(self, debug: bool) -> Self {
Self { debug, ..self }
}
#[inline]
pub fn release<T>(self, release: T) -> Self
where
T: Into<Cow<'static, str>>,
{
let release = Some(release.into());
Self { release, ..self }
}
#[inline]
pub fn maybe_release<T>(self, release: Option<T>) -> Self
where
T: Into<Cow<'static, str>>,
{
match release {
Some(release) => self.release(release),
None => self,
}
}
#[inline]
pub fn environment<T>(self, environment: T) -> Self
where
T: Into<Cow<'static, str>>,
{
let environment = Some(environment.into());
Self {
environment,
..self
}
}
#[inline]
pub fn sample_rate(self, sample_rate: f32) -> Self {
if !(0.0..=1.0).contains(&sample_rate) {
panic!("Sample rate {sample_rate} is outside the allowed range [0.0, 1.0].")
}
Self {
sample_rate,
..self
}
}
#[inline]
pub fn traces_sample_rate(self, traces_sample_rate: f32) -> Self {
if !(0.0..=1.0).contains(&traces_sample_rate) {
panic!(
"Traces sample rate {traces_sample_rate} is outside the allowed range [0.0, 1.0]."
)
}
Self {
traces_sample_rate,
..self
}
}
#[inline]
pub fn traces_sampler<F>(self, traces_sampler: F) -> Self
where
F: Fn(&TransactionContext) -> f32 + Send + Sync + 'static,
{
let traces_sampler = Some(Arc::new(traces_sampler) as Arc<TracesSampler>);
Self {
traces_sampler,
..self
}
}
#[inline]
pub fn max_breadcrumbs(self, max_breadcrumbs: usize) -> Self {
Self {
max_breadcrumbs,
..self
}
}
#[inline]
pub fn attach_stacktrace(self, attach_stacktrace: bool) -> Self {
Self {
attach_stacktrace,
..self
}
}
#[inline]
pub fn send_default_pii(self, send_default_pii: bool) -> Self {
Self {
send_default_pii,
..self
}
}
#[inline]
pub fn server_name<T>(self, server_name: T) -> Self
where
T: Into<Cow<'static, str>>,
{
let server_name = Some(server_name.into());
Self {
server_name,
..self
}
}
#[inline]
pub fn in_app_include<I>(self, in_app_include: I) -> Self
where
I: IntoIterator<Item = &'static str>,
{
let in_app_include = in_app_include.into_iter().collect();
Self {
in_app_include,
..self
}
}
#[inline]
pub fn in_app_exclude<I>(self, in_app_exclude: I) -> Self
where
I: IntoIterator<Item = &'static str>,
{
let in_app_exclude = in_app_exclude.into_iter().collect();
Self {
in_app_exclude,
..self
}
}
#[inline]
pub fn integrations<I>(self, integrations: I) -> Self
where
I: IntoIterator<Item = Arc<dyn Integration>>,
{
let integrations = integrations.into_iter().collect();
Self {
integrations,
..self
}
}
#[inline]
pub fn default_integrations(self, default_integrations: bool) -> Self {
Self {
default_integrations,
..self
}
}
#[inline]
pub fn before_send<F>(self, before_send: F) -> Self
where
F: Fn(Event<'static>) -> Option<Event<'static>> + Send + Sync + 'static,
{
let before_send = Some(Arc::new(before_send) as BeforeCallback<Event<'static>>);
Self {
before_send,
..self
}
}
#[inline]
pub fn before_breadcrumb<F>(self, before_breadcrumb: F) -> Self
where
F: Fn(Breadcrumb) -> Option<Breadcrumb> + Send + Sync + 'static,
{
let before_breadcrumb = Some(Arc::new(before_breadcrumb) as BeforeCallback<Breadcrumb>);
Self {
before_breadcrumb,
..self
}
}
#[cfg(feature = "logs")]
#[inline]
pub fn before_send_log<F>(self, before_send_log: F) -> Self
where
F: Fn(Log) -> Option<Log> + Send + Sync + 'static,
{
let before_send_log = Some(Arc::new(before_send_log) as BeforeCallback<Log>);
Self {
before_send_log,
..self
}
}
#[cfg(feature = "metrics")]
#[inline]
pub fn before_send_metric<F>(self, before_send_metric: F) -> Self
where
F: Fn(Metric) -> Option<Metric> + Send + Sync + 'static,
{
let before_send_metric = Some(Arc::new(before_send_metric) as BeforeCallback<Metric>);
Self {
before_send_metric,
..self
}
}
#[inline]
pub fn transport<T: TransportFactory + 'static>(self, transport: T) -> Self {
let transport = Some(Arc::new(transport) as Arc<dyn TransportFactory>);
Self { transport, ..self }
}
#[inline]
pub fn http_proxy<T>(self, http_proxy: T) -> Self
where
T: Into<Cow<'static, str>>,
{
let http_proxy = Some(http_proxy.into());
Self { http_proxy, ..self }
}
#[inline]
pub fn https_proxy<T>(self, https_proxy: T) -> Self
where
T: Into<Cow<'static, str>>,
{
let https_proxy = Some(https_proxy.into());
Self {
https_proxy,
..self
}
}
#[inline]
pub fn shutdown_timeout(self, shutdown_timeout: Duration) -> Self {
Self {
shutdown_timeout,
..self
}
}
#[inline]
pub fn max_request_body_size(self, max_request_body_size: MaxRequestBodySize) -> Self {
Self {
max_request_body_size,
..self
}
}
#[inline]
pub fn enable_logs(self, enable_logs: bool) -> Self {
Self {
enable_logs,
..self
}
}
#[inline]
pub fn enable_metrics(self, enable_metrics: bool) -> Self {
Self {
enable_metrics,
..self
}
}
#[inline]
pub fn accept_invalid_certs(self, accept_invalid_certs: bool) -> Self {
Self {
accept_invalid_certs,
..self
}
}
#[cfg(feature = "release-health")]
#[inline]
pub fn auto_session_tracking(self, auto_session_tracking: bool) -> Self {
Self {
auto_session_tracking,
..self
}
}
#[cfg(feature = "release-health")]
#[inline]
pub fn session_mode(self, session_mode: SessionMode) -> Self {
Self {
session_mode,
..self
}
}
#[inline]
pub fn user_agent<T>(self, user_agent: T) -> Self
where
T: Into<Cow<'static, str>>,
{
let user_agent = user_agent.into();
Self { user_agent, ..self }
}
#[inline]
pub fn add_integration<I: Integration>(mut self, integration: I) -> Self {
self.integrations.push(Arc::new(integration));
self
}
}
impl fmt::Debug for ClientOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[derive(Debug)]
struct BeforeSend;
let before_send = self.before_send.as_ref().map(|_| BeforeSend);
#[derive(Debug)]
struct BeforeBreadcrumb;
let before_breadcrumb = self.before_breadcrumb.as_ref().map(|_| BeforeBreadcrumb);
let before_send_log = {
#[derive(Debug)]
struct BeforeSendLog;
self.before_send_log.as_ref().map(|_| BeforeSendLog)
};
let before_send_metric = {
#[derive(Debug)]
struct BeforeSendMetric;
self.before_send_metric.as_ref().map(|_| BeforeSendMetric)
};
#[derive(Debug)]
struct TransportFactory;
let integrations: Vec<_> = self.integrations.iter().map(|i| i.name()).collect();
let mut debug_struct = f.debug_struct("ClientOptions");
debug_struct
.field("dsn", &self.dsn)
.field("debug", &self.debug)
.field("release", &self.release)
.field("environment", &self.environment)
.field("sample_rate", &self.sample_rate)
.field("traces_sample_rate", &self.traces_sample_rate)
.field(
"traces_sampler",
&self
.traces_sampler
.as_ref()
.map(|arc| std::ptr::addr_of!(**arc)),
)
.field("max_breadcrumbs", &self.max_breadcrumbs)
.field("attach_stacktrace", &self.attach_stacktrace)
.field("send_default_pii", &self.send_default_pii)
.field("server_name", &self.server_name)
.field("in_app_include", &self.in_app_include)
.field("in_app_exclude", &self.in_app_exclude)
.field("integrations", &integrations)
.field("default_integrations", &self.default_integrations)
.field("before_send", &before_send)
.field("before_breadcrumb", &before_breadcrumb)
.field("transport", &TransportFactory)
.field("http_proxy", &self.http_proxy)
.field("https_proxy", &self.https_proxy)
.field("shutdown_timeout", &self.shutdown_timeout)
.field("accept_invalid_certs", &self.accept_invalid_certs)
.field("auto_session_tracking", &self.auto_session_tracking)
.field("session_mode", &self.session_mode)
.field("enable_logs", &self.enable_logs)
.field("before_send_log", &before_send_log)
.field("enable_metrics", &self.enable_metrics)
.field("before_send_metric", &before_send_metric)
.field("user_agent", &self.user_agent)
.finish()
}
}
impl Default for ClientOptions {
fn default() -> ClientOptions {
ClientOptions {
dsn: None,
debug: false,
release: None,
environment: None,
sample_rate: 1.0,
traces_sample_rate: 0.0,
traces_sampler: None,
max_breadcrumbs: 100,
attach_stacktrace: false,
send_default_pii: false,
server_name: None,
in_app_include: vec![],
in_app_exclude: vec![],
integrations: vec![],
default_integrations: true,
before_send: None,
before_breadcrumb: None,
transport: None,
http_proxy: None,
https_proxy: None,
shutdown_timeout: Duration::from_secs(2),
accept_invalid_certs: false,
auto_session_tracking: false,
session_mode: SessionMode::Application,
user_agent: Cow::Borrowed(USER_AGENT),
max_request_body_size: MaxRequestBodySize::Medium,
enable_logs: true,
before_send_log: None,
enable_metrics: true,
before_send_metric: None,
}
}
}
impl<T: IntoDsn> From<(T, ClientOptions)> for ClientOptions {
fn from((into_dsn, mut opts): (T, ClientOptions)) -> ClientOptions {
opts.dsn = into_dsn.into_dsn().expect("invalid value for DSN");
opts
}
}
impl<T: IntoDsn> From<T> for ClientOptions {
fn from(into_dsn: T) -> ClientOptions {
ClientOptions {
dsn: into_dsn.into_dsn().expect("invalid value for DSN"),
..ClientOptions::default()
}
}
}