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::TAGS_PREFIX;
16
17bitflags! {
18 #[derive(Debug, Clone, Copy)]
20 pub struct EventFilter: u32 {
21 const Ignore = 0b000;
23 const Breadcrumb = 0b001;
25 const Event = 0b010;
27 const Log = 0b100;
29 }
30}
31
32#[derive(Debug)]
34#[allow(clippy::large_enum_variant)]
35pub enum EventMapping {
36 Ignore,
38 Breadcrumb(Breadcrumb),
40 Event(sentry_core::protocol::Event<'static>),
42 #[cfg(feature = "logs")]
44 Log(sentry_core::protocol::Log),
45 Combined(CombinedEventMapping),
48}
49
50#[derive(Debug)]
52pub struct CombinedEventMapping(Vec<EventMapping>);
53
54impl From<EventMapping> for CombinedEventMapping {
55 fn from(value: EventMapping) -> Self {
56 match value {
57 EventMapping::Combined(combined) => combined,
58 _ => CombinedEventMapping(vec![value]),
59 }
60 }
61}
62
63impl From<Vec<EventMapping>> for CombinedEventMapping {
64 fn from(value: Vec<EventMapping>) -> Self {
65 Self(value)
66 }
67}
68
69pub fn default_event_filter(metadata: &Metadata) -> EventFilter {
74 match metadata.level() {
75 &Level::ERROR => EventFilter::Event,
76 &Level::WARN | &Level::INFO => EventFilter::Breadcrumb,
77 &Level::DEBUG | &Level::TRACE => EventFilter::Ignore,
78 }
79}
80
81pub fn default_span_filter(metadata: &Metadata) -> bool {
86 matches!(
87 metadata.level(),
88 &Level::ERROR | &Level::WARN | &Level::INFO
89 )
90}
91
92type EventMapper<S> = Box<dyn Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync>;
93
94pub struct SentryLayer<S> {
96 event_filter: Box<dyn Fn(&Metadata) -> EventFilter + Send + Sync>,
97 event_mapper: Option<EventMapper<S>>,
98
99 span_filter: Box<dyn Fn(&Metadata) -> bool + Send + Sync>,
100
101 with_span_attributes: bool,
102}
103
104impl<S> SentryLayer<S> {
105 #[must_use]
110 pub fn event_filter<F>(mut self, filter: F) -> Self
111 where
112 F: Fn(&Metadata) -> EventFilter + Send + Sync + 'static,
113 {
114 self.event_filter = Box::new(filter);
115 self
116 }
117
118 #[must_use]
123 pub fn event_mapper<F>(mut self, mapper: F) -> Self
124 where
125 F: Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync + 'static,
126 {
127 self.event_mapper = Some(Box::new(mapper));
128 self
129 }
130
131 #[must_use]
138 pub fn span_filter<F>(mut self, filter: F) -> Self
139 where
140 F: Fn(&Metadata) -> bool + Send + Sync + 'static,
141 {
142 self.span_filter = Box::new(filter);
143 self
144 }
145
146 #[must_use]
154 pub fn enable_span_attributes(mut self) -> Self {
155 self.with_span_attributes = true;
156 self
157 }
158}
159
160impl<S> Default for SentryLayer<S>
161where
162 S: Subscriber + for<'a> LookupSpan<'a>,
163{
164 fn default() -> Self {
165 Self {
166 event_filter: Box::new(default_event_filter),
167 event_mapper: None,
168
169 span_filter: Box::new(default_span_filter),
170
171 with_span_attributes: false,
172 }
173 }
174}
175
176#[inline(always)]
177fn record_fields<'a, K: AsRef<str> + Into<Cow<'a, str>>>(
178 span: &TransactionOrSpan,
179 data: BTreeMap<K, Value>,
180) {
181 match span {
182 TransactionOrSpan::Span(span) => {
183 let mut span = span.data();
184 for (key, value) in data {
185 if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
186 match value {
187 Value::Bool(value) => {
188 span.set_tag(stripped_key.to_owned(), value.to_string())
189 }
190 Value::Number(value) => {
191 span.set_tag(stripped_key.to_owned(), value.to_string())
192 }
193 Value::String(value) => span.set_tag(stripped_key.to_owned(), value),
194 _ => span.set_data(key.into().into_owned(), value),
195 }
196 } else {
197 span.set_data(key.into().into_owned(), value);
198 }
199 }
200 }
201 TransactionOrSpan::Transaction(transaction) => {
202 let mut transaction = transaction.data();
203 for (key, value) in data {
204 if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
205 match value {
206 Value::Bool(value) => {
207 transaction.set_tag(stripped_key.into(), value.to_string())
208 }
209 Value::Number(value) => {
210 transaction.set_tag(stripped_key.into(), value.to_string())
211 }
212 Value::String(value) => transaction.set_tag(stripped_key.into(), value),
213 _ => transaction.set_data(key.into(), value),
214 }
215 } else {
216 transaction.set_data(key.into(), value);
217 }
218 }
219 }
220 }
221}
222
223pub(super) struct SentrySpanData {
227 pub(super) sentry_span: TransactionOrSpan,
228 parent_sentry_span: Option<TransactionOrSpan>,
229 hub: Arc<sentry_core::Hub>,
230 hub_switch_guard: Option<sentry_core::HubSwitchGuard>,
231}
232
233impl<S> Layer<S> for SentryLayer<S>
234where
235 S: Subscriber + for<'a> LookupSpan<'a>,
236{
237 fn on_event(&self, event: &Event, ctx: Context<'_, S>) {
238 let items = match &self.event_mapper {
239 Some(mapper) => mapper(event, ctx),
240 None => {
241 let span_ctx = self.with_span_attributes.then_some(ctx);
242 let filter = (self.event_filter)(event.metadata());
243 let mut items = vec![];
244 if filter.contains(EventFilter::Breadcrumb) {
245 items.push(EventMapping::Breadcrumb(breadcrumb_from_event(
246 event,
247 span_ctx.as_ref(),
248 )));
249 }
250 if filter.contains(EventFilter::Event) {
251 items.push(EventMapping::Event(event_from_event(
252 event,
253 span_ctx.as_ref(),
254 )));
255 }
256 #[cfg(feature = "logs")]
257 if filter.contains(EventFilter::Log) {
258 items.push(EventMapping::Log(log_from_event(event, span_ctx.as_ref())));
259 }
260 EventMapping::Combined(CombinedEventMapping(items))
261 }
262 };
263 let items = CombinedEventMapping::from(items);
264
265 for item in items.0 {
266 match item {
267 EventMapping::Ignore => (),
268 EventMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
269 EventMapping::Event(event) => {
270 sentry_core::capture_event(event);
271 }
272 #[cfg(feature = "logs")]
273 EventMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)),
274 EventMapping::Combined(_) => {
275 sentry_core::sentry_debug!(
276 "[SentryLayer] found nested CombinedEventMapping, ignoring"
277 )
278 }
279 }
280 }
281 }
282
283 fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
286 let span = match ctx.span(id) {
287 Some(span) => span,
288 None => return,
289 };
290
291 if !(self.span_filter)(span.metadata()) {
292 return;
293 }
294
295 let (description, data) = extract_span_data(attrs);
296 let op = span.name();
297
298 let description = description.unwrap_or_else(|| {
301 let target = span.metadata().target();
302 if target.is_empty() {
303 op.to_string()
304 } else {
305 format!("{target}::{op}")
306 }
307 });
308
309 let hub = sentry_core::Hub::current();
310 let parent_sentry_span = hub.configure_scope(|scope| scope.get_span());
311
312 let sentry_span: sentry_core::TransactionOrSpan = match &parent_sentry_span {
313 Some(parent) => parent.start_child(op, &description).into(),
314 None => {
315 let ctx = sentry_core::TransactionContext::new(&description, op);
316 sentry_core::start_transaction(ctx).into()
317 }
318 };
319 record_fields(&sentry_span, data);
322
323 let mut extensions = span.extensions_mut();
324 extensions.insert(SentrySpanData {
325 sentry_span,
326 parent_sentry_span,
327 hub,
328 hub_switch_guard: None,
329 });
330 }
331
332 fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
335 let span = match ctx.span(id) {
336 Some(span) => span,
337 None => return,
338 };
339
340 let mut extensions = span.extensions_mut();
341 if let Some(data) = extensions.get_mut::<SentrySpanData>() {
342 data.hub_switch_guard = Some(sentry_core::HubSwitchGuard::new(data.hub.clone()));
343 data.hub.configure_scope(|scope| {
344 scope.set_span(Some(data.sentry_span.clone()));
345 })
346 }
347 }
348
349 fn on_exit(&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 mut extensions = span.extensions_mut();
357 if let Some(data) = extensions.get_mut::<SentrySpanData>() {
358 data.hub.configure_scope(|scope| {
359 scope.set_span(data.parent_sentry_span.clone());
360 });
361 data.hub_switch_guard.take();
362 }
363 }
364
365 fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
368 let span = match ctx.span(&id) {
369 Some(span) => span,
370 None => return,
371 };
372
373 let mut extensions = span.extensions_mut();
374 let SentrySpanData { sentry_span, .. } = match extensions.remove::<SentrySpanData>() {
375 Some(data) => data,
376 None => return,
377 };
378
379 sentry_span.finish();
380 }
381
382 fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
384 let span = match ctx.span(span) {
385 Some(s) => s,
386 _ => return,
387 };
388
389 let mut extensions = span.extensions_mut();
390 let span = match extensions.get_mut::<SentrySpanData>() {
391 Some(t) => &t.sentry_span,
392 _ => return,
393 };
394
395 let mut data = FieldVisitor::default();
396 values.record(&mut data);
397
398 record_fields(span, data.json_values);
399 }
400}
401
402pub fn layer<S>() -> SentryLayer<S>
404where
405 S: Subscriber + for<'a> LookupSpan<'a>,
406{
407 Default::default()
408}
409
410fn extract_span_data(attrs: &span::Attributes) -> (Option<String>, BTreeMap<&'static str, Value>) {
412 let mut json_values = VISITOR_BUFFER.with_borrow_mut(|debug_buffer| {
413 let mut visitor = SpanFieldVisitor {
414 debug_buffer,
415 json_values: Default::default(),
416 };
417 attrs.record(&mut visitor);
418 visitor.json_values
419 });
420
421 let message = json_values.remove("message").and_then(|v| match v {
423 Value::String(s) => Some(s),
424 _ => None,
425 });
426
427 (message, json_values)
428}
429
430thread_local! {
431 static VISITOR_BUFFER: RefCell<String> = const { RefCell::new(String::new()) };
432}
433
434struct SpanFieldVisitor<'s> {
436 debug_buffer: &'s mut String,
437 json_values: BTreeMap<&'static str, Value>,
438}
439
440impl SpanFieldVisitor<'_> {
441 fn record<T: Into<Value>>(&mut self, field: &Field, value: T) {
442 self.json_values.insert(field.name(), value.into());
443 }
444}
445
446impl Visit for SpanFieldVisitor<'_> {
447 fn record_i64(&mut self, field: &Field, value: i64) {
448 self.record(field, value);
449 }
450
451 fn record_u64(&mut self, field: &Field, value: u64) {
452 self.record(field, value);
453 }
454
455 fn record_bool(&mut self, field: &Field, value: bool) {
456 self.record(field, value);
457 }
458
459 fn record_str(&mut self, field: &Field, value: &str) {
460 self.record(field, value);
461 }
462
463 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
464 use std::fmt::Write;
465 self.debug_buffer.reserve(128);
466 write!(self.debug_buffer, "{value:?}").unwrap();
467 self.json_values
468 .insert(field.name(), self.debug_buffer.as_str().into());
469 self.debug_buffer.clear();
470 }
471}