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#[allow(clippy::large_enum_variant)]
41pub enum EventMapping {
42 Ignore,
44 Breadcrumb(Breadcrumb),
46 Event(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(event_from_event(
262 event,
263 span_ctx.as_ref(),
264 )));
265 }
266 #[cfg(feature = "logs")]
267 if filter.contains(EventFilter::Log) {
268 items.push(EventMapping::Log(log_from_event(event, span_ctx.as_ref())));
269 }
270 EventMapping::Combined(CombinedEventMapping(items))
271 }
272 };
273 let items = CombinedEventMapping::from(items);
274
275 for item in items.0 {
276 match item {
277 EventMapping::Ignore => (),
278 EventMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
279 EventMapping::Event(event) => {
280 sentry_core::capture_event(event);
281 }
282 #[cfg(feature = "logs")]
283 EventMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)),
284 EventMapping::Combined(_) => {
285 sentry_core::sentry_debug!(
286 "[SentryLayer] found nested CombinedEventMapping, ignoring"
287 )
288 }
289 }
290 }
291 }
292
293 fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
296 let span = match ctx.span(id) {
297 Some(span) => span,
298 None => return,
299 };
300
301 if !(self.span_filter)(span.metadata()) {
302 return;
303 }
304
305 let (data, sentry_name, sentry_op, sentry_trace) = extract_span_data(attrs);
306 let sentry_name = sentry_name.as_deref().unwrap_or_else(|| span.name());
307 let sentry_op =
308 sentry_op.unwrap_or_else(|| format!("{}::{}", span.metadata().target(), span.name()));
309
310 let hub = sentry_core::Hub::current();
311 let parent_sentry_span = hub.configure_scope(|scope| scope.get_span());
312
313 let mut sentry_span: sentry_core::TransactionOrSpan = match &parent_sentry_span {
314 Some(parent) => parent.start_child(&sentry_op, sentry_name).into(),
315 None => {
316 let ctx = if let Some(trace_header) = sentry_trace {
317 sentry_core::TransactionContext::continue_from_headers(
318 sentry_name,
319 &sentry_op,
320 [("sentry-trace", trace_header.as_str())],
321 )
322 } else {
323 sentry_core::TransactionContext::new(sentry_name, &sentry_op)
324 };
325
326 let tx = sentry_core::start_transaction(ctx);
327 tx.set_origin("auto.tracing");
328 tx.into()
329 }
330 };
331 record_fields(&sentry_span, data);
334
335 set_default_attributes(&mut sentry_span, span.metadata());
336
337 let mut extensions = span.extensions_mut();
338 extensions.insert(SentrySpanData { sentry_span, hub });
339 }
340
341 fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
352 let span = match ctx.span(id) {
353 Some(span) => span,
354 None => return,
355 };
356
357 let extensions = span.extensions();
358 if let Some(data) = extensions.get::<SentrySpanData>() {
359 let hub = Arc::new(Hub::new_from_top(&data.hub));
368
369 hub.configure_scope(|scope| {
370 scope.set_span(Some(data.sentry_span.clone()));
371 });
372
373 let guard = HubSwitchGuard::new(hub);
374
375 SPAN_GUARDS.with(|guards| {
376 guards.borrow_mut().push(id.clone(), guard);
377 });
378 }
379 }
380
381 fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
383 let popped = SPAN_GUARDS.with(|guards| guards.borrow_mut().pop(id.clone()));
384
385 sentry_core::debug_assert_or_log!(
387 popped.is_some()
388 || ctx
389 .span(id)
390 .is_none_or(|span| span.extensions().get::<SentrySpanData>().is_none()),
391 "[SentryLayer] missing HubSwitchGuard on exit for span {id:?}. \
392 This span has been exited more times on this thread than it has been entered, \
393 likely due to dropping an `Entered` guard in a different thread than where it was \
394 entered. This mismatch will likely cause the sentry-tracing layer to leak memory."
395 );
396 }
397
398 fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
401 let span = match ctx.span(&id) {
402 Some(span) => span,
403 None => return,
404 };
405
406 let mut extensions = span.extensions_mut();
407 let SentrySpanData { sentry_span, .. } = match extensions.remove::<SentrySpanData>() {
408 Some(data) => data,
409 None => return,
410 };
411
412 sentry_span.finish();
413 }
414
415 fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
417 let span = match ctx.span(span) {
418 Some(s) => s,
419 _ => return,
420 };
421
422 let mut extensions = span.extensions_mut();
423 let span = match extensions.get_mut::<SentrySpanData>() {
424 Some(t) => &t.sentry_span,
425 _ => return,
426 };
427
428 let mut data = FieldVisitor::default();
429 values.record(&mut data);
430
431 let sentry_name = data
432 .json_values
433 .remove(SENTRY_NAME_FIELD)
434 .and_then(|v| match v {
435 Value::String(s) => Some(s),
436 _ => None,
437 });
438
439 let sentry_op = data
440 .json_values
441 .remove(SENTRY_OP_FIELD)
442 .and_then(|v| match v {
443 Value::String(s) => Some(s),
444 _ => None,
445 });
446
447 data.json_values.remove(SENTRY_TRACE_FIELD);
449
450 if let Some(name) = sentry_name {
451 span.set_name(&name);
452 }
453 if let Some(op) = sentry_op {
454 span.set_op(&op);
455 }
456
457 record_fields(span, data.json_values);
458 }
459}
460
461fn set_default_attributes(span: &mut TransactionOrSpan, metadata: &Metadata<'_>) {
462 span.set_data("sentry.tracing.target", metadata.target().into());
463
464 if let Some(module) = metadata.module_path() {
465 span.set_data("code.module.name", module.into());
466 }
467
468 if let Some(file) = metadata.file() {
469 span.set_data("code.file.path", file.into());
470 }
471
472 if let Some(line) = metadata.line() {
473 span.set_data("code.line.number", line.into());
474 }
475}
476
477pub fn layer<S>() -> SentryLayer<S>
479where
480 S: Subscriber + for<'a> LookupSpan<'a>,
481{
482 Default::default()
483}
484
485fn extract_span_data(
488 attrs: &span::Attributes,
489) -> (
490 BTreeMap<&'static str, Value>,
491 Option<String>,
492 Option<String>,
493 Option<String>,
494) {
495 let mut json_values = VISITOR_BUFFER.with_borrow_mut(|debug_buffer| {
496 let mut visitor = SpanFieldVisitor {
497 debug_buffer,
498 json_values: Default::default(),
499 };
500 attrs.record(&mut visitor);
501 visitor.json_values
502 });
503
504 let name = json_values.remove(SENTRY_NAME_FIELD).and_then(|v| match v {
505 Value::String(s) => Some(s),
506 _ => None,
507 });
508
509 let op = json_values.remove(SENTRY_OP_FIELD).and_then(|v| match v {
510 Value::String(s) => Some(s),
511 _ => None,
512 });
513
514 let sentry_trace = json_values
515 .remove(SENTRY_TRACE_FIELD)
516 .and_then(|v| match v {
517 Value::String(s) => Some(s),
518 _ => None,
519 });
520
521 (json_values, name, op, sentry_trace)
522}
523
524thread_local! {
525 static VISITOR_BUFFER: RefCell<String> = const { RefCell::new(String::new()) };
526 static SPAN_GUARDS: RefCell<SpanGuardStack> = RefCell::new(SpanGuardStack::new());
531}
532
533struct SpanFieldVisitor<'s> {
535 debug_buffer: &'s mut String,
536 json_values: BTreeMap<&'static str, Value>,
537}
538
539impl SpanFieldVisitor<'_> {
540 fn record<T: Into<Value>>(&mut self, field: &Field, value: T) {
541 self.json_values.insert(field.name(), value.into());
542 }
543}
544
545impl Visit for SpanFieldVisitor<'_> {
546 fn record_i64(&mut self, field: &Field, value: i64) {
547 self.record(field, value);
548 }
549
550 fn record_u64(&mut self, field: &Field, value: u64) {
551 self.record(field, value);
552 }
553
554 fn record_bool(&mut self, field: &Field, value: bool) {
555 self.record(field, value);
556 }
557
558 fn record_str(&mut self, field: &Field, value: &str) {
559 self.record(field, value);
560 }
561
562 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
563 use std::fmt::Write;
564 self.debug_buffer.reserve(128);
565 write!(self.debug_buffer, "{value:?}").unwrap();
566 self.json_values
567 .insert(field.name(), self.debug_buffer.as_str().into());
568 self.debug_buffer.clear();
569 }
570}