ckb_sentry_core/
client.rs

1use std::any::TypeId;
2use std::borrow::Cow;
3use std::fmt;
4use std::panic::RefUnwindSafe;
5use std::sync::Arc;
6use std::sync::RwLock;
7use std::time::Duration;
8
9use rand::random;
10use sentry_types::protocol::v7::SessionUpdate;
11
12use crate::constants::SDK_INFO;
13use crate::protocol::{ClientSdkInfo, Event};
14use crate::session::SessionFlusher;
15use crate::types::{Dsn, Uuid};
16use crate::{ClientOptions, Envelope, Hub, Integration, Scope, Transport};
17
18impl<T: Into<ClientOptions>> From<T> for Client {
19    fn from(o: T) -> Client {
20        Client::with_options(o.into())
21    }
22}
23
24pub(crate) type TransportArc = Arc<RwLock<Option<Arc<dyn Transport>>>>;
25
26/// The Sentry Client.
27///
28/// The Client is responsible for event processing and sending events to the
29/// sentry server via the configured [`Transport`]. It can be created from a
30/// [`ClientOptions`].
31///
32/// See the [Unified API] document for more details.
33///
34/// # Examples
35///
36/// ```
37/// sentry::Client::from(sentry::ClientOptions::default());
38/// ```
39///
40/// [`ClientOptions`]: struct.ClientOptions.html
41/// [`Transport`]: trait.Transport.html
42/// [Unified API]: https://develop.sentry.dev/sdk/unified-api/
43pub struct Client {
44    options: ClientOptions,
45    transport: TransportArc,
46    session_flusher: SessionFlusher,
47    integrations: Vec<(TypeId, Arc<dyn Integration>)>,
48    sdk_info: ClientSdkInfo,
49}
50
51impl fmt::Debug for Client {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        f.debug_struct("Client")
54            .field("dsn", &self.dsn())
55            .field("options", &self.options)
56            .finish()
57    }
58}
59
60impl Clone for Client {
61    fn clone(&self) -> Client {
62        let transport = Arc::new(RwLock::new(self.transport.read().unwrap().clone()));
63        let session_flusher = SessionFlusher::new(transport.clone());
64        Client {
65            options: self.options.clone(),
66            transport,
67            session_flusher,
68            integrations: self.integrations.clone(),
69            sdk_info: self.sdk_info.clone(),
70        }
71    }
72}
73
74impl Client {
75    /// Creates a new Sentry client from a config.
76    ///
77    /// # Supported Configs
78    ///
79    /// The following common values are supported for the client config:
80    ///
81    /// * `ClientOptions`: configure the client with the given client options.
82    /// * `()` or empty string: Disable the client.
83    /// * `&str` / `String` / `&OsStr` / `String`: configure the client with the given DSN.
84    /// * `Dsn` / `&Dsn`: configure the client with a given DSN.
85    /// * `(Dsn, ClientOptions)`: configure the client from the given DSN and optional options.
86    ///
87    /// The `Default` implementation of `ClientOptions` pulls in the DSN from the
88    /// `SENTRY_DSN` environment variable.
89    ///
90    /// # Panics
91    ///
92    /// The `Into<ClientOptions>` implementations can panic for the forms where a DSN needs to be
93    /// parsed.  If you want to handle invalid DSNs you need to parse them manually by calling
94    /// parse on it and handle the error.
95    pub fn from_config<O: Into<ClientOptions>>(opts: O) -> Client {
96        Client::with_options(opts.into())
97    }
98
99    /// Creates a new sentry client for the given options.
100    ///
101    /// If the DSN on the options is set to `None` the client will be entirely
102    /// disabled.
103    pub fn with_options(mut options: ClientOptions) -> Client {
104        // Create the main hub eagerly to avoid problems with the background thread
105        // See https://github.com/getsentry/sentry-rust/issues/237
106        Hub::with(|_| {});
107
108        let create_transport = || {
109            options.dsn.as_ref()?;
110            let factory = options.transport.as_ref()?;
111            Some(factory.create_transport(&options))
112        };
113
114        let transport = Arc::new(RwLock::new(create_transport()));
115
116        let mut sdk_info = SDK_INFO.clone();
117
118        // NOTE: We do not filter out duplicate integrations based on their
119        // TypeId.
120        let integrations: Vec<_> = options
121            .integrations
122            .iter()
123            .map(|integration| (integration.as_ref().type_id(), integration.clone()))
124            .collect();
125
126        for (_, integration) in integrations.iter() {
127            integration.setup(&mut options);
128            sdk_info.integrations.push(integration.name().to_string());
129        }
130
131        let session_flusher = SessionFlusher::new(transport.clone());
132        Client {
133            options,
134            transport,
135            session_flusher,
136            integrations,
137            sdk_info,
138        }
139    }
140
141    pub(crate) fn get_integration<I>(&self) -> Option<&I>
142    where
143        I: Integration,
144    {
145        let id = TypeId::of::<I>();
146        let integration = &self.integrations.iter().find(|(iid, _)| *iid == id)?.1;
147        integration.as_ref().as_any().downcast_ref()
148    }
149
150    fn prepare_event(
151        &self,
152        mut event: Event<'static>,
153        scope: Option<&Scope>,
154    ) -> Option<Event<'static>> {
155        if let Some(scope) = scope {
156            scope.update_session_from_event(&event);
157        }
158
159        if !self.sample_should_send() {
160            return None;
161        }
162
163        // event_id and sdk_info are set before the processors run so that the
164        // processors can poke around in that data.
165        if event.event_id.is_nil() {
166            event.event_id = Uuid::new_v4();
167        }
168
169        if event.sdk.is_none() {
170            // NOTE: we need to clone here because `Event` must be `'static`
171            event.sdk = Some(Cow::Owned(self.sdk_info.clone()));
172        }
173
174        if let Some(scope) = scope {
175            event = match scope.apply_to_event(event) {
176                Some(event) => event,
177                None => return None,
178            };
179        }
180
181        for (_, integration) in self.integrations.iter() {
182            let id = event.event_id;
183            event = match integration.process_event(event, &self.options) {
184                Some(event) => event,
185                None => {
186                    sentry_debug!("integration dropped event {:?}", id);
187                    return None;
188                }
189            }
190        }
191
192        if event.release.is_none() {
193            event.release = self.options.release.clone();
194        }
195        if event.environment.is_none() {
196            event.environment = self.options.environment.clone();
197        }
198        if event.server_name.is_none() {
199            event.server_name = self.options.server_name.clone();
200        }
201        if &event.platform == "other" {
202            event.platform = "native".into();
203        }
204
205        if let Some(ref func) = self.options.before_send {
206            sentry_debug!("invoking before_send callback");
207            let id = event.event_id;
208            func(event).or_else(move || {
209                sentry_debug!("before_send dropped event {:?}", id);
210                None
211            })
212        } else {
213            Some(event)
214        }
215    }
216
217    /// Returns the options of this client.
218    pub fn options(&self) -> &ClientOptions {
219        &self.options
220    }
221
222    /// Returns the DSN that constructed this client.
223    pub fn dsn(&self) -> Option<&Dsn> {
224        self.options.dsn.as_ref()
225    }
226
227    /// Quick check to see if the client is enabled.
228    ///
229    /// The Client is enabled if it has a valid DSN and Transport configured.
230    ///
231    /// # Examples
232    ///
233    /// ```
234    /// use std::sync::Arc;
235    ///
236    /// let client = sentry::Client::from(sentry::ClientOptions::default());
237    /// assert!(!client.is_enabled());
238    ///
239    /// let dsn = "https://public@example.com/1";
240    /// let transport = sentry::test::TestTransport::new();
241    /// let client = sentry::Client::from((
242    ///     dsn,
243    ///     sentry::ClientOptions {
244    ///         transport: Some(Arc::new(transport)),
245    ///         ..Default::default()
246    ///     },
247    /// ));
248    /// assert!(client.is_enabled());
249    /// ```
250    pub fn is_enabled(&self) -> bool {
251        self.options.dsn.is_some() && self.transport.read().unwrap().is_some()
252    }
253
254    /// Captures an event and sends it to sentry.
255    pub fn capture_event(&self, event: Event<'static>, scope: Option<&Scope>) -> Uuid {
256        if let Some(ref transport) = *self.transport.read().unwrap() {
257            if let Some(event) = self.prepare_event(event, scope) {
258                let event_id = event.event_id;
259                let mut envelope: Envelope = event.into();
260                let session_item = scope.and_then(|scope| {
261                    scope
262                        .session
263                        .lock()
264                        .unwrap()
265                        .as_mut()
266                        .and_then(|session| session.create_envelope_item())
267                });
268                if let Some(session_item) = session_item {
269                    envelope.add_item(session_item);
270                }
271                transport.send_envelope(envelope);
272                return event_id;
273            }
274        }
275        Default::default()
276    }
277
278    pub(crate) fn enqueue_session(&self, session_update: SessionUpdate<'static>) {
279        self.session_flusher.enqueue(session_update)
280    }
281
282    /// Drains all pending events and shuts down the transport behind the
283    /// client.  After shutting down the transport is removed.
284    ///
285    /// This returns `true` if the queue was successfully drained in the
286    /// given time or `false` if not (for instance because of a timeout).
287    /// If no timeout is provided the client will wait for as long a
288    /// `shutdown_timeout` in the client options.
289    pub fn close(&self, timeout: Option<Duration>) -> bool {
290        let transport_opt = self.transport.write().unwrap().take();
291        if let Some(transport) = transport_opt {
292            sentry_debug!("client close; request transport to shut down");
293            transport.shutdown(timeout.unwrap_or(self.options.shutdown_timeout))
294        } else {
295            sentry_debug!("client close; no transport to shut down");
296            true
297        }
298    }
299
300    fn sample_should_send(&self) -> bool {
301        let rate = self.options.sample_rate;
302        if rate >= 1.0 {
303            true
304        } else {
305            random::<f32>() <= rate
306        }
307    }
308}
309
310// Make this unwind safe. It's not out of the box because of the
311// `BeforeCallback`s inside `ClientOptions`, and the contained Integrations
312impl RefUnwindSafe for Client {}