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]
41pub enum EventMapping {
42 Ignore,
44 Breadcrumb(Breadcrumb),
46 Event(Box<sentry_core::protocol::Event<'static>>),
48 #[cfg(feature = "logs")]
50 Log(sentry_core::protocol::Log),
51 Combined(CombinedEventMapping),
54}
55
56#[derive(Debug)]
58pub struct CombinedEventMapping(Vec<EventMapping>);
59
60impl From<EventMapping> for CombinedEventMapping {
61 fn from(value: EventMapping) -> Self {
62 match value {
63 EventMapping::Combined(combined) => combined,
64 _ => CombinedEventMapping(vec![value]),
65 }
66 }
67}
68
69impl From<Vec<EventMapping>> for CombinedEventMapping {
70 fn from(value: Vec<EventMapping>) -> Self {
71 Self(value)
72 }
73}
74
75pub fn default_event_filter(metadata: &Metadata) -> EventFilter {
80 match metadata.level() {
81 #[cfg(feature = "logs")]
82 &Level::ERROR => EventFilter::Event | EventFilter::Log,
83 #[cfg(not(feature = "logs"))]
84 &Level::ERROR => EventFilter::Event,
85 #[cfg(feature = "logs")]
86 &Level::WARN | &Level::INFO => EventFilter::Breadcrumb | EventFilter::Log,
87 #[cfg(not(feature = "logs"))]
88 &Level::WARN | &Level::INFO => EventFilter::Breadcrumb,
89 &Level::DEBUG | &Level::TRACE => EventFilter::Ignore,
90 }
91}
92
93pub fn default_span_filter(metadata: &Metadata) -> bool {
98 matches!(
99 metadata.level(),
100 &Level::ERROR | &Level::WARN | &Level::INFO
101 )
102}
103
104type EventMapper<S> = Box<dyn Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync>;
105
106pub struct SentryLayer<S> {
108 event_filter: Box<dyn Fn(&Metadata) -> EventFilter + Send + Sync>,
109 event_mapper: Option<EventMapper<S>>,
110
111 span_filter: Box<dyn Fn(&Metadata) -> bool + Send + Sync>,
112
113 with_span_attributes: bool,
114}
115
116impl<S> SentryLayer<S> {
117 #[must_use]
122 pub fn event_filter<F>(mut self, filter: F) -> Self
123 where
124 F: Fn(&Metadata) -> EventFilter + Send + Sync + 'static,
125 {
126 self.event_filter = Box::new(filter);
127 self
128 }
129
130 #[must_use]
135 pub fn event_mapper<F>(mut self, mapper: F) -> Self
136 where
137 F: Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync + 'static,
138 {
139 self.event_mapper = Some(Box::new(mapper));
140 self
141 }
142
143 #[must_use]
150 pub fn span_filter<F>(mut self, filter: F) -> Self
151 where
152 F: Fn(&Metadata) -> bool + Send + Sync + 'static,
153 {
154 self.span_filter = Box::new(filter);
155 self
156 }
157
158 #[must_use]
166 pub fn enable_span_attributes(mut self) -> Self {
167 self.with_span_attributes = true;
168 self
169 }
170}
171
172impl<S> Default for SentryLayer<S>
173where
174 S: Subscriber + for<'a> LookupSpan<'a>,
175{
176 fn default() -> Self {
177 Self {
178 event_filter: Box::new(default_event_filter),
179 event_mapper: None,
180
181 span_filter: Box::new(default_span_filter),
182
183 with_span_attributes: false,
184 }
185 }
186}
187
188#[inline(always)]
189fn record_fields<'a, K: AsRef<str> + Into<Cow<'a, str>>>(
190 span: &TransactionOrSpan,
191 data: BTreeMap<K, Value>,
192) {
193 match span {
194 TransactionOrSpan::Span(span) => {
195 let mut span = span.data();
196 for (key, value) in data {
197 if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
198 match value {
199 Value::Bool(value) => {
200 span.set_tag(stripped_key.to_owned(), value.to_string())
201 }
202 Value::Number(value) => {
203 span.set_tag(stripped_key.to_owned(), value.to_string())
204 }
205 Value::String(value) => span.set_tag(stripped_key.to_owned(), value),
206 _ => span.set_data(key.into().into_owned(), value),
207 }
208 } else {
209 span.set_data(key.into().into_owned(), value);
210 }
211 }
212 }
213 TransactionOrSpan::Transaction(transaction) => {
214 let mut transaction = transaction.data();
215 for (key, value) in data {
216 if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
217 match value {
218 Value::Bool(value) => {
219 transaction.set_tag(stripped_key.into(), value.to_string())
220 }
221 Value::Number(value) => {
222 transaction.set_tag(stripped_key.into(), value.to_string())
223 }
224 Value::String(value) => transaction.set_tag(stripped_key.into(), value),
225 _ => transaction.set_data(key.into(), value),
226 }
227 } else {
228 transaction.set_data(key.into(), value);
229 }
230 }
231 }
232 }
233}
234
235pub(super) struct SentrySpanData {
239 pub(super) sentry_span: TransactionOrSpan,
240 hub: Arc<sentry_core::Hub>,
241}
242
243impl<S> Layer<S> for SentryLayer<S>
244where
245 S: Subscriber + for<'a> LookupSpan<'a>,
246{
247 fn on_event(&self, event: &Event, ctx: Context<'_, S>) {
248 let items = match &self.event_mapper {
249 Some(mapper) => mapper(event, ctx),
250 None => {
251 let span_ctx = self.with_span_attributes.then_some(ctx);
252 let filter = (self.event_filter)(event.metadata());
253 let mut items = vec![];
254 if filter.contains(EventFilter::Breadcrumb) {
255 items.push(EventMapping::Breadcrumb(breadcrumb_from_event(
256 event,
257 span_ctx.as_ref(),
258 )));
259 }
260 if filter.contains(EventFilter::Event) {
261 items.push(EventMapping::Event(
262 event_from_event(event, span_ctx.as_ref()).into(),
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| {
283 let enabled = hub.client().is_none_or(|client| {
284 let options = client.options();
285
286 #[expect(deprecated, reason = "checking a deprecated field")]
287 options.enable_logs
288 });
289 if enabled {
290 hub.capture_log(log);
291 }
292 }),
293 EventMapping::Combined(_) => {
294 sentry_core::sentry_debug!(
295 "[SentryLayer] found nested CombinedEventMapping, ignoring"
296 )
297 }
298 }
299 }
300 }
301
302 fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
305 let span = match ctx.span(id) {
306 Some(span) => span,
307 None => return,
308 };
309
310 if !(self.span_filter)(span.metadata()) {
311 return;
312 }
313
314 let (data, sentry_name, sentry_op, sentry_trace) = extract_span_data(attrs);
315 let sentry_name = sentry_name.as_deref().unwrap_or_else(|| span.name());
316 let sentry_op =
317 sentry_op.unwrap_or_else(|| format!("{}::{}", span.metadata().target(), span.name()));
318
319 let hub = sentry_core::Hub::current();
320 let parent_sentry_span = hub.configure_scope(|scope| scope.get_span());
321
322 let mut sentry_span: sentry_core::TransactionOrSpan = match &parent_sentry_span {
323 Some(parent) => parent.start_child(&sentry_op, sentry_name).into(),
324 None => {
325 let ctx = if let Some(trace_header) = sentry_trace {
326 sentry_core::TransactionContext::continue_from_headers(
327 sentry_name,
328 &sentry_op,
329 [("sentry-trace", trace_header.as_str())],
330 )
331 } else {
332 sentry_core::TransactionContext::new(sentry_name, &sentry_op)
333 };
334
335 let tx = sentry_core::start_transaction(ctx);
336 tx.set_origin("auto.tracing");
337 tx.into()
338 }
339 };
340 record_fields(&sentry_span, data);
343
344 set_default_attributes(&mut sentry_span, span.metadata());
345
346 let mut extensions = span.extensions_mut();
347 extensions.insert(SentrySpanData { sentry_span, hub });
348 }
349
350 fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
361 let span = match ctx.span(id) {
362 Some(span) => span,
363 None => return,
364 };
365
366 let extensions = span.extensions();
367 if let Some(data) = extensions.get::<SentrySpanData>() {
368 let hub = Arc::new(Hub::new_from_top(&data.hub));
377
378 hub.configure_scope(|scope| {
379 scope.set_span(Some(data.sentry_span.clone()));
380 });
381
382 let guard = HubSwitchGuard::new(hub);
383
384 SPAN_GUARDS.with(|guards| {
385 guards.borrow_mut().push(id.clone(), guard);
386 });
387 }
388 }
389
390 fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
392 let popped = SPAN_GUARDS.with(|guards| guards.borrow_mut().pop(id.clone()));
393
394 sentry_core::debug_assert_or_log!(
396 popped.is_some()
397 || ctx
398 .span(id)
399 .is_none_or(|span| span.extensions().get::<SentrySpanData>().is_none()),
400 "[SentryLayer] missing HubSwitchGuard on exit for span {id:?}. \
401 This span has been exited more times on this thread than it has been entered, \
402 likely due to dropping an `Entered` guard in a different thread than where it was \
403 entered. This mismatch will likely cause the sentry-tracing layer to leak memory."
404 );
405 }
406
407 fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
410 let span = match ctx.span(&id) {
411 Some(span) => span,
412 None => return,
413 };
414
415 let mut extensions = span.extensions_mut();
416 let SentrySpanData { sentry_span, .. } = match extensions.remove::<SentrySpanData>() {
417 Some(data) => data,
418 None => return,
419 };
420
421 sentry_span.finish();
422 }
423
424 fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
426 let span = match ctx.span(span) {
427 Some(s) => s,
428 _ => return,
429 };
430
431 let mut extensions = span.extensions_mut();
432 let span = match extensions.get_mut::<SentrySpanData>() {
433 Some(t) => &t.sentry_span,
434 _ => return,
435 };
436
437 let mut data = FieldVisitor::default();
438 values.record(&mut data);
439
440 let sentry_name = data
441 .json_values
442 .remove(SENTRY_NAME_FIELD)
443 .and_then(|v| match v {
444 Value::String(s) => Some(s),
445 _ => None,
446 });
447
448 let sentry_op = data
449 .json_values
450 .remove(SENTRY_OP_FIELD)
451 .and_then(|v| match v {
452 Value::String(s) => Some(s),
453 _ => None,
454 });
455
456 data.json_values.remove(SENTRY_TRACE_FIELD);
458
459 if let Some(name) = sentry_name {
460 span.set_name(&name);
461 }
462 if let Some(op) = sentry_op {
463 span.set_op(&op);
464 }
465
466 record_fields(span, data.json_values);
467 }
468}
469
470fn set_default_attributes(span: &mut TransactionOrSpan, metadata: &Metadata<'_>) {
471 span.set_data("sentry.tracing.target", metadata.target().into());
472
473 if let Some(module) = metadata.module_path() {
474 span.set_data("code.module.name", module.into());
475 }
476
477 if let Some(file) = metadata.file() {
478 span.set_data("code.file.path", file.into());
479 }
480
481 if let Some(line) = metadata.line() {
482 span.set_data("code.line.number", line.into());
483 }
484}
485
486pub fn layer<S>() -> SentryLayer<S>
488where
489 S: Subscriber + for<'a> LookupSpan<'a>,
490{
491 Default::default()
492}
493
494fn extract_span_data(
497 attrs: &span::Attributes,
498) -> (
499 BTreeMap<&'static str, Value>,
500 Option<String>,
501 Option<String>,
502 Option<String>,
503) {
504 let mut json_values = VISITOR_BUFFER.with_borrow_mut(|debug_buffer| {
505 let mut visitor = SpanFieldVisitor {
506 debug_buffer,
507 json_values: Default::default(),
508 };
509 attrs.record(&mut visitor);
510 visitor.json_values
511 });
512
513 let name = json_values.remove(SENTRY_NAME_FIELD).and_then(|v| match v {
514 Value::String(s) => Some(s),
515 _ => None,
516 });
517
518 let op = json_values.remove(SENTRY_OP_FIELD).and_then(|v| match v {
519 Value::String(s) => Some(s),
520 _ => None,
521 });
522
523 let sentry_trace = json_values
524 .remove(SENTRY_TRACE_FIELD)
525 .and_then(|v| match v {
526 Value::String(s) => Some(s),
527 _ => None,
528 });
529
530 (json_values, name, op, sentry_trace)
531}
532
533thread_local! {
534 static VISITOR_BUFFER: RefCell<String> = const { RefCell::new(String::new()) };
535 static SPAN_GUARDS: RefCell<SpanGuardStack> = RefCell::new(SpanGuardStack::new());
540}
541
542struct SpanFieldVisitor<'s> {
544 debug_buffer: &'s mut String,
545 json_values: BTreeMap<&'static str, Value>,
546}
547
548impl SpanFieldVisitor<'_> {
549 fn record<T: Into<Value>>(&mut self, field: &Field, value: T) {
550 self.json_values.insert(field.name(), value.into());
551 }
552}
553
554impl Visit for SpanFieldVisitor<'_> {
555 fn record_i64(&mut self, field: &Field, value: i64) {
556 self.record(field, value);
557 }
558
559 fn record_u64(&mut self, field: &Field, value: u64) {
560 self.record(field, value);
561 }
562
563 fn record_bool(&mut self, field: &Field, value: bool) {
564 self.record(field, value);
565 }
566
567 fn record_f64(&mut self, field: &Field, value: f64) {
568 self.record(field, value);
569 }
570
571 fn record_str(&mut self, field: &Field, value: &str) {
572 self.record(field, value);
573 }
574
575 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
576 use std::fmt::Write;
577 self.debug_buffer.reserve(128);
578 write!(self.debug_buffer, "{value:?}").unwrap();
579 self.json_values
580 .insert(field.name(), self.debug_buffer.as_str().into());
581 self.debug_buffer.clear();
582 }
583}