Skip to main content

sentry_core/scope/
real.rs

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