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, Hub, HubSwitchGuard, 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;
19use span_guard_stack::SpanGuardStack;
20
21mod span_guard_stack;
22
23bitflags! {
24 #[derive(Debug, Clone, Copy)]
26 pub struct EventFilter: u32 {
27 const Ignore = 0b000;
29 const Breadcrumb = 0b001;
31 const Event = 0b010;
33 const Log = 0b100;
35 }
36}
37
38#[derive(Debug)]
40#[non_exhaustive]
41#[allow(clippy::large_enum_variant)]
42pub enum EventMapping {
43 Ignore,
45 Breadcrumb(Breadcrumb),
47 Event(sentry_core::protocol::Event<'static>),
49 #[cfg(feature = "logs")]
51 Log(sentry_core::protocol::Log),
52 Combined(CombinedEventMapping),
55}
56
57#[derive(Debug)]
59pub struct CombinedEventMapping(Vec<EventMapping>);
60
61impl From<EventMapping> for CombinedEventMapping {
62 fn from(value: EventMapping) -> Self {
63 match value {
64 EventMapping::Combined(combined) => combined,
65 _ => CombinedEventMapping(vec![value]),
66 }
67 }
68}
69
70impl From<Vec<EventMapping>> for CombinedEventMapping {
71 fn from(value: Vec<EventMapping>) -> Self {
72 Self(value)
73 }
74}
75
76pub fn default_event_filter(metadata: &Metadata) -> EventFilter {
81 match metadata.level() {
82 #[cfg(feature = "logs")]
83 &Level::ERROR => EventFilter::Event | EventFilter::Log,
84 #[cfg(not(feature = "logs"))]
85 &Level::ERROR => EventFilter::Event,
86 #[cfg(feature = "logs")]
87 &Level::WARN | &Level::INFO => EventFilter::Breadcrumb | EventFilter::Log,
88 #[cfg(not(feature = "logs"))]
89 &Level::WARN | &Level::INFO => EventFilter::Breadcrumb,
90 &Level::DEBUG | &Level::TRACE => EventFilter::Ignore,
91 }
92}
93
94pub fn default_span_filter(metadata: &Metadata) -> bool {
99 matches!(
100 metadata.level(),
101 &Level::ERROR | &Level::WARN | &Level::INFO
102 )
103}
104
105type EventMapper<S> = Box<dyn Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync>;
106
107pub struct SentryLayer<S> {
109 event_filter: Box<dyn Fn(&Metadata) -> EventFilter + Send + Sync>,
110 event_mapper: Option<EventMapper<S>>,
111
112 span_filter: Box<dyn Fn(&Metadata) -> bool + Send + Sync>,
113
114 with_span_attributes: bool,
115}
116
117impl<S> SentryLayer<S> {
118 #[must_use]
123 pub fn event_filter<F>(mut self, filter: F) -> Self
124 where
125 F: Fn(&Metadata) -> EventFilter + Send + Sync + 'static,
126 {
127 self.event_filter = Box::new(filter);
128 self
129 }
130
131 #[must_use]
136 pub fn event_mapper<F>(mut self, mapper: F) -> Self
137 where
138 F: Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync + 'static,
139 {
140 self.event_mapper = Some(Box::new(mapper));
141 self
142 }
143
144 #[must_use]
151 pub fn span_filter<F>(mut self, filter: F) -> Self
152 where
153 F: Fn(&Metadata) -> bool + Send + Sync + 'static,
154 {
155 self.span_filter = Box::new(filter);
156 self
157 }
158
159 #[must_use]
167 pub fn enable_span_attributes(mut self) -> Self {
168 self.with_span_attributes = true;
169 self
170 }
171}
172
173impl<S> Default for SentryLayer<S>
174where
175 S: Subscriber + for<'a> LookupSpan<'a>,
176{
177 fn default() -> Self {
178 Self {
179 event_filter: Box::new(default_event_filter),
180 event_mapper: None,
181
182 span_filter: Box::new(default_span_filter),
183
184 with_span_attributes: false,
185 }
186 }
187}
188
189#[inline(always)]
190fn record_fields<'a, K: AsRef<str> + Into<Cow<'a, str>>>(
191 span: &TransactionOrSpan,
192 data: BTreeMap<K, Value>,
193) {
194 match span {
195 TransactionOrSpan::Span(span) => {
196 let mut span = span.data();
197 for (key, value) in data {
198 if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
199 match value {
200 Value::Bool(value) => {
201 span.set_tag(stripped_key.to_owned(), value.to_string())
202 }
203 Value::Number(value) => {
204 span.set_tag(stripped_key.to_owned(), value.to_string())
205 }
206 Value::String(value) => span.set_tag(stripped_key.to_owned(), value),
207 _ => span.set_data(key.into().into_owned(), value),
208 }
209 } else {
210 span.set_data(key.into().into_owned(), value);
211 }
212 }
213 }
214 TransactionOrSpan::Transaction(transaction) => {
215 let mut transaction = transaction.data();
216 for (key, value) in data {
217 if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
218 match value {
219 Value::Bool(value) => {
220 transaction.set_tag(stripped_key.into(), value.to_string())
221 }
222 Value::Number(value) => {
223 transaction.set_tag(stripped_key.into(), value.to_string())
224 }
225 Value::String(value) => transaction.set_tag(stripped_key.into(), value),
226 _ => transaction.set_data(key.into(), value),
227 }
228 } else {
229 transaction.set_data(key.into(), value);
230 }
231 }
232 }
233 }
234}
235
236pub(super) struct SentrySpanData {
240 pub(super) sentry_span: TransactionOrSpan,
241 hub: Arc<sentry_core::Hub>,
242}
243
244impl<S> Layer<S> for SentryLayer<S>
245where
246 S: Subscriber + for<'a> LookupSpan<'a>,
247{
248 fn on_event(&self, event: &Event, ctx: Context<'_, S>) {
249 let items = match &self.event_mapper {
250 Some(mapper) => mapper(event, ctx),
251 None => {
252 let span_ctx = self.with_span_attributes.then_some(ctx);
253 let filter = (self.event_filter)(event.metadata());
254 let mut items = vec![];
255 if filter.contains(EventFilter::Breadcrumb) {
256 items.push(EventMapping::Breadcrumb(breadcrumb_from_event(
257 event,
258 span_ctx.as_ref(),
259 )));
260 }
261 if filter.contains(EventFilter::Event) {
262 items.push(EventMapping::Event(event_from_event(
263 event,
264 span_ctx.as_ref(),
265 )));
266 }
267 #[cfg(feature = "logs")]
268 if filter.contains(EventFilter::Log) {
269 items.push(EventMapping::Log(log_from_event(event, span_ctx.as_ref())));
270 }
271 EventMapping::Combined(CombinedEventMapping(items))
272 }
273 };
274 let items = CombinedEventMapping::from(items);
275
276 for item in items.0 {
277 match item {
278 EventMapping::Ignore => (),
279 EventMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
280 EventMapping::Event(event) => {
281 sentry_core::capture_event(event);
282 }
283 #[cfg(feature = "logs")]
284 EventMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)),
285 EventMapping::Combined(_) => {
286 sentry_core::sentry_debug!(
287 "[SentryLayer] found nested CombinedEventMapping, ignoring"
288 )
289 }
290 }
291 }
292 }
293
294 fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
297 let span = match ctx.span(id) {
298 Some(span) => span,
299 None => return,
300 };
301
302 if !(self.span_filter)(span.metadata()) {
303 return;
304 }
305
306 let (data, sentry_name, sentry_op, sentry_trace) = extract_span_data(attrs);
307 let sentry_name = sentry_name.as_deref().unwrap_or_else(|| span.name());
308 let sentry_op =
309 sentry_op.unwrap_or_else(|| format!("{}::{}", span.metadata().target(), span.name()));
310
311 let hub = sentry_core::Hub::current();
312 let parent_sentry_span = hub.configure_scope(|scope| scope.get_span());
313
314 let mut sentry_span: sentry_core::TransactionOrSpan = match &parent_sentry_span {
315 Some(parent) => parent.start_child(&sentry_op, sentry_name).into(),
316 None => {
317 let ctx = if let Some(trace_header) = sentry_trace {
318 sentry_core::TransactionContext::continue_from_headers(
319 sentry_name,
320 &sentry_op,
321 [("sentry-trace", trace_header.as_str())],
322 )
323 } else {
324 sentry_core::TransactionContext::new(sentry_name, &sentry_op)
325 };
326
327 let tx = sentry_core::start_transaction(ctx);
328 tx.set_origin("auto.tracing");
329 tx.into()
330 }
331 };
332 record_fields(&sentry_span, data);
335
336 set_default_attributes(&mut sentry_span, span.metadata());
337
338 let mut extensions = span.extensions_mut();
339 extensions.insert(SentrySpanData { sentry_span, hub });
340 }
341
342 fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
353 let span = match ctx.span(id) {
354 Some(span) => span,
355 None => return,
356 };
357
358 let extensions = span.extensions();
359 if let Some(data) = extensions.get::<SentrySpanData>() {
360 let hub = Arc::new(Hub::new_from_top(&data.hub));
369
370 hub.configure_scope(|scope| {
371 scope.set_span(Some(data.sentry_span.clone()));
372 });
373
374 let guard = HubSwitchGuard::new(hub);
375
376 SPAN_GUARDS.with(|guards| {
377 guards.borrow_mut().push(id.clone(), guard);
378 });
379 }
380 }
381
382 fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
384 let popped = SPAN_GUARDS.with(|guards| guards.borrow_mut().pop(id.clone()));
385
386 sentry_core::debug_assert_or_log!(
388 popped.is_some()
389 || ctx
390 .span(id)
391 .is_none_or(|span| span.extensions().get::<SentrySpanData>().is_none()),
392 "[SentryLayer] missing HubSwitchGuard on exit for span {id:?}. \
393 This span has been exited more times on this thread than it has been entered, \
394 likely due to dropping an `Entered` guard in a different thread than where it was \
395 entered. This mismatch will likely cause the sentry-tracing layer to leak memory."
396 );
397 }
398
399 fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
402 let span = match ctx.span(&id) {
403 Some(span) => span,
404 None => return,
405 };
406
407 let mut extensions = span.extensions_mut();
408 let SentrySpanData { sentry_span, .. } = match extensions.remove::<SentrySpanData>() {
409 Some(data) => data,
410 None => return,
411 };
412
413 sentry_span.finish();
414 }
415
416 fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
418 let span = match ctx.span(span) {
419 Some(s) => s,
420 _ => return,
421 };
422
423 let mut extensions = span.extensions_mut();
424 let span = match extensions.get_mut::<SentrySpanData>() {
425 Some(t) => &t.sentry_span,
426 _ => return,
427 };
428
429 let mut data = FieldVisitor::default();
430 values.record(&mut data);
431
432 let sentry_name = data
433 .json_values
434 .remove(SENTRY_NAME_FIELD)
435 .and_then(|v| match v {
436 Value::String(s) => Some(s),
437 _ => None,
438 });
439
440 let sentry_op = data
441 .json_values
442 .remove(SENTRY_OP_FIELD)
443 .and_then(|v| match v {
444 Value::String(s) => Some(s),
445 _ => None,
446 });
447
448 data.json_values.remove(SENTRY_TRACE_FIELD);
450
451 if let Some(name) = sentry_name {
452 span.set_name(&name);
453 }
454 if let Some(op) = sentry_op {
455 span.set_op(&op);
456 }
457
458 record_fields(span, data.json_values);
459 }
460}
461
462fn set_default_attributes(span: &mut TransactionOrSpan, metadata: &Metadata<'_>) {
463 span.set_data("sentry.tracing.target", metadata.target().into());
464
465 if let Some(module) = metadata.module_path() {
466 span.set_data("code.module.name", module.into());
467 }
468
469 if let Some(file) = metadata.file() {
470 span.set_data("code.file.path", file.into());
471 }
472
473 if let Some(line) = metadata.line() {
474 span.set_data("code.line.number", line.into());
475 }
476}
477
478pub fn layer<S>() -> SentryLayer<S>
480where
481 S: Subscriber + for<'a> LookupSpan<'a>,
482{
483 Default::default()
484}
485
486fn extract_span_data(
489 attrs: &span::Attributes,
490) -> (
491 BTreeMap<&'static str, Value>,
492 Option<String>,
493 Option<String>,
494 Option<String>,
495) {
496 let mut json_values = VISITOR_BUFFER.with_borrow_mut(|debug_buffer| {
497 let mut visitor = SpanFieldVisitor {
498 debug_buffer,
499 json_values: Default::default(),
500 };
501 attrs.record(&mut visitor);
502 visitor.json_values
503 });
504
505 let name = json_values.remove(SENTRY_NAME_FIELD).and_then(|v| match v {
506 Value::String(s) => Some(s),
507 _ => None,
508 });
509
510 let op = json_values.remove(SENTRY_OP_FIELD).and_then(|v| match v {
511 Value::String(s) => Some(s),
512 _ => None,
513 });
514
515 let sentry_trace = json_values
516 .remove(SENTRY_TRACE_FIELD)
517 .and_then(|v| match v {
518 Value::String(s) => Some(s),
519 _ => None,
520 });
521
522 (json_values, name, op, sentry_trace)
523}
524
525thread_local! {
526 static VISITOR_BUFFER: RefCell<String> = const { RefCell::new(String::new()) };
527 static SPAN_GUARDS: RefCell<SpanGuardStack> = RefCell::new(SpanGuardStack::new());
532}
533
534struct SpanFieldVisitor<'s> {
536 debug_buffer: &'s mut String,
537 json_values: BTreeMap<&'static str, Value>,
538}
539
540impl SpanFieldVisitor<'_> {
541 fn record<T: Into<Value>>(&mut self, field: &Field, value: T) {
542 self.json_values.insert(field.name(), value.into());
543 }
544}
545
546impl Visit for SpanFieldVisitor<'_> {
547 fn record_i64(&mut self, field: &Field, value: i64) {
548 self.record(field, value);
549 }
550
551 fn record_u64(&mut self, field: &Field, value: u64) {
552 self.record(field, value);
553 }
554
555 fn record_bool(&mut self, field: &Field, value: bool) {
556 self.record(field, value);
557 }
558
559 fn record_str(&mut self, field: &Field, value: &str) {
560 self.record(field, value);
561 }
562
563 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
564 use std::fmt::Write;
565 self.debug_buffer.reserve(128);
566 write!(self.debug_buffer, "{value:?}").unwrap();
567 self.json_values
568 .insert(field.name(), self.debug_buffer.as_str().into());
569 self.debug_buffer.clear();
570 }
571}