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| 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 { sentry_span, hub });
338 }
339
340 fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
351 let span = match ctx.span(id) {
352 Some(span) => span,
353 None => return,
354 };
355
356 let extensions = span.extensions();
357 if let Some(data) = extensions.get::<SentrySpanData>() {
358 let hub = Arc::new(Hub::new_from_top(&data.hub));
367
368 hub.configure_scope(|scope| {
369 scope.set_span(Some(data.sentry_span.clone()));
370 });
371
372 let guard = HubSwitchGuard::new(hub);
373
374 SPAN_GUARDS.with(|guards| {
375 guards.borrow_mut().push(id.clone(), guard);
376 });
377 }
378 }
379
380 fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
382 let popped = SPAN_GUARDS.with(|guards| guards.borrow_mut().pop(id.clone()));
383
384 sentry_core::debug_assert_or_log!(
386 popped.is_some()
387 || ctx
388 .span(id)
389 .is_none_or(|span| span.extensions().get::<SentrySpanData>().is_none()),
390 "[SentryLayer] missing HubSwitchGuard on exit for span {id:?}. \
391 This span has been exited more times on this thread than it has been entered, \
392 likely due to dropping an `Entered` guard in a different thread than where it was \
393 entered. This mismatch will likely cause the sentry-tracing layer to leak memory."
394 );
395 }
396
397 fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
400 let span = match ctx.span(&id) {
401 Some(span) => span,
402 None => return,
403 };
404
405 let mut extensions = span.extensions_mut();
406 let SentrySpanData { sentry_span, .. } = match extensions.remove::<SentrySpanData>() {
407 Some(data) => data,
408 None => return,
409 };
410
411 sentry_span.finish();
412 }
413
414 fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
416 let span = match ctx.span(span) {
417 Some(s) => s,
418 _ => return,
419 };
420
421 let mut extensions = span.extensions_mut();
422 let span = match extensions.get_mut::<SentrySpanData>() {
423 Some(t) => &t.sentry_span,
424 _ => return,
425 };
426
427 let mut data = FieldVisitor::default();
428 values.record(&mut data);
429
430 let sentry_name = data
431 .json_values
432 .remove(SENTRY_NAME_FIELD)
433 .and_then(|v| match v {
434 Value::String(s) => Some(s),
435 _ => None,
436 });
437
438 let sentry_op = data
439 .json_values
440 .remove(SENTRY_OP_FIELD)
441 .and_then(|v| match v {
442 Value::String(s) => Some(s),
443 _ => None,
444 });
445
446 data.json_values.remove(SENTRY_TRACE_FIELD);
448
449 if let Some(name) = sentry_name {
450 span.set_name(&name);
451 }
452 if let Some(op) = sentry_op {
453 span.set_op(&op);
454 }
455
456 record_fields(span, data.json_values);
457 }
458}
459
460fn set_default_attributes(span: &mut TransactionOrSpan, metadata: &Metadata<'_>) {
461 span.set_data("sentry.tracing.target", metadata.target().into());
462
463 if let Some(module) = metadata.module_path() {
464 span.set_data("code.module.name", module.into());
465 }
466
467 if let Some(file) = metadata.file() {
468 span.set_data("code.file.path", file.into());
469 }
470
471 if let Some(line) = metadata.line() {
472 span.set_data("code.line.number", line.into());
473 }
474}
475
476pub fn layer<S>() -> SentryLayer<S>
478where
479 S: Subscriber + for<'a> LookupSpan<'a>,
480{
481 Default::default()
482}
483
484fn extract_span_data(
487 attrs: &span::Attributes,
488) -> (
489 BTreeMap<&'static str, Value>,
490 Option<String>,
491 Option<String>,
492 Option<String>,
493) {
494 let mut json_values = VISITOR_BUFFER.with_borrow_mut(|debug_buffer| {
495 let mut visitor = SpanFieldVisitor {
496 debug_buffer,
497 json_values: Default::default(),
498 };
499 attrs.record(&mut visitor);
500 visitor.json_values
501 });
502
503 let name = json_values.remove(SENTRY_NAME_FIELD).and_then(|v| match v {
504 Value::String(s) => Some(s),
505 _ => None,
506 });
507
508 let op = json_values.remove(SENTRY_OP_FIELD).and_then(|v| match v {
509 Value::String(s) => Some(s),
510 _ => None,
511 });
512
513 let sentry_trace = json_values
514 .remove(SENTRY_TRACE_FIELD)
515 .and_then(|v| match v {
516 Value::String(s) => Some(s),
517 _ => None,
518 });
519
520 (json_values, name, op, sentry_trace)
521}
522
523thread_local! {
524 static VISITOR_BUFFER: RefCell<String> = const { RefCell::new(String::new()) };
525 static SPAN_GUARDS: RefCell<SpanGuardStack> = RefCell::new(SpanGuardStack::new());
530}
531
532struct SpanFieldVisitor<'s> {
534 debug_buffer: &'s mut String,
535 json_values: BTreeMap<&'static str, Value>,
536}
537
538impl SpanFieldVisitor<'_> {
539 fn record<T: Into<Value>>(&mut self, field: &Field, value: T) {
540 self.json_values.insert(field.name(), value.into());
541 }
542}
543
544impl Visit for SpanFieldVisitor<'_> {
545 fn record_i64(&mut self, field: &Field, value: i64) {
546 self.record(field, value);
547 }
548
549 fn record_u64(&mut self, field: &Field, value: u64) {
550 self.record(field, value);
551 }
552
553 fn record_bool(&mut self, field: &Field, value: bool) {
554 self.record(field, value);
555 }
556
557 fn record_str(&mut self, field: &Field, value: &str) {
558 self.record(field, value);
559 }
560
561 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
562 use std::fmt::Write;
563 self.debug_buffer.reserve(128);
564 write!(self.debug_buffer, "{value:?}").unwrap();
565 self.json_values
566 .insert(field.name(), self.debug_buffer.as_str().into());
567 self.debug_buffer.clear();
568 }
569}