ckb_sentry_core/
client.rs1use 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
26pub 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 pub fn from_config<O: Into<ClientOptions>>(opts: O) -> Client {
96 Client::with_options(opts.into())
97 }
98
99 pub fn with_options(mut options: ClientOptions) -> Client {
104 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 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 if event.event_id.is_nil() {
166 event.event_id = Uuid::new_v4();
167 }
168
169 if event.sdk.is_none() {
170 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 pub fn options(&self) -> &ClientOptions {
219 &self.options
220 }
221
222 pub fn dsn(&self) -> Option<&Dsn> {
224 self.options.dsn.as_ref()
225 }
226
227 pub fn is_enabled(&self) -> bool {
251 self.options.dsn.is_some() && self.transport.read().unwrap().is_some()
252 }
253
254 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 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
310impl RefUnwindSafe for Client {}