sentry_core/
client.rs
1use std::any::TypeId;
2use std::borrow::Cow;
3use std::fmt;
4use std::panic::RefUnwindSafe;
5use std::sync::{Arc, RwLock};
6use std::time::Duration;
7
8use rand::random;
9#[cfg(feature = "release-health")]
10use sentry_types::protocol::v7::SessionUpdate;
11use sentry_types::random_uuid;
12
13use crate::constants::SDK_INFO;
14use crate::protocol::{ClientSdkInfo, Event};
15#[cfg(feature = "release-health")]
16use crate::session::SessionFlusher;
17use crate::types::{Dsn, Uuid};
18#[cfg(feature = "release-health")]
19use crate::SessionMode;
20use crate::{ClientOptions, Envelope, Hub, Integration, Scope, Transport};
21
22impl<T: Into<ClientOptions>> From<T> for Client {
23 fn from(o: T) -> Client {
24 Client::with_options(o.into())
25 }
26}
27
28pub(crate) type TransportArc = Arc<RwLock<Option<Arc<dyn Transport>>>>;
29
30pub struct Client {
48 options: ClientOptions,
49 transport: TransportArc,
50 #[cfg(feature = "release-health")]
51 session_flusher: RwLock<Option<SessionFlusher>>,
52 integrations: Vec<(TypeId, Arc<dyn Integration>)>,
53 pub(crate) sdk_info: ClientSdkInfo,
54}
55
56impl fmt::Debug for Client {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 f.debug_struct("Client")
59 .field("dsn", &self.dsn())
60 .field("options", &self.options)
61 .finish()
62 }
63}
64
65impl Clone for Client {
66 fn clone(&self) -> Client {
67 let transport = Arc::new(RwLock::new(self.transport.read().unwrap().clone()));
68
69 #[cfg(feature = "release-health")]
70 let session_flusher = RwLock::new(Some(SessionFlusher::new(
71 transport.clone(),
72 self.options.session_mode,
73 )));
74
75 Client {
76 options: self.options.clone(),
77 transport,
78 #[cfg(feature = "release-health")]
79 session_flusher,
80 integrations: self.integrations.clone(),
81 sdk_info: self.sdk_info.clone(),
82 }
83 }
84}
85
86impl Client {
87 pub fn from_config<O: Into<ClientOptions>>(opts: O) -> Client {
108 Client::with_options(opts.into())
109 }
110
111 pub fn with_options(mut options: ClientOptions) -> Client {
116 Hub::with(|_| {});
119
120 let create_transport = || {
121 options.dsn.as_ref()?;
122 let factory = options.transport.as_ref()?;
123 Some(factory.create_transport(&options))
124 };
125
126 let transport = Arc::new(RwLock::new(create_transport()));
127
128 let mut sdk_info = SDK_INFO.clone();
129
130 let integrations: Vec<_> = options
133 .integrations
134 .iter()
135 .map(|integration| (integration.as_ref().type_id(), integration.clone()))
136 .collect();
137
138 for (_, integration) in integrations.iter() {
139 integration.setup(&mut options);
140 sdk_info.integrations.push(integration.name().to_string());
141 }
142
143 #[cfg(feature = "release-health")]
144 let session_flusher = RwLock::new(Some(SessionFlusher::new(
145 transport.clone(),
146 options.session_mode,
147 )));
148
149 Client {
150 options,
151 transport,
152 #[cfg(feature = "release-health")]
153 session_flusher,
154 integrations,
155 sdk_info,
156 }
157 }
158
159 pub(crate) fn get_integration<I>(&self) -> Option<&I>
160 where
161 I: Integration,
162 {
163 let id = TypeId::of::<I>();
164 let integration = &self.integrations.iter().find(|(iid, _)| *iid == id)?.1;
165 integration.as_ref().as_any().downcast_ref()
166 }
167
168 pub fn prepare_event(
170 &self,
171 mut event: Event<'static>,
172 scope: Option<&Scope>,
173 ) -> Option<Event<'static>> {
174 if event.event_id.is_nil() {
177 event.event_id = random_uuid();
178 }
179
180 if event.sdk.is_none() {
181 event.sdk = Some(Cow::Owned(self.sdk_info.clone()));
183 }
184
185 if let Some(scope) = scope {
186 event = scope.apply_to_event(event)?;
187 }
188
189 for (_, integration) in self.integrations.iter() {
190 let id = event.event_id;
191 event = match integration.process_event(event, &self.options) {
192 Some(event) => event,
193 None => {
194 sentry_debug!("integration dropped event {:?}", id);
195 return None;
196 }
197 }
198 }
199
200 if event.release.is_none() {
201 event.release.clone_from(&self.options.release);
202 }
203 if event.environment.is_none() {
204 event.environment.clone_from(&self.options.environment);
205 }
206 if event.server_name.is_none() {
207 event.server_name.clone_from(&self.options.server_name);
208 }
209 if &event.platform == "other" {
210 event.platform = "native".into();
211 }
212
213 if let Some(ref func) = self.options.before_send {
214 sentry_debug!("invoking before_send callback");
215 let id = event.event_id;
216 if let Some(processed_event) = func(event) {
217 event = processed_event;
218 } else {
219 sentry_debug!("before_send dropped event {:?}", id);
220 return None;
221 }
222 }
223
224 if let Some(scope) = scope {
225 scope.update_session_from_event(&event);
226 }
227
228 if !self.sample_should_send(self.options.sample_rate) {
229 None
230 } else {
231 Some(event)
232 }
233 }
234
235 pub fn options(&self) -> &ClientOptions {
237 &self.options
238 }
239
240 pub fn dsn(&self) -> Option<&Dsn> {
242 self.options.dsn.as_ref()
243 }
244
245 pub fn is_enabled(&self) -> bool {
269 self.options.dsn.is_some() && self.transport.read().unwrap().is_some()
270 }
271
272 pub fn capture_event(&self, event: Event<'static>, scope: Option<&Scope>) -> Uuid {
274 if let Some(ref transport) = *self.transport.read().unwrap() {
275 if let Some(event) = self.prepare_event(event, scope) {
276 let event_id = event.event_id;
277 let mut envelope: Envelope = event.into();
278 #[cfg(feature = "release-health")]
281 if self.options.session_mode == SessionMode::Application {
282 let session_item = scope.and_then(|scope| {
283 scope
284 .session
285 .lock()
286 .unwrap()
287 .as_mut()
288 .and_then(|session| session.create_envelope_item())
289 });
290 if let Some(session_item) = session_item {
291 envelope.add_item(session_item);
292 }
293 }
294
295 if let Some(scope) = scope {
296 for attachment in scope.attachments.iter().cloned() {
297 envelope.add_item(attachment);
298 }
299 }
300
301 transport.send_envelope(envelope);
302 return event_id;
303 }
304 }
305 Default::default()
306 }
307
308 pub fn send_envelope(&self, envelope: Envelope) {
310 if let Some(ref transport) = *self.transport.read().unwrap() {
311 transport.send_envelope(envelope);
312 }
313 }
314
315 #[cfg(feature = "release-health")]
316 pub(crate) fn enqueue_session(&self, session_update: SessionUpdate<'static>) {
317 if let Some(ref flusher) = *self.session_flusher.read().unwrap() {
318 flusher.enqueue(session_update);
319 }
320 }
321
322 pub fn flush(&self, timeout: Option<Duration>) -> bool {
324 #[cfg(feature = "release-health")]
325 if let Some(ref flusher) = *self.session_flusher.read().unwrap() {
326 flusher.flush();
327 }
328 if let Some(ref transport) = *self.transport.read().unwrap() {
329 transport.flush(timeout.unwrap_or(self.options.shutdown_timeout))
330 } else {
331 true
332 }
333 }
334
335 pub fn close(&self, timeout: Option<Duration>) -> bool {
343 #[cfg(feature = "release-health")]
344 drop(self.session_flusher.write().unwrap().take());
345 let transport_opt = self.transport.write().unwrap().take();
346 if let Some(transport) = transport_opt {
347 sentry_debug!("client close; request transport to shut down");
348 transport.shutdown(timeout.unwrap_or(self.options.shutdown_timeout))
349 } else {
350 sentry_debug!("client close; no transport to shut down");
351 true
352 }
353 }
354
355 pub fn sample_should_send(&self, rate: f32) -> bool {
358 if rate >= 1.0 {
359 true
360 } else if rate <= 0.0 {
361 false
362 } else {
363 random::<f32>() < rate
364 }
365 }
366}
367
368impl RefUnwindSafe for Client {}