diff --git a/sentry-core/src/client.rs b/sentry-core/src/client.rs index ecf29022..7dac459f 100644 --- a/sentry-core/src/client.rs +++ b/sentry-core/src/client.rs @@ -23,7 +23,7 @@ impl> From for Client { /// The Sentry client object. pub struct Client { options: ClientOptions, - transport: RwLock>>>, + transport: RwLock>>, } impl fmt::Debug for Client { @@ -74,10 +74,13 @@ impl Client { /// If the DSN on the options is set to `None` the client will be entirely /// disabled. pub fn with_options(options: ClientOptions) -> Client { - let transport = RwLock::new(match options.dsn { - Some(_) => Some(Arc::new(options.transport.create_transport(&options))), - None => None, - }); + let create_transport = || { + options.dsn.as_ref()?; + let factory = options.transport.as_ref()?; + Some(factory.create_transport(&options)) + }; + let transport = RwLock::new(create_transport()); + Client { options, transport } } diff --git a/sentry-core/src/clientoptions.rs b/sentry-core/src/clientoptions.rs index 735f6047..38b1c7fe 100644 --- a/sentry-core/src/clientoptions.rs +++ b/sentry-core/src/clientoptions.rs @@ -1,50 +1,64 @@ use std::borrow::Cow; -use std::ffi::{OsStr, OsString}; use std::fmt; use std::panic::RefUnwindSafe; use std::sync::Arc; use std::time::Duration; use crate::constants::USER_AGENT; -use crate::internals::{Dsn, ParseDsnError}; +use crate::internals::Dsn; +use crate::intodsn::IntoDsn; use crate::protocol::{Breadcrumb, Event}; -use crate::transport::{DefaultTransportFactory, TransportFactory}; -use crate::utils; +use crate::transport::TransportFactory; /// Type alias for before event/breadcrumb handlers. -pub type BeforeCallback = Arc Option + Send + Sync>>; +pub type BeforeCallback = Arc Option + Send + Sync>; /// Configuration settings for the client. +/// +/// These options are explained in more detail in the general +/// [sentry documentation](https://docs.sentry.io/error-reporting/configuration/?platform=rust). +#[derive(Clone)] pub struct ClientOptions { + // Common options /// The DSN to use. If not set the client is effectively disabled. pub dsn: Option, - /// The transport to use. + /// Enables debug mode. /// - /// This is typically either a boxed function taking the client options by - /// reference and returning a `Transport`, a boxed `Arc` or - /// alternatively the `DefaultTransportFactory`. - pub transport: Box, - /// module prefixes that are always considered in_app - pub in_app_include: Vec<&'static str>, - /// module prefixes that are never in_app - pub in_app_exclude: Vec<&'static str>, - /// border frames which indicate a border from a backtrace to - /// useless internals. Some are automatically included. - pub extra_border_frames: Vec<&'static str>, - /// Maximum number of breadcrumbs (0 to disable feature). - pub max_breadcrumbs: usize, - /// Automatically trim backtraces of junk before sending. - pub trim_backtraces: bool, + /// In debug mode debug information is printed to stderr to help you understand what + /// sentry is doing. When the `with_debug_to_log` flag is enabled Sentry will instead + /// log to the `sentry` logger independently of this flag with the `Debug` level. + pub debug: bool, /// The release to be sent with events. pub release: Option>, /// The environment to be sent with events. pub environment: Option>, + /// The sample rate for event submission. (0.0 - 1.0, defaults to 1.0) + pub sample_rate: f32, + /// Maximum number of breadcrumbs. (defaults to 100) + pub max_breadcrumbs: usize, + /// Attaches stacktraces to messages. + pub attach_stacktrace: bool, + /// If turned on some default PII informat is attached. + pub send_default_pii: bool, /// The server name to be reported. pub server_name: Option>, - /// The sample rate for event submission (0.0 - 1.0, defaults to 1.0) - pub sample_rate: f32, - /// The user agent that should be reported. - pub user_agent: Cow<'static, str>, + /// Module prefixes that are always considered "in_app". + pub in_app_include: Vec<&'static str>, + /// Module prefixes that are never "in_app". + pub in_app_exclude: Vec<&'static str>, + // TODO: Integration options + // Hooks + /// Callback that is executed before event sending. + pub before_send: Option>>, + /// Callback that is executed for each Breadcrumb being added. + pub before_breadcrumb: Option>, + // Transport options + /// The transport to use. + /// + /// This is typically either a boxed function taking the client options by + /// reference and returning a `Transport`, a boxed `Arc` or + /// alternatively the `DefaultTransportFactory`. + pub transport: Option>, /// An optional HTTP proxy to use. /// /// This will default to the `http_proxy` environment variable. @@ -56,140 +70,82 @@ pub struct ClientOptions { pub https_proxy: Option>, /// The timeout on client drop for draining events on shutdown. pub shutdown_timeout: Duration, - /// Enables debug mode. - /// - /// In debug mode debug information is printed to stderr to help you understand what - /// sentry is doing. When the `with_debug_to_log` flag is enabled Sentry will instead - /// log to the `sentry` logger independently of this flag with the `Debug` level. - pub debug: bool, - /// Attaches stacktraces to messages. - pub attach_stacktrace: bool, - /// If turned on some default PII informat is attached. - pub send_default_pii: bool, - /// Before send callback. - pub before_send: Option>>, - /// Before breadcrumb add callback. - pub before_breadcrumb: Option>, + // Other options not documented in Unified API + /// Border frames which indicate a border from a backtrace to + /// useless internals. Some are automatically included. + pub extra_border_frames: Vec<&'static str>, + /// Automatically trim backtraces of junk before sending. (defaults to true) + pub trim_backtraces: bool, + /// The user agent that should be reported. + pub user_agent: Cow<'static, str>, } -// make this unwind safe. It's not out of the box because of the contained `BeforeCallback`s. +// Make this unwind safe. It's not out of the box because of the contained `BeforeCallback`s. impl RefUnwindSafe for ClientOptions {} impl fmt::Debug for ClientOptions { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[derive(Debug)] - struct TransportFactory; + struct BeforeSend; + let before_send = self.before_send.as_ref().map(|_| BeforeSend); #[derive(Debug)] - struct BeforeSendSet(bool); + struct BeforeBreadcrumb; + let before_breadcrumb = self.before_breadcrumb.as_ref().map(|_| BeforeBreadcrumb); #[derive(Debug)] - struct BeforeBreadcrumbSet(bool); + struct TransportFactory; + f.debug_struct("ClientOptions") .field("dsn", &self.dsn) - .field("transport", &TransportFactory) - .field("in_app_include", &self.in_app_include) - .field("in_app_exclude", &self.in_app_exclude) - .field("extra_border_frames", &self.extra_border_frames) - .field("max_breadcrumbs", &self.max_breadcrumbs) - .field("trim_backtraces", &self.trim_backtraces) + .field("debug", &self.debug) .field("release", &self.release) .field("environment", &self.environment) - .field("server_name", &self.server_name) .field("sample_rate", &self.sample_rate) - .field("user_agent", &self.user_agent) + .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("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("debug", &self.debug) - .field("attach_stacktrace", &self.attach_stacktrace) - .field("send_default_pii", &self.send_default_pii) - .field("before_send", &BeforeSendSet(self.before_send.is_some())) - .field( - "before_breadcrumb", - &BeforeBreadcrumbSet(self.before_breadcrumb.is_some()), - ) + .field("extra_border_frames", &self.extra_border_frames) + .field("trim_backtraces", &self.trim_backtraces) + .field("user_agent", &self.user_agent) .finish() } } -impl Clone for ClientOptions { - fn clone(&self) -> ClientOptions { - ClientOptions { - dsn: self.dsn.clone(), - transport: self.transport.clone_factory(), - in_app_include: self.in_app_include.clone(), - in_app_exclude: self.in_app_exclude.clone(), - extra_border_frames: self.extra_border_frames.clone(), - max_breadcrumbs: self.max_breadcrumbs, - trim_backtraces: self.trim_backtraces, - release: self.release.clone(), - environment: self.environment.clone(), - server_name: self.server_name.clone(), - sample_rate: self.sample_rate, - user_agent: self.user_agent.clone(), - http_proxy: self.http_proxy.clone(), - https_proxy: self.https_proxy.clone(), - shutdown_timeout: self.shutdown_timeout, - debug: self.debug, - attach_stacktrace: self.attach_stacktrace, - send_default_pii: self.send_default_pii, - before_send: self.before_send.clone(), - before_breadcrumb: self.before_breadcrumb.clone(), - } - } -} - impl Default for ClientOptions { fn default() -> ClientOptions { ClientOptions { - // any invalid dsn including the empty string disables the dsn - dsn: std::env::var("SENTRY_DSN") - .ok() - .and_then(|dsn| dsn.parse::().ok()), - transport: Box::new(DefaultTransportFactory), - in_app_include: vec![], - in_app_exclude: vec![], - extra_border_frames: vec![], - max_breadcrumbs: 100, - trim_backtraces: true, - release: std::env::var("SENTRY_RELEASE").ok().map(Cow::Owned), - environment: std::env::var("SENTRY_ENVIRONMENT") - .ok() - .map(Cow::Owned) - .or_else(|| { - Some(Cow::Borrowed(if cfg!(debug_assertions) { - "debug" - } else { - "release" - })) - }), - server_name: utils::server_name().map(Cow::Owned), - sample_rate: 1.0, - user_agent: Cow::Borrowed(&USER_AGENT), - http_proxy: std::env::var("http_proxy").ok().map(Cow::Owned), - https_proxy: std::env::var("https_proxy") - .ok() - .map(Cow::Owned) - .or_else(|| std::env::var("HTTPS_PROXY").ok().map(Cow::Owned)) - .or_else(|| std::env::var("http_proxy").ok().map(Cow::Owned)), - shutdown_timeout: Duration::from_secs(2), + dsn: None, debug: false, + release: None, + environment: None, + sample_rate: 1.0, + max_breadcrumbs: 100, attach_stacktrace: false, send_default_pii: false, + server_name: None, + in_app_include: vec![], + in_app_exclude: vec![], before_send: None, before_breadcrumb: None, + transport: None, + http_proxy: None, + https_proxy: None, + shutdown_timeout: Duration::from_secs(2), + extra_border_frames: vec![], + trim_backtraces: true, + user_agent: Cow::Borrowed(&USER_AGENT), } } } -/// Helper trait to convert a string into an `Option`. -/// -/// This converts a value into a DSN by parsing. The empty string or -/// null values result in no DSN being parsed. -pub trait IntoDsn { - /// Converts the value into a `Result, E>`. - fn into_dsn(self) -> Result, ParseDsnError>; -} - impl 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"); @@ -205,65 +161,3 @@ impl From for ClientOptions { } } } - -impl IntoDsn for Option { - fn into_dsn(self) -> Result, ParseDsnError> { - match self { - Some(into_dsn) => into_dsn.into_dsn(), - None => Ok(None), - } - } -} - -impl IntoDsn for () { - fn into_dsn(self) -> Result, ParseDsnError> { - Ok(None) - } -} - -impl<'a> IntoDsn for &'a str { - fn into_dsn(self) -> Result, ParseDsnError> { - if self.is_empty() { - Ok(None) - } else { - self.parse().map(Some) - } - } -} - -impl<'a> IntoDsn for Cow<'a, str> { - fn into_dsn(self) -> Result, ParseDsnError> { - let x: &str = &self; - x.into_dsn() - } -} - -impl<'a> IntoDsn for &'a OsStr { - fn into_dsn(self) -> Result, ParseDsnError> { - self.to_string_lossy().into_dsn() - } -} - -impl IntoDsn for OsString { - fn into_dsn(self) -> Result, ParseDsnError> { - self.as_os_str().into_dsn() - } -} - -impl IntoDsn for String { - fn into_dsn(self) -> Result, ParseDsnError> { - self.as_str().into_dsn() - } -} - -impl<'a> IntoDsn for &'a Dsn { - fn into_dsn(self) -> Result, ParseDsnError> { - Ok(Some(self.clone())) - } -} - -impl IntoDsn for Dsn { - fn into_dsn(self) -> Result, ParseDsnError> { - Ok(Some(self)) - } -} diff --git a/sentry-core/src/intodsn.rs b/sentry-core/src/intodsn.rs new file mode 100644 index 00000000..d720db8c --- /dev/null +++ b/sentry-core/src/intodsn.rs @@ -0,0 +1,75 @@ +use std::borrow::Cow; +use std::ffi::{OsStr, OsString}; + +use sentry_types::{Dsn, ParseDsnError}; + +/// Helper trait to convert a string into an `Option`. +/// +/// This converts a value into a DSN by parsing. The empty string or +/// null values result in no DSN being parsed. +pub trait IntoDsn { + /// Converts the value into a `Result, E>`. + fn into_dsn(self) -> Result, ParseDsnError>; +} + +impl IntoDsn for Option { + fn into_dsn(self) -> Result, ParseDsnError> { + match self { + Some(into_dsn) => into_dsn.into_dsn(), + None => Ok(None), + } + } +} + +impl IntoDsn for () { + fn into_dsn(self) -> Result, ParseDsnError> { + Ok(None) + } +} + +impl<'a> IntoDsn for &'a str { + fn into_dsn(self) -> Result, ParseDsnError> { + if self.is_empty() { + Ok(None) + } else { + self.parse().map(Some) + } + } +} + +impl<'a> IntoDsn for Cow<'a, str> { + fn into_dsn(self) -> Result, ParseDsnError> { + let x: &str = &self; + x.into_dsn() + } +} + +impl<'a> IntoDsn for &'a OsStr { + fn into_dsn(self) -> Result, ParseDsnError> { + self.to_string_lossy().into_dsn() + } +} + +impl IntoDsn for OsString { + fn into_dsn(self) -> Result, ParseDsnError> { + self.as_os_str().into_dsn() + } +} + +impl IntoDsn for String { + fn into_dsn(self) -> Result, ParseDsnError> { + self.as_str().into_dsn() + } +} + +impl<'a> IntoDsn for &'a Dsn { + fn into_dsn(self) -> Result, ParseDsnError> { + Ok(Some(self.clone())) + } +} + +impl IntoDsn for Dsn { + fn into_dsn(self) -> Result, ParseDsnError> { + Ok(Some(self)) + } +} diff --git a/sentry-core/src/lib.rs b/sentry-core/src/lib.rs index 96db7f18..cdbb8434 100644 --- a/sentry-core/src/lib.rs +++ b/sentry-core/src/lib.rs @@ -132,6 +132,8 @@ mod clientoptions; #[cfg(feature = "with_client_implementation")] mod constants; #[cfg(feature = "with_client_implementation")] +mod intodsn; +#[cfg(feature = "with_client_implementation")] mod transport; #[cfg(feature = "with_client_implementation")] pub mod utils; @@ -149,10 +151,9 @@ pub mod internals { pub use crate::scope::ScopeGuard; #[cfg(feature = "with_client_implementation")] - pub use crate::{ - clientoptions::IntoDsn, - transport::{Transport, TransportFactory}, - }; + pub use crate::intodsn::IntoDsn; + #[cfg(feature = "with_client_implementation")] + pub use crate::transport::{Transport, TransportFactory}; pub use sentry_types::{ Auth, ChronoParseError, DateTime, DebugId, Dsn, ParseDebugIdError, ParseDsnError, diff --git a/sentry-core/src/test.rs b/sentry-core/src/test.rs index 03c54afe..81f187a7 100644 --- a/sentry-core/src/test.rs +++ b/sentry-core/src/test.rs @@ -35,7 +35,7 @@ lazy_static::lazy_static! { /// /// Example usage: /// -/// ```rust +/// ``` /// # use sentry_core as sentry; /// use std::sync::Arc; /// use sentry::{Hub, ClientOptions}; @@ -44,7 +44,7 @@ lazy_static::lazy_static! { /// let transport = TestTransport::new(); /// let options = ClientOptions { /// dsn: Some("https://public@example.com/1".parse().unwrap()), -/// transport: Box::new(transport.clone()), +/// transport: Some(Arc::new(transport.clone())), /// ..ClientOptions::default() /// }; /// Hub::current().bind_client(Some(Arc::new(options.into()))); @@ -98,7 +98,7 @@ pub fn with_captured_events_options>( let transport = TestTransport::new(); let mut options = options.into(); options.dsn = Some(options.dsn.unwrap_or_else(|| TEST_DSN.clone())); - options.transport = Box::new(transport.clone()); + options.transport = Some(Arc::new(transport.clone())); Hub::run( Arc::new(Hub::new( Some(Arc::new(options.into())), diff --git a/sentry-core/src/transport.rs b/sentry-core/src/transport.rs index 27b69dbe..6edf29eb 100644 --- a/sentry-core/src/transport.rs +++ b/sentry-core/src/transport.rs @@ -39,39 +39,29 @@ pub trait Transport: Send + Sync + 'static { } } -pub trait InternalTransportFactoryClone { - fn clone_factory(&self) -> Box; -} - -impl InternalTransportFactoryClone for T { - fn clone_factory(&self) -> Box { - Box::new(self.clone()) - } -} - /// A factory creating transport instances. /// /// Because options are potentially reused between different clients the /// options do not actually contain a transport but a factory object that /// can create transports instead. /// -/// The factory has a single method that creates a new boxed transport. +/// The factory has a single method that creates a new arced transport. /// Because transports can be wrapped in `Arc`s and those are clonable /// any `Arc` is also a valid transport factory. This for /// instance lets you put a `Arc` directly into the options. /// /// This is automatically implemented for all closures optionally taking /// options and returning a boxed factory. -pub trait TransportFactory: Send + Sync + InternalTransportFactoryClone { +pub trait TransportFactory: Send + Sync { /// Given some options creates a transport. - fn create_transport(&self, options: &ClientOptions) -> Box; + fn create_transport(&self, options: &ClientOptions) -> Arc; } impl TransportFactory for F where - F: Fn(&ClientOptions) -> Box + Clone + Send + Sync + 'static, + F: Fn(&ClientOptions) -> Arc + Clone + Send + Sync + 'static, { - fn create_transport(&self, options: &ClientOptions) -> Box { + fn create_transport(&self, options: &ClientOptions) -> Arc { (*self)(options) } } @@ -87,9 +77,9 @@ impl Transport for Arc { } impl TransportFactory for Arc { - fn create_transport(&self, options: &ClientOptions) -> Box { + fn create_transport(&self, options: &ClientOptions) -> Arc { let _options = options; - Box::new(self.clone()) + self.clone() } } @@ -102,10 +92,10 @@ impl TransportFactory for Arc { pub struct DefaultTransportFactory; impl TransportFactory for DefaultTransportFactory { - fn create_transport(&self, options: &ClientOptions) -> Box { + fn create_transport(&self, options: &ClientOptions) -> Arc { #[cfg(any(feature = "with_reqwest_transport", feature = "with_curl_transport"))] { - Box::new(HttpTransport::new(options)) + Arc::new(HttpTransport::new(options)) } #[cfg(not(any(feature = "with_reqwest_transport", feature = "with_curl_transport")))] { diff --git a/sentry/Cargo.toml b/sentry/Cargo.toml index 803b7d21..e3e597f0 100644 --- a/sentry/Cargo.toml +++ b/sentry/Cargo.toml @@ -25,9 +25,9 @@ with_client_implementation = ["sentry-core/with_client_implementation"] with_backtrace = ["sentry-core/with_backtrace"] with_panic = ["sentry-core/with_panic"] with_failure = ["sentry-core/with_failure"] -with_log = ["sentry-core/with_log"] +with_log = ["log", "sentry-core/with_log"] with_debug_to_log = ["log", "sentry-core/with_debug_to_log"] -with_env_logger = ["sentry-core/with_env_logger"] +with_env_logger = ["with_log", "sentry-core/with_env_logger"] with_error_chain = ["sentry-core/with_error_chain"] with_device_info = ["sentry-core/with_device_info"] with_rust_info = ["sentry-core/with_rust_info"] diff --git a/sentry/examples/before-send.rs b/sentry/examples/before-send.rs index 94c498f0..b0c0fc51 100644 --- a/sentry/examples/before-send.rs +++ b/sentry/examples/before-send.rs @@ -5,14 +5,14 @@ fn main() { dsn: "https://a94ae32be2584e0bbd7a4cbb95971fee@sentry.io/1041156" .parse() .ok(), - before_send: Some(Arc::new(Box::new(|mut event| { + before_send: Some(Arc::new(|mut event| { event.request = Some(sentry::protocol::Request { url: Some("https://example.com/".parse().unwrap()), method: Some("GET".into()), ..Default::default() }); Some(event) - }))), + })), debug: true, ..Default::default() }); diff --git a/sentry/examples/event-processors.rs b/sentry/examples/event-processors.rs index 178f452a..cd0c0c54 100644 --- a/sentry/examples/event-processors.rs +++ b/sentry/examples/event-processors.rs @@ -1,7 +1,5 @@ fn main() { - let client = - sentry::Client::from_config("https://a94ae32be2584e0bbd7a4cbb95971fee@sentry.io/1041156"); - let _sentry = sentry::init(client); + let _sentry = sentry::init("https://a94ae32be2584e0bbd7a4cbb95971fee@sentry.io/1041156"); sentry::configure_scope(|scope| { scope.add_event_processor(Box::new(move |mut event| { diff --git a/sentry/examples/init-with-client.rs b/sentry/examples/init-with-client.rs deleted file mode 100644 index 793a86fd..00000000 --- a/sentry/examples/init-with-client.rs +++ /dev/null @@ -1,12 +0,0 @@ -fn main() { - let client = - sentry::Client::from_config("https://a94ae32be2584e0bbd7a4cbb95971fee@sentry.io/1041156"); - let _sentry = sentry::init(client); - sentry::configure_scope(|scope| { - scope.set_fingerprint(Some(["a-message"].as_ref())); - scope.set_tag("foo", "bar"); - }); - - let id = sentry::capture_message("This is recorded as a warning now", sentry::Level::Warning); - println!("sent event {}", id); -} diff --git a/sentry/src/defaults.rs b/sentry/src/defaults.rs new file mode 100644 index 00000000..65070fd3 --- /dev/null +++ b/sentry/src/defaults.rs @@ -0,0 +1,71 @@ +use std::env; +use std::{borrow::Cow, sync::Arc}; + +use sentry_core::transports::DefaultTransportFactory; + +use crate::internals::Dsn; +use crate::utils; +use crate::ClientOptions; + +/// Apply default client options. +/// +/// Extends the given `ClientOptions` with default options such as a default +/// transport, a set of default integrations if not requested otherwise, and +/// also sets the `dsn`, `release`, `environment`, and proxy settings based on +/// environment variables. +/// +/// # Examples +/// ``` +/// std::env::set_var("SENTRY_RELEASE", "release-from-env"); +/// +/// let options = sentry::ClientOptions::default(); +/// assert_eq!(options.release, None); +/// assert!(options.transport.is_none()); +/// +/// let options = sentry::internals::apply_defaults(options); +/// assert_eq!(options.release, Some("release-from-env".into())); +/// assert!(options.transport.is_some()); +/// ``` +pub fn apply_defaults(mut opts: ClientOptions) -> ClientOptions { + // TODO: move this to an integration + if opts.server_name.is_none() { + opts.server_name = utils::server_name().map(Cow::Owned); + } + if opts.transport.is_none() { + opts.transport = Some(Arc::new(DefaultTransportFactory)); + } + if opts.dsn.is_none() { + opts.dsn = env::var("SENTRY_DSN") + .ok() + .and_then(|dsn| dsn.parse::().ok()); + } + if opts.release.is_none() { + opts.release = env::var("SENTRY_RELEASE").ok().map(Cow::Owned); + } + if opts.environment.is_none() { + opts.environment = env::var("SENTRY_ENVIRONMENT") + .ok() + .map(Cow::Owned) + .or_else(|| { + Some(Cow::Borrowed(if cfg!(debug_assertions) { + "debug" + } else { + "release" + })) + }); + } + if opts.http_proxy.is_none() { + opts.http_proxy = std::env::var("HTTP_PROXY") + .ok() + .map(Cow::Owned) + .or_else(|| std::env::var("http_proxy").ok().map(Cow::Owned)); + } + if opts.https_proxy.is_none() { + opts.https_proxy = std::env::var("HTTPS_PROXY") + .ok() + .map(Cow::Owned) + .or_else(|| std::env::var("https_proxy").ok().map(Cow::Owned)) + .or_else(|| opts.http_proxy.clone()); + } + opts +} diff --git a/sentry/src/init.rs b/sentry/src/init.rs index 2b9d0f59..dd2a2c7a 100644 --- a/sentry/src/init.rs +++ b/sentry/src/init.rs @@ -1,8 +1,9 @@ use std::sync::Arc; -use crate::{Client, Hub}; use sentry_core::sentry_debug; +use crate::{defaults::apply_defaults, Client, ClientOptions, Hub}; + /// Helper struct that is returned from `init`. /// /// When this is dropped events are drained with a 1 second timeout. @@ -10,6 +11,13 @@ use sentry_core::sentry_debug; events can be sent. If you do want to ignore this use mem::forget on it."] pub struct ClientInitGuard(Arc); +impl std::ops::Deref for ClientInitGuard { + type Target = Client; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl ClientInitGuard { /// Quick check if the client is enabled. pub fn is_enabled(&self) -> bool { @@ -33,7 +41,7 @@ impl Drop for ClientInitGuard { /// This returns a client init guard that must kept in scope will help the /// client send events before the application closes. When the guard is /// dropped then the transport that was initialized shuts down and no -/// further events can be set on it. +/// further events can be sent on it. /// /// If you don't want (or can) keep the guard around it's permissible to /// call `mem::forget` on it. @@ -66,11 +74,24 @@ impl Drop for ClientInitGuard { /// ``` /// /// This behaves similar to creating a client by calling `Client::from_config` -/// and to then bind it to the hub except it's also possible to directly pass -/// a client. For more information about the formats accepted see -/// `Client::from_config`. -pub fn init>(cfg: C) -> ClientInitGuard { - let client = Arc::new(cfg.into()); +/// and to then bind it to the hub except it also applies default integrations, +/// a default transport, as well as other options populated from environment +/// variables. +/// For more information about the formats accepted see `Client::from_config`, +/// and `ClientOptions`. +/// +/// # Panics +/// +/// This will panic when the provided DSN is invalid. +/// If you want to handle invalid DSNs you need to parse them manually by +/// calling `parse` on it and handle the error. +pub fn init(opts: C) -> ClientInitGuard +where + C: Into, +{ + let opts = apply_defaults(opts.into()); + let client = Arc::new(Client::from(opts)); + Hub::with(|hub| hub.bind_client(Some(client.clone()))); if let Some(dsn) = client.dsn() { sentry_debug!("enabled sentry client for DSN {}", dsn); diff --git a/sentry/src/lib.rs b/sentry/src/lib.rs index 7d31eaf2..a2dcfc64 100644 --- a/sentry/src/lib.rs +++ b/sentry/src/lib.rs @@ -111,6 +111,8 @@ //! * `with_test_support`: Enables the test support module. #![warn(missing_docs)] +#[cfg(feature = "with_client_implementation")] +mod defaults; #[cfg(feature = "with_client_implementation")] mod init; @@ -127,6 +129,9 @@ pub mod internals { #[cfg(feature = "with_client_implementation")] pub use crate::init::ClientInitGuard; + + #[cfg(feature = "with_client_implementation")] + pub use crate::defaults::apply_defaults; } #[cfg(feature = "with_client_implementation")] diff --git a/sentry/src/transport.rs b/sentry/src/transport.rs new file mode 100644 index 00000000..e69de29b diff --git a/sentry/tests/test_basic.rs b/sentry/tests/test_basic.rs index cbecc316..653c49fa 100644 --- a/sentry/tests/test_basic.rs +++ b/sentry/tests/test_basic.rs @@ -92,12 +92,12 @@ fn test_factory() { let events_for_options = events.clone(); let options = sentry::ClientOptions { dsn: "http://foo@example.com/42".parse().ok(), - transport: Box::new( - move |opts: &sentry::ClientOptions| -> Box { + transport: Some(Arc::new( + move |opts: &sentry::ClientOptions| -> Arc { assert_eq!(opts.dsn.as_ref().unwrap().host(), "example.com"); - Box::new(TestTransport(events_for_options.clone())) + Arc::new(TestTransport(events_for_options.clone())) }, - ), + )), ..Default::default() }; diff --git a/sentry/tests/test_client.rs b/sentry/tests/test_client.rs index 8ca7b5f1..8afb6a7a 100644 --- a/sentry/tests/test_client.rs +++ b/sentry/tests/test_client.rs @@ -38,7 +38,7 @@ fn test_unwind_safe() { let transport = sentry::test::TestTransport::new(); let options = sentry::ClientOptions { dsn: Some("https://public@example.com/1".parse().unwrap()), - transport: Box::new(transport.clone()), + transport: Some(Arc::new(transport.clone())), ..sentry::ClientOptions::default() };