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