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}