1use std::borrow::Cow;
2use std::cell::RefCell;
3use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use bitflags::bitflags;
7use sentry_core::protocol::Value;
8use sentry_core::{Breadcrumb, TransactionOrSpan};
9use tracing_core::field::Visit;
10use tracing_core::{span, Event, Field, Level, Metadata, Subscriber};
11use tracing_subscriber::layer::{Context, Layer};
12use tracing_subscriber::registry::LookupSpan;
13
14use crate::converters::*;
15use crate::SENTRY_NAME_FIELD;
16use crate::SENTRY_OP_FIELD;
17use crate::SENTRY_TRACE_FIELD;
18use crate::TAGS_PREFIX;
19
20bitflags! {
21    #[derive(Debug, Clone, Copy)]
23    pub struct EventFilter: u32 {
24        const Ignore = 0b000;
26        const Breadcrumb = 0b001;
28        const Event = 0b010;
30        const Log = 0b100;
32    }
33}
34
35#[derive(Debug)]
37#[allow(clippy::large_enum_variant)]
38pub enum EventMapping {
39    Ignore,
41    Breadcrumb(Breadcrumb),
43    Event(sentry_core::protocol::Event<'static>),
45    #[cfg(feature = "logs")]
47    Log(sentry_core::protocol::Log),
48    Combined(CombinedEventMapping),
51}
52
53#[derive(Debug)]
55pub struct CombinedEventMapping(Vec<EventMapping>);
56
57impl From<EventMapping> for CombinedEventMapping {
58    fn from(value: EventMapping) -> Self {
59        match value {
60            EventMapping::Combined(combined) => combined,
61            _ => CombinedEventMapping(vec![value]),
62        }
63    }
64}
65
66impl From<Vec<EventMapping>> for CombinedEventMapping {
67    fn from(value: Vec<EventMapping>) -> Self {
68        Self(value)
69    }
70}
71
72pub fn default_event_filter(metadata: &Metadata) -> EventFilter {
77    match metadata.level() {
78        #[cfg(feature = "logs")]
79        &Level::ERROR => EventFilter::Event | EventFilter::Log,
80        #[cfg(not(feature = "logs"))]
81        &Level::ERROR => EventFilter::Event,
82        #[cfg(feature = "logs")]
83        &Level::WARN | &Level::INFO => EventFilter::Breadcrumb | EventFilter::Log,
84        #[cfg(not(feature = "logs"))]
85        &Level::WARN | &Level::INFO => EventFilter::Breadcrumb,
86        &Level::DEBUG | &Level::TRACE => EventFilter::Ignore,
87    }
88}
89
90pub fn default_span_filter(metadata: &Metadata) -> bool {
95    matches!(
96        metadata.level(),
97        &Level::ERROR | &Level::WARN | &Level::INFO
98    )
99}
100
101type EventMapper<S> = Box<dyn Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync>;
102
103pub struct SentryLayer<S> {
105    event_filter: Box<dyn Fn(&Metadata) -> EventFilter + Send + Sync>,
106    event_mapper: Option<EventMapper<S>>,
107
108    span_filter: Box<dyn Fn(&Metadata) -> bool + Send + Sync>,
109
110    with_span_attributes: bool,
111}
112
113impl<S> SentryLayer<S> {
114    #[must_use]
119    pub fn event_filter<F>(mut self, filter: F) -> Self
120    where
121        F: Fn(&Metadata) -> EventFilter + Send + Sync + 'static,
122    {
123        self.event_filter = Box::new(filter);
124        self
125    }
126
127    #[must_use]
132    pub fn event_mapper<F>(mut self, mapper: F) -> Self
133    where
134        F: Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync + 'static,
135    {
136        self.event_mapper = Some(Box::new(mapper));
137        self
138    }
139
140    #[must_use]
147    pub fn span_filter<F>(mut self, filter: F) -> Self
148    where
149        F: Fn(&Metadata) -> bool + Send + Sync + 'static,
150    {
151        self.span_filter = Box::new(filter);
152        self
153    }
154
155    #[must_use]
163    pub fn enable_span_attributes(mut self) -> Self {
164        self.with_span_attributes = true;
165        self
166    }
167}
168
169impl<S> Default for SentryLayer<S>
170where
171    S: Subscriber + for<'a> LookupSpan<'a>,
172{
173    fn default() -> Self {
174        Self {
175            event_filter: Box::new(default_event_filter),
176            event_mapper: None,
177
178            span_filter: Box::new(default_span_filter),
179
180            with_span_attributes: false,
181        }
182    }
183}
184
185#[inline(always)]
186fn record_fields<'a, K: AsRef<str> + Into<Cow<'a, str>>>(
187    span: &TransactionOrSpan,
188    data: BTreeMap<K, Value>,
189) {
190    match span {
191        TransactionOrSpan::Span(span) => {
192            let mut span = span.data();
193            for (key, value) in data {
194                if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
195                    match value {
196                        Value::Bool(value) => {
197                            span.set_tag(stripped_key.to_owned(), value.to_string())
198                        }
199                        Value::Number(value) => {
200                            span.set_tag(stripped_key.to_owned(), value.to_string())
201                        }
202                        Value::String(value) => span.set_tag(stripped_key.to_owned(), value),
203                        _ => span.set_data(key.into().into_owned(), value),
204                    }
205                } else {
206                    span.set_data(key.into().into_owned(), value);
207                }
208            }
209        }
210        TransactionOrSpan::Transaction(transaction) => {
211            let mut transaction = transaction.data();
212            for (key, value) in data {
213                if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
214                    match value {
215                        Value::Bool(value) => {
216                            transaction.set_tag(stripped_key.into(), value.to_string())
217                        }
218                        Value::Number(value) => {
219                            transaction.set_tag(stripped_key.into(), value.to_string())
220                        }
221                        Value::String(value) => transaction.set_tag(stripped_key.into(), value),
222                        _ => transaction.set_data(key.into(), value),
223                    }
224                } else {
225                    transaction.set_data(key.into(), value);
226                }
227            }
228        }
229    }
230}
231
232pub(super) struct SentrySpanData {
236    pub(super) sentry_span: TransactionOrSpan,
237    parent_sentry_span: Option<TransactionOrSpan>,
238    hub: Arc<sentry_core::Hub>,
239    hub_switch_guard: Option<sentry_core::HubSwitchGuard>,
240}
241
242impl<S> Layer<S> for SentryLayer<S>
243where
244    S: Subscriber + for<'a> LookupSpan<'a>,
245{
246    fn on_event(&self, event: &Event, ctx: Context<'_, S>) {
247        let items = match &self.event_mapper {
248            Some(mapper) => mapper(event, ctx),
249            None => {
250                let span_ctx = self.with_span_attributes.then_some(ctx);
251                let filter = (self.event_filter)(event.metadata());
252                let mut items = vec![];
253                if filter.contains(EventFilter::Breadcrumb) {
254                    items.push(EventMapping::Breadcrumb(breadcrumb_from_event(
255                        event,
256                        span_ctx.as_ref(),
257                    )));
258                }
259                if filter.contains(EventFilter::Event) {
260                    items.push(EventMapping::Event(event_from_event(
261                        event,
262                        span_ctx.as_ref(),
263                    )));
264                }
265                #[cfg(feature = "logs")]
266                if filter.contains(EventFilter::Log) {
267                    items.push(EventMapping::Log(log_from_event(event, span_ctx.as_ref())));
268                }
269                EventMapping::Combined(CombinedEventMapping(items))
270            }
271        };
272        let items = CombinedEventMapping::from(items);
273
274        for item in items.0 {
275            match item {
276                EventMapping::Ignore => (),
277                EventMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
278                EventMapping::Event(event) => {
279                    sentry_core::capture_event(event);
280                }
281                #[cfg(feature = "logs")]
282                EventMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)),
283                EventMapping::Combined(_) => {
284                    sentry_core::sentry_debug!(
285                        "[SentryLayer] found nested CombinedEventMapping, ignoring"
286                    )
287                }
288            }
289        }
290    }
291
292    fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
295        let span = match ctx.span(id) {
296            Some(span) => span,
297            None => return,
298        };
299
300        if !(self.span_filter)(span.metadata()) {
301            return;
302        }
303
304        let (data, sentry_name, sentry_op, sentry_trace) = extract_span_data(attrs);
305        let sentry_name = sentry_name.as_deref().unwrap_or_else(|| span.name());
306        let sentry_op =
307            sentry_op.unwrap_or_else(|| format!("{}::{}", span.metadata().target(), span.name()));
308
309        let hub = sentry_core::Hub::current();
310        let parent_sentry_span = hub.configure_scope(|scope| scope.get_span());
311
312        let mut sentry_span: sentry_core::TransactionOrSpan = match &parent_sentry_span {
313            Some(parent) => parent.start_child(&sentry_op, sentry_name).into(),
314            None => {
315                let ctx = if let Some(trace_header) = sentry_trace {
316                    sentry_core::TransactionContext::continue_from_headers(
317                        sentry_name,
318                        &sentry_op,
319                        [("sentry-trace", trace_header.as_str())],
320                    )
321                } else {
322                    sentry_core::TransactionContext::new(sentry_name, &sentry_op)
323                };
324
325                let tx = sentry_core::start_transaction(ctx);
326                tx.set_origin("auto.tracing");
327                tx.into()
328            }
329        };
330        record_fields(&sentry_span, data);
333
334        set_default_attributes(&mut sentry_span, span.metadata());
335
336        let mut extensions = span.extensions_mut();
337        extensions.insert(SentrySpanData {
338            sentry_span,
339            parent_sentry_span,
340            hub,
341            hub_switch_guard: None,
342        });
343    }
344
345    fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
348        let span = match ctx.span(id) {
349            Some(span) => span,
350            None => return,
351        };
352
353        let mut extensions = span.extensions_mut();
354        if let Some(data) = extensions.get_mut::<SentrySpanData>() {
355            data.hub_switch_guard = Some(sentry_core::HubSwitchGuard::new(data.hub.clone()));
356            data.hub.configure_scope(|scope| {
357                scope.set_span(Some(data.sentry_span.clone()));
358            })
359        }
360    }
361
362    fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
364        let span = match ctx.span(id) {
365            Some(span) => span,
366            None => return,
367        };
368
369        let mut extensions = span.extensions_mut();
370        if let Some(data) = extensions.get_mut::<SentrySpanData>() {
371            data.hub.configure_scope(|scope| {
372                scope.set_span(data.parent_sentry_span.clone());
373            });
374            data.hub_switch_guard.take();
375        }
376    }
377
378    fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
381        let span = match ctx.span(&id) {
382            Some(span) => span,
383            None => return,
384        };
385
386        let mut extensions = span.extensions_mut();
387        let SentrySpanData { sentry_span, .. } = match extensions.remove::<SentrySpanData>() {
388            Some(data) => data,
389            None => return,
390        };
391
392        sentry_span.finish();
393    }
394
395    fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
397        let span = match ctx.span(span) {
398            Some(s) => s,
399            _ => return,
400        };
401
402        let mut extensions = span.extensions_mut();
403        let span = match extensions.get_mut::<SentrySpanData>() {
404            Some(t) => &t.sentry_span,
405            _ => return,
406        };
407
408        let mut data = FieldVisitor::default();
409        values.record(&mut data);
410
411        let sentry_name = data
412            .json_values
413            .remove(SENTRY_NAME_FIELD)
414            .and_then(|v| match v {
415                Value::String(s) => Some(s),
416                _ => None,
417            });
418
419        let sentry_op = data
420            .json_values
421            .remove(SENTRY_OP_FIELD)
422            .and_then(|v| match v {
423                Value::String(s) => Some(s),
424                _ => None,
425            });
426
427        data.json_values.remove(SENTRY_TRACE_FIELD);
429
430        if let Some(name) = sentry_name {
431            span.set_name(&name);
432        }
433        if let Some(op) = sentry_op {
434            span.set_op(&op);
435        }
436
437        record_fields(span, data.json_values);
438    }
439}
440
441fn set_default_attributes(span: &mut TransactionOrSpan, metadata: &Metadata<'_>) {
442    span.set_data("sentry.tracing.target", metadata.target().into());
443
444    if let Some(module) = metadata.module_path() {
445        span.set_data("code.module.name", module.into());
446    }
447
448    if let Some(file) = metadata.file() {
449        span.set_data("code.file.path", file.into());
450    }
451
452    if let Some(line) = metadata.line() {
453        span.set_data("code.line.number", line.into());
454    }
455}
456
457pub fn layer<S>() -> SentryLayer<S>
459where
460    S: Subscriber + for<'a> LookupSpan<'a>,
461{
462    Default::default()
463}
464
465fn extract_span_data(
468    attrs: &span::Attributes,
469) -> (
470    BTreeMap<&'static str, Value>,
471    Option<String>,
472    Option<String>,
473    Option<String>,
474) {
475    let mut json_values = VISITOR_BUFFER.with_borrow_mut(|debug_buffer| {
476        let mut visitor = SpanFieldVisitor {
477            debug_buffer,
478            json_values: Default::default(),
479        };
480        attrs.record(&mut visitor);
481        visitor.json_values
482    });
483
484    let name = json_values.remove(SENTRY_NAME_FIELD).and_then(|v| match v {
485        Value::String(s) => Some(s),
486        _ => None,
487    });
488
489    let op = json_values.remove(SENTRY_OP_FIELD).and_then(|v| match v {
490        Value::String(s) => Some(s),
491        _ => None,
492    });
493
494    let sentry_trace = json_values
495        .remove(SENTRY_TRACE_FIELD)
496        .and_then(|v| match v {
497            Value::String(s) => Some(s),
498            _ => None,
499        });
500
501    (json_values, name, op, sentry_trace)
502}
503
504thread_local! {
505    static VISITOR_BUFFER: RefCell<String> = const { RefCell::new(String::new()) };
506}
507
508struct SpanFieldVisitor<'s> {
510    debug_buffer: &'s mut String,
511    json_values: BTreeMap<&'static str, Value>,
512}
513
514impl SpanFieldVisitor<'_> {
515    fn record<T: Into<Value>>(&mut self, field: &Field, value: T) {
516        self.json_values.insert(field.name(), value.into());
517    }
518}
519
520impl Visit for SpanFieldVisitor<'_> {
521    fn record_i64(&mut self, field: &Field, value: i64) {
522        self.record(field, value);
523    }
524
525    fn record_u64(&mut self, field: &Field, value: u64) {
526        self.record(field, value);
527    }
528
529    fn record_bool(&mut self, field: &Field, value: bool) {
530        self.record(field, value);
531    }
532
533    fn record_str(&mut self, field: &Field, value: &str) {
534        self.record(field, value);
535    }
536
537    fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
538        use std::fmt::Write;
539        self.debug_buffer.reserve(128);
540        write!(self.debug_buffer, "{value:?}").unwrap();
541        self.json_values
542            .insert(field.name(), self.debug_buffer.as_str().into());
543        self.debug_buffer.clear();
544    }
545}