ckb_sentry_core/scope/
real.rs

1use std::borrow::Cow;
2use std::collections::{HashMap, VecDeque};
3use std::fmt;
4use std::sync::{Arc, Mutex, PoisonError, RwLock};
5
6use crate::protocol::{Breadcrumb, Context, Event, Level, User, Value};
7use crate::session::Session;
8use crate::Client;
9
10#[derive(Debug)]
11pub struct Stack {
12    layers: Vec<StackLayer>,
13}
14
15pub type EventProcessor = Box<dyn Fn(Event<'static>) -> Option<Event<'static>> + Send + Sync>;
16
17/// Holds contextual data for the current scope.
18///
19/// The scope is an object that can be cloned efficiently and stores data that
20/// is locally relevant to an event.  For instance the scope will hold recorded
21/// breadcrumbs and similar information.
22///
23/// The scope can be interacted with in two ways:
24///
25/// 1. the scope is routinely updated with information by functions such as
26///    [`add_breadcrumb`] which will modify the currently top-most scope.
27/// 2. the topmost scope can also be configured through the [`configure_scope`]
28///    method.
29///
30/// Note that the scope can only be modified but not inspected.  Only the
31/// client can use the scope to extract information currently.
32///
33/// [`add_breadcrumb`]: fn.add_breadcrumb.html
34/// [`configure_scope`]: fn.configure_scope.html
35#[derive(Clone)]
36pub struct Scope {
37    pub(crate) level: Option<Level>,
38    pub(crate) fingerprint: Option<Arc<Vec<Cow<'static, str>>>>,
39    pub(crate) transaction: Option<Arc<String>>,
40    pub(crate) breadcrumbs: VecDeque<Breadcrumb>,
41    pub(crate) user: Option<Arc<User>>,
42    pub(crate) extra: HashMap<String, Value>,
43    pub(crate) tags: HashMap<String, String>,
44    pub(crate) contexts: HashMap<String, Context>,
45    pub(crate) event_processors: VecDeque<Arc<EventProcessor>>,
46    pub(crate) session: Arc<Mutex<Option<Session>>>,
47}
48
49impl fmt::Debug for Scope {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.debug_struct("Scope")
52            .field("level", &self.level)
53            .field("fingerprint", &self.fingerprint)
54            .field("transaction", &self.transaction)
55            .field("breadcrumbs", &self.breadcrumbs)
56            .field("user", &self.user)
57            .field("extra", &self.extra)
58            .field("tags", &self.tags)
59            .field("contexts", &self.contexts)
60            .field("event_processors", &self.event_processors.len())
61            .field("session", &self.session)
62            .finish()
63    }
64}
65
66impl Default for Scope {
67    fn default() -> Scope {
68        Scope {
69            level: None,
70            fingerprint: None,
71            transaction: None,
72            breadcrumbs: Default::default(),
73            user: None,
74            extra: Default::default(),
75            tags: Default::default(),
76            contexts: Default::default(),
77            event_processors: Default::default(),
78            session: Default::default(),
79        }
80    }
81}
82
83#[derive(Debug, Clone)]
84pub struct StackLayer {
85    pub client: Option<Arc<Client>>,
86    pub scope: Arc<Scope>,
87}
88
89impl Stack {
90    pub fn from_client_and_scope(client: Option<Arc<Client>>, scope: Arc<Scope>) -> Stack {
91        Stack {
92            layers: vec![StackLayer { client, scope }],
93        }
94    }
95
96    pub fn push(&mut self) {
97        let layer = self.layers[self.layers.len() - 1].clone();
98        self.layers.push(layer);
99    }
100
101    pub fn pop(&mut self) {
102        if self.layers.len() <= 1 {
103            panic!("Pop from empty stack");
104        }
105        self.layers.pop().unwrap();
106    }
107
108    pub fn top(&self) -> &StackLayer {
109        &self.layers[self.layers.len() - 1]
110    }
111
112    pub fn top_mut(&mut self) -> &mut StackLayer {
113        let top = self.layers.len() - 1;
114        &mut self.layers[top]
115    }
116
117    pub fn depth(&self) -> usize {
118        self.layers.len()
119    }
120}
121
122/// A scope guard.
123///
124/// This is returned from [`Hub::push_scope`] and will automatically pop the
125/// scope on drop.
126///
127/// [`Hub::push_scope`]: struct.Hub.html#method.with_scope
128#[derive(Default)]
129pub struct ScopeGuard(pub(crate) Option<(Arc<RwLock<Stack>>, usize)>);
130
131impl fmt::Debug for ScopeGuard {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(f, "ScopeGuard")
134    }
135}
136
137impl Drop for ScopeGuard {
138    fn drop(&mut self) {
139        if let Some((stack, depth)) = self.0.take() {
140            let mut stack = stack.write().unwrap_or_else(PoisonError::into_inner);
141            if stack.depth() != depth {
142                panic!("Tried to pop guards out of order");
143            }
144            stack.pop();
145        }
146    }
147}
148
149impl Scope {
150    /// Clear the scope.
151    ///
152    /// By default a scope will inherit all values from the higher scope.
153    /// In some situations this might not be what a user wants.  Calling
154    /// this method will wipe all data contained within.
155    pub fn clear(&mut self) {
156        *self = Default::default();
157    }
158
159    /// Deletes current breadcrumbs from the scope.
160    pub fn clear_breadcrumbs(&mut self) {
161        self.breadcrumbs.clear();
162    }
163
164    /// Sets a level override.
165    pub fn set_level(&mut self, level: Option<Level>) {
166        self.level = level;
167    }
168
169    /// Sets the fingerprint.
170    pub fn set_fingerprint(&mut self, fingerprint: Option<&[&str]>) {
171        self.fingerprint = fingerprint
172            .map(|fp| Arc::new(fp.iter().map(|x| Cow::Owned((*x).to_string())).collect()))
173    }
174
175    /// Sets the transaction.
176    pub fn set_transaction(&mut self, transaction: Option<&str>) {
177        self.transaction = transaction.map(|txn| Arc::new(txn.to_string()));
178    }
179
180    /// Sets the user for the current scope.
181    pub fn set_user(&mut self, user: Option<User>) {
182        self.user = user.map(Arc::new);
183    }
184
185    /// Sets a tag to a specific value.
186    #[allow(clippy::needless_pass_by_value)]
187    pub fn set_tag<V: ToString>(&mut self, key: &str, value: V) {
188        self.tags.insert(key.to_string(), value.to_string());
189    }
190
191    /// Removes a tag.
192    pub fn remove_tag(&mut self, key: &str) {
193        self.tags.remove(key);
194    }
195
196    /// Sets a context for a key.
197    pub fn set_context<C: Into<Context>>(&mut self, key: &str, value: C) {
198        self.contexts.insert(key.to_string(), value.into());
199    }
200
201    /// Removes a context for a key.
202    pub fn remove_context(&mut self, key: &str) {
203        self.contexts.remove(key);
204    }
205
206    /// Sets a extra to a specific value.
207    pub fn set_extra(&mut self, key: &str, value: Value) {
208        self.extra.insert(key.to_string(), value);
209    }
210
211    /// Removes a extra.
212    pub fn remove_extra(&mut self, key: &str) {
213        self.extra.remove(key);
214    }
215
216    /// Add an event processor to the scope.
217    pub fn add_event_processor(
218        &mut self,
219        f: Box<dyn Fn(Event<'static>) -> Option<Event<'static>> + Send + Sync>,
220    ) {
221        self.event_processors.push_back(Arc::new(f));
222    }
223
224    /// Applies the contained scoped data to fill an event.
225    #[allow(clippy::cognitive_complexity)]
226    pub fn apply_to_event(&self, mut event: Event<'static>) -> Option<Event<'static>> {
227        // TODO: event really should have an optional level
228        if self.level.is_some() {
229            event.level = self.level.unwrap();
230        }
231
232        if event.user.is_none() {
233            if let Some(ref user) = self.user {
234                event.user = Some((**user).clone());
235            }
236        }
237
238        event.breadcrumbs.extend(self.breadcrumbs.clone());
239        event.extra.extend(self.extra.clone());
240        event.tags.extend(self.tags.clone());
241        event.contexts.extend(self.contexts.clone());
242
243        if event.transaction.is_none() {
244            if let Some(ref txn) = self.transaction {
245                event.transaction = Some((**txn).clone());
246            }
247        }
248
249        if event.fingerprint.len() == 1
250            && (event.fingerprint[0] == "{{ default }}" || event.fingerprint[0] == "{{default}}")
251        {
252            if let Some(ref fp) = self.fingerprint {
253                event.fingerprint = Cow::Owned((**fp).clone());
254            }
255        }
256
257        for processor in &self.event_processors {
258            let id = event.event_id;
259            event = match processor(event) {
260                Some(event) => event,
261                None => {
262                    sentry_debug!("event processor dropped event {}", id);
263                    return None;
264                }
265            }
266        }
267
268        Some(event)
269    }
270
271    pub(crate) fn update_session_from_event(&self, event: &Event<'static>) {
272        if let Some(session) = self.session.lock().unwrap().as_mut() {
273            session.update_from_event(event);
274        }
275    }
276}