sentry_core/scope/
real.rs

1use std::borrow::Cow;
2use std::collections::{HashMap, VecDeque};
3use std::fmt;
4#[cfg(feature = "release-health")]
5use std::sync::Mutex;
6use std::sync::{Arc, PoisonError, RwLock};
7
8use crate::performance::TransactionOrSpan;
9use crate::protocol::{
10    Attachment, Breadcrumb, Context, Event, Level, TraceContext, Transaction, User, Value,
11};
12#[cfg(feature = "logs")]
13use crate::protocol::{Log, LogAttribute};
14#[cfg(feature = "release-health")]
15use crate::session::Session;
16use crate::{Client, SentryTrace, TraceHeader, TraceHeadersIter};
17
18#[derive(Debug)]
19pub struct Stack {
20    top: StackLayer,
21    layers: Vec<StackLayer>,
22}
23
24pub type EventProcessor = Arc<dyn Fn(Event<'static>) -> Option<Event<'static>> + Send + Sync>;
25
26/// Holds contextual data for the current scope.
27///
28/// The scope is an object that can be cloned efficiently and stores data that
29/// is locally relevant to an event.  For instance the scope will hold recorded
30/// breadcrumbs and similar information.
31///
32/// The scope can be interacted with in two ways:
33///
34/// 1. the scope is routinely updated with information by functions such as
35///    [`add_breadcrumb`] which will modify the currently top-most scope.
36/// 2. the topmost scope can also be configured through the [`configure_scope`]
37///    method.
38///
39/// Note that the scope can only be modified but not inspected.  Only the
40/// client can use the scope to extract information currently.
41///
42/// [`add_breadcrumb`]: fn.add_breadcrumb.html
43/// [`configure_scope`]: fn.configure_scope.html
44#[derive(Clone, Default)]
45pub struct Scope {
46    pub(crate) level: Option<Level>,
47    pub(crate) fingerprint: Option<Arc<[Cow<'static, str>]>>,
48    pub(crate) transaction: Option<Arc<str>>,
49    pub(crate) breadcrumbs: Arc<VecDeque<Breadcrumb>>,
50    pub(crate) user: Option<Arc<User>>,
51    pub(crate) extra: Arc<HashMap<String, Value>>,
52    pub(crate) tags: Arc<HashMap<String, String>>,
53    pub(crate) contexts: Arc<HashMap<String, Context>>,
54    pub(crate) event_processors: Arc<Vec<EventProcessor>>,
55    #[cfg(feature = "release-health")]
56    pub(crate) session: Arc<Mutex<Option<Session>>>,
57    pub(crate) span: Arc<Option<TransactionOrSpan>>,
58    pub(crate) attachments: Arc<Vec<Attachment>>,
59    pub(crate) propagation_context: SentryTrace,
60}
61
62impl fmt::Debug for Scope {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        let mut debug_struct = f.debug_struct("Scope");
65        debug_struct
66            .field("level", &self.level)
67            .field("fingerprint", &self.fingerprint)
68            .field("transaction", &self.transaction)
69            .field("breadcrumbs", &self.breadcrumbs)
70            .field("user", &self.user)
71            .field("extra", &self.extra)
72            .field("tags", &self.tags)
73            .field("contexts", &self.contexts)
74            .field("event_processors", &self.event_processors.len());
75
76        #[cfg(feature = "release-health")]
77        debug_struct.field("session", &self.session);
78
79        debug_struct
80            .field("span", &self.span)
81            .field("attachments", &self.attachments.len())
82            .field("propagation_context", &self.propagation_context)
83            .finish()
84    }
85}
86
87#[derive(Debug, Clone)]
88pub struct StackLayer {
89    pub client: Option<Arc<Client>>,
90    pub scope: Arc<Scope>,
91}
92
93impl Stack {
94    pub fn from_client_and_scope(client: Option<Arc<Client>>, scope: Arc<Scope>) -> Stack {
95        Stack {
96            top: StackLayer { client, scope },
97            layers: vec![],
98        }
99    }
100
101    pub fn push(&mut self) {
102        let layer = self.top.clone();
103        self.layers.push(layer);
104    }
105
106    pub fn pop(&mut self) {
107        if self.layers.is_empty() {
108            panic!("Pop from empty stack");
109        }
110        self.top = self.layers.pop().unwrap();
111    }
112
113    #[inline(always)]
114    pub fn top(&self) -> &StackLayer {
115        &self.top
116    }
117
118    #[inline(always)]
119    pub fn top_mut(&mut self) -> &mut StackLayer {
120        &mut self.top
121    }
122
123    pub fn depth(&self) -> usize {
124        self.layers.len()
125    }
126}
127
128/// A scope guard.
129///
130/// This is returned from [`Hub::push_scope`] and will automatically pop the
131/// scope on drop.
132///
133/// [`Hub::push_scope`]: struct.Hub.html#method.with_scope
134#[derive(Default)]
135pub struct ScopeGuard(pub(crate) Option<(Arc<RwLock<Stack>>, usize)>);
136
137impl fmt::Debug for ScopeGuard {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        write!(f, "ScopeGuard")
140    }
141}
142
143impl Drop for ScopeGuard {
144    fn drop(&mut self) {
145        if let Some((stack, depth)) = self.0.take() {
146            let popped_depth = {
147                let mut stack = stack.write().unwrap_or_else(PoisonError::into_inner);
148                let popped_depth = stack.depth();
149                stack.pop();
150                popped_depth
151            };
152            // NOTE: We need to drop the `stack` lock before panicking, as the
153            // `PanicIntegration` will want to lock the `stack` itself
154            // (through `capture_event` -> `HubImpl::with`), and would thus
155            // result in a deadlock.
156            // Though that deadlock itself is detected by the `RwLock` (on macOS)
157            // and results in its own panic: `rwlock read lock would result in deadlock`.
158            // However that panic happens in the panic handler and will thus
159            // ultimately result in a `thread panicked while processing panic. aborting.`
160            // Long story short, we should not panic while holding the lock :-)
161            if popped_depth != depth {
162                panic!("Popped scope guard out of order");
163            }
164        }
165    }
166}
167
168impl Scope {
169    /// Clear the scope.
170    ///
171    /// By default a scope will inherit all values from the higher scope.
172    /// In some situations this might not be what a user wants.  Calling
173    /// this method will wipe all data contained within.
174    pub fn clear(&mut self) {
175        *self = Default::default();
176    }
177
178    /// Deletes current breadcrumbs from the scope.
179    pub fn clear_breadcrumbs(&mut self) {
180        self.breadcrumbs = Default::default();
181    }
182
183    /// Sets a level override.
184    pub fn set_level(&mut self, level: Option<Level>) {
185        self.level = level;
186    }
187
188    /// Sets the fingerprint.
189    pub fn set_fingerprint(&mut self, fingerprint: Option<&[&str]>) {
190        self.fingerprint =
191            fingerprint.map(|fp| fp.iter().map(|s| Cow::Owned((*s).into())).collect())
192    }
193
194    /// Sets the transaction.
195    pub fn set_transaction(&mut self, transaction: Option<&str>) {
196        self.transaction = transaction.map(Arc::from);
197        if let Some(name) = transaction {
198            let trx = match self.span.as_ref() {
199                Some(TransactionOrSpan::Span(span)) => &span.transaction,
200                Some(TransactionOrSpan::Transaction(trx)) => &trx.inner,
201                _ => return,
202            };
203
204            if let Some(trx) = trx.lock().unwrap().transaction.as_mut() {
205                trx.name = Some(name.into());
206            }
207        }
208    }
209
210    /// Sets the user for the current scope.
211    pub fn set_user(&mut self, user: Option<User>) {
212        self.user = user.map(Arc::new);
213    }
214
215    /// Retrieves the user of the current scope.
216    pub fn user(&self) -> Option<&User> {
217        self.user.as_deref()
218    }
219
220    /// Sets a tag to a specific value.
221    pub fn set_tag<V: ToString>(&mut self, key: &str, value: V) {
222        Arc::make_mut(&mut self.tags).insert(key.to_string(), value.to_string());
223    }
224
225    /// Removes a tag.
226    ///
227    /// If the tag is not set, does nothing.
228    pub fn remove_tag(&mut self, key: &str) {
229        Arc::make_mut(&mut self.tags).remove(key);
230    }
231
232    /// Sets a context for a key.
233    pub fn set_context<C: Into<Context>>(&mut self, key: &str, value: C) {
234        Arc::make_mut(&mut self.contexts).insert(key.to_string(), value.into());
235    }
236
237    /// Removes a context for a key.
238    pub fn remove_context(&mut self, key: &str) {
239        Arc::make_mut(&mut self.contexts).remove(key);
240    }
241
242    /// Sets a extra to a specific value.
243    pub fn set_extra(&mut self, key: &str, value: Value) {
244        Arc::make_mut(&mut self.extra).insert(key.to_string(), value);
245    }
246
247    /// Removes a extra.
248    pub fn remove_extra(&mut self, key: &str) {
249        Arc::make_mut(&mut self.extra).remove(key);
250    }
251
252    /// Add an event processor to the scope.
253    pub fn add_event_processor<F>(&mut self, f: F)
254    where
255        F: Fn(Event<'static>) -> Option<Event<'static>> + Send + Sync + 'static,
256    {
257        Arc::make_mut(&mut self.event_processors).push(Arc::new(f));
258    }
259
260    /// Adds an attachment to the scope
261    pub fn add_attachment(&mut self, attachment: Attachment) {
262        Arc::make_mut(&mut self.attachments).push(attachment);
263    }
264
265    /// Clears attachments from the scope
266    pub fn clear_attachments(&mut self) {
267        Arc::make_mut(&mut self.attachments).clear();
268    }
269
270    /// Applies the contained scoped data to fill an event.
271    pub fn apply_to_event(&self, mut event: Event<'static>) -> Option<Event<'static>> {
272        // TODO: event really should have an optional level
273        if self.level.is_some() {
274            event.level = self.level.unwrap();
275        }
276
277        if event.user.is_none() {
278            if let Some(user) = self.user.as_deref() {
279                event.user = Some(user.clone());
280            }
281        }
282
283        event.breadcrumbs.extend(self.breadcrumbs.iter().cloned());
284        event
285            .extra
286            .extend(self.extra.iter().map(|(k, v)| (k.to_owned(), v.to_owned())));
287        event
288            .tags
289            .extend(self.tags.iter().map(|(k, v)| (k.to_owned(), v.to_owned())));
290        event.contexts.extend(
291            self.contexts
292                .iter()
293                .map(|(k, v)| (k.to_owned(), v.to_owned())),
294        );
295
296        if let Some(span) = self.span.as_ref() {
297            span.apply_to_event(&mut event);
298        } else {
299            self.apply_propagation_context(&mut event);
300        }
301
302        if event.transaction.is_none() {
303            if let Some(txn) = self.transaction.as_deref() {
304                event.transaction = Some(txn.to_owned());
305            }
306        }
307
308        if event.fingerprint.len() == 1
309            && (event.fingerprint[0] == "{{ default }}" || event.fingerprint[0] == "{{default}}")
310        {
311            if let Some(fp) = self.fingerprint.as_deref() {
312                event.fingerprint = Cow::Owned(fp.to_owned());
313            }
314        }
315
316        for processor in self.event_processors.as_ref() {
317            let id = event.event_id;
318            event = match processor(event) {
319                Some(event) => event,
320                None => {
321                    sentry_debug!("event processor dropped event {}", id);
322                    return None;
323                }
324            }
325        }
326
327        Some(event)
328    }
329
330    /// Applies the contained scoped data to fill a transaction.
331    pub fn apply_to_transaction(&self, transaction: &mut Transaction<'static>) {
332        if transaction.user.is_none() {
333            if let Some(user) = self.user.as_deref() {
334                transaction.user = Some(user.clone());
335            }
336        }
337
338        transaction
339            .extra
340            .extend(self.extra.iter().map(|(k, v)| (k.to_owned(), v.to_owned())));
341        transaction
342            .tags
343            .extend(self.tags.iter().map(|(k, v)| (k.to_owned(), v.to_owned())));
344        transaction.contexts.extend(
345            self.contexts
346                .iter()
347                .map(|(k, v)| (k.to_owned(), v.to_owned())),
348        );
349    }
350
351    /// Applies the contained scoped data to a log, setting the `trace_id` and certain default
352    /// attributes.
353    #[cfg(feature = "logs")]
354    pub fn apply_to_log(&self, log: &mut Log) {
355        if let Some(span) = self.span.as_ref() {
356            log.trace_id = Some(span.get_trace_context().trace_id);
357        } else {
358            log.trace_id = Some(self.propagation_context.trace_id);
359        }
360
361        if !log.attributes.contains_key("sentry.trace.parent_span_id") {
362            if let Some(span) = self.get_span() {
363                let span_id = match span {
364                    crate::TransactionOrSpan::Transaction(transaction) => {
365                        transaction.get_trace_context().span_id
366                    }
367                    crate::TransactionOrSpan::Span(span) => span.get_span_id(),
368                };
369                log.attributes.insert(
370                    "parent_span_id".to_owned(),
371                    LogAttribute(span_id.to_string().into()),
372                );
373            }
374        }
375
376        if let Some(user) = self.user.as_ref() {
377            if !log.attributes.contains_key("user.id") {
378                if let Some(id) = user.id.as_ref() {
379                    log.attributes
380                        .insert("user.id".to_owned(), LogAttribute(id.to_owned().into()));
381                }
382            }
383
384            if !log.attributes.contains_key("user.name") {
385                if let Some(name) = user.username.as_ref() {
386                    log.attributes
387                        .insert("user.name".to_owned(), LogAttribute(name.to_owned().into()));
388                }
389            }
390
391            if !log.attributes.contains_key("user.email") {
392                if let Some(email) = user.email.as_ref() {
393                    log.attributes.insert(
394                        "user.email".to_owned(),
395                        LogAttribute(email.to_owned().into()),
396                    );
397                }
398            }
399        }
400    }
401
402    /// Set the given [`TransactionOrSpan`] as the active span for this scope.
403    pub fn set_span(&mut self, span: Option<TransactionOrSpan>) {
404        self.span = Arc::new(span);
405    }
406
407    /// Returns the currently active span.
408    pub fn get_span(&self) -> Option<TransactionOrSpan> {
409        self.span.as_ref().clone()
410    }
411
412    #[allow(unused_variables)]
413    pub(crate) fn update_session_from_event(&self, event: &Event<'static>) {
414        #[cfg(feature = "release-health")]
415        if let Some(session) = self.session.lock().unwrap().as_mut() {
416            session.update_from_event(event);
417        }
418    }
419
420    pub(crate) fn apply_propagation_context(&self, event: &mut Event<'_>) {
421        if event.contexts.contains_key("trace") {
422            return;
423        }
424
425        let context = TraceContext {
426            trace_id: self.propagation_context.trace_id,
427            span_id: self.propagation_context.span_id,
428            ..Default::default()
429        };
430        event.contexts.insert("trace".into(), context.into());
431    }
432
433    /// Returns the headers needed for distributed tracing.
434    pub fn iter_trace_propagation_headers(&self) -> impl Iterator<Item = TraceHeader> {
435        if let Some(span) = self.get_span() {
436            span.iter_headers()
437        } else {
438            let data = SentryTrace::new(
439                self.propagation_context.trace_id,
440                self.propagation_context.span_id,
441                None,
442            );
443            TraceHeadersIter::new(data.to_string())
444        }
445    }
446}