1use std::borrow::Cow;
2use std::cell::RefCell;
3use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use sentry_core::protocol::Value;
7use sentry_core::{Breadcrumb, TransactionOrSpan};
8use tracing_core::field::Visit;
9use tracing_core::{span, Event, Field, Level, Metadata, Subscriber};
10use tracing_subscriber::layer::{Context, Layer};
11use tracing_subscriber::registry::LookupSpan;
12
13use crate::converters::*;
14use crate::TAGS_PREFIX;
15
16#[derive(Debug, Clone, Copy)]
18pub enum EventFilter {
19 Ignore,
21 Breadcrumb,
23 Event,
25 Exception,
27}
28
29#[derive(Debug)]
31#[allow(clippy::large_enum_variant)]
32pub enum EventMapping {
33 Ignore,
35 Breadcrumb(Breadcrumb),
37 Event(sentry_core::protocol::Event<'static>),
39}
40
41pub fn default_event_filter(metadata: &Metadata) -> EventFilter {
46 match metadata.level() {
47 &Level::ERROR => EventFilter::Exception,
48 &Level::WARN | &Level::INFO => EventFilter::Breadcrumb,
49 &Level::DEBUG | &Level::TRACE => EventFilter::Ignore,
50 }
51}
52
53pub fn default_span_filter(metadata: &Metadata) -> bool {
58 matches!(
59 metadata.level(),
60 &Level::ERROR | &Level::WARN | &Level::INFO
61 )
62}
63
64type EventMapper<S> = Box<dyn Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync>;
65
66pub struct SentryLayer<S> {
68 event_filter: Box<dyn Fn(&Metadata) -> EventFilter + Send + Sync>,
69 event_mapper: Option<EventMapper<S>>,
70
71 span_filter: Box<dyn Fn(&Metadata) -> bool + Send + Sync>,
72
73 with_span_attributes: bool,
74}
75
76impl<S> SentryLayer<S> {
77 #[must_use]
82 pub fn event_filter<F>(mut self, filter: F) -> Self
83 where
84 F: Fn(&Metadata) -> EventFilter + Send + Sync + 'static,
85 {
86 self.event_filter = Box::new(filter);
87 self
88 }
89
90 #[must_use]
95 pub fn event_mapper<F>(mut self, mapper: F) -> Self
96 where
97 F: Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync + 'static,
98 {
99 self.event_mapper = Some(Box::new(mapper));
100 self
101 }
102
103 #[must_use]
110 pub fn span_filter<F>(mut self, filter: F) -> Self
111 where
112 F: Fn(&Metadata) -> bool + Send + Sync + 'static,
113 {
114 self.span_filter = Box::new(filter);
115 self
116 }
117
118 #[must_use]
126 pub fn enable_span_attributes(mut self) -> Self {
127 self.with_span_attributes = true;
128 self
129 }
130}
131
132impl<S> Default for SentryLayer<S>
133where
134 S: Subscriber + for<'a> LookupSpan<'a>,
135{
136 fn default() -> Self {
137 Self {
138 event_filter: Box::new(default_event_filter),
139 event_mapper: None,
140
141 span_filter: Box::new(default_span_filter),
142
143 with_span_attributes: false,
144 }
145 }
146}
147
148#[inline(always)]
149fn record_fields<'a, K: AsRef<str> + Into<Cow<'a, str>>>(
150 span: &TransactionOrSpan,
151 data: BTreeMap<K, Value>,
152) {
153 match span {
154 TransactionOrSpan::Span(span) => {
155 let mut span = span.data();
156 for (key, value) in data {
157 if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
158 match value {
159 Value::Bool(value) => {
160 span.set_tag(stripped_key.to_owned(), value.to_string())
161 }
162 Value::Number(value) => {
163 span.set_tag(stripped_key.to_owned(), value.to_string())
164 }
165 Value::String(value) => span.set_tag(stripped_key.to_owned(), value),
166 _ => span.set_data(key.into().into_owned(), value),
167 }
168 } else {
169 span.set_data(key.into().into_owned(), value);
170 }
171 }
172 }
173 TransactionOrSpan::Transaction(transaction) => {
174 let mut transaction = transaction.data();
175 for (key, value) in data {
176 if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
177 match value {
178 Value::Bool(value) => {
179 transaction.set_tag(stripped_key.into(), value.to_string())
180 }
181 Value::Number(value) => {
182 transaction.set_tag(stripped_key.into(), value.to_string())
183 }
184 Value::String(value) => transaction.set_tag(stripped_key.into(), value),
185 _ => transaction.set_data(key.into(), value),
186 }
187 } else {
188 transaction.set_data(key.into(), value);
189 }
190 }
191 }
192 }
193}
194
195pub(super) struct SentrySpanData {
199 pub(super) sentry_span: TransactionOrSpan,
200 parent_sentry_span: Option<TransactionOrSpan>,
201 hub: Arc<sentry_core::Hub>,
202 hub_switch_guard: Option<sentry_core::HubSwitchGuard>,
203}
204
205impl<S> Layer<S> for SentryLayer<S>
206where
207 S: Subscriber + for<'a> LookupSpan<'a>,
208{
209 fn on_event(&self, event: &Event, ctx: Context<'_, S>) {
210 let item = match &self.event_mapper {
211 Some(mapper) => mapper(event, ctx),
212 None => {
213 let span_ctx = self.with_span_attributes.then_some(ctx);
214 match (self.event_filter)(event.metadata()) {
215 EventFilter::Ignore => EventMapping::Ignore,
216 EventFilter::Breadcrumb => {
217 EventMapping::Breadcrumb(breadcrumb_from_event(event, span_ctx))
218 }
219 EventFilter::Event => EventMapping::Event(event_from_event(event, span_ctx)),
220 EventFilter::Exception => {
221 EventMapping::Event(exception_from_event(event, span_ctx))
222 }
223 }
224 }
225 };
226
227 match item {
228 EventMapping::Event(event) => {
229 sentry_core::capture_event(event);
230 }
231 EventMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
232 _ => (),
233 }
234 }
235
236 fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
239 let span = match ctx.span(id) {
240 Some(span) => span,
241 None => return,
242 };
243
244 if !(self.span_filter)(span.metadata()) {
245 return;
246 }
247
248 let (description, data) = extract_span_data(attrs);
249 let op = span.name();
250
251 let description = description.unwrap_or_else(|| {
254 let target = span.metadata().target();
255 if target.is_empty() {
256 op.to_string()
257 } else {
258 format!("{target}::{op}")
259 }
260 });
261
262 let hub = sentry_core::Hub::current();
263 let parent_sentry_span = hub.configure_scope(|scope| scope.get_span());
264
265 let sentry_span: sentry_core::TransactionOrSpan = match &parent_sentry_span {
266 Some(parent) => parent.start_child(op, &description).into(),
267 None => {
268 let ctx = sentry_core::TransactionContext::new(&description, op);
269 sentry_core::start_transaction(ctx).into()
270 }
271 };
272 record_fields(&sentry_span, data);
275
276 let mut extensions = span.extensions_mut();
277 extensions.insert(SentrySpanData {
278 sentry_span,
279 parent_sentry_span,
280 hub,
281 hub_switch_guard: None,
282 });
283 }
284
285 fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
288 let span = match ctx.span(id) {
289 Some(span) => span,
290 None => return,
291 };
292
293 let mut extensions = span.extensions_mut();
294 if let Some(data) = extensions.get_mut::<SentrySpanData>() {
295 data.hub_switch_guard = Some(sentry_core::HubSwitchGuard::new(data.hub.clone()));
296 data.hub.configure_scope(|scope| {
297 scope.set_span(Some(data.sentry_span.clone()));
298 })
299 }
300 }
301
302 fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
304 let span = match ctx.span(id) {
305 Some(span) => span,
306 None => return,
307 };
308
309 let mut extensions = span.extensions_mut();
310 if let Some(data) = extensions.get_mut::<SentrySpanData>() {
311 data.hub.configure_scope(|scope| {
312 scope.set_span(data.parent_sentry_span.clone());
313 });
314 data.hub_switch_guard.take();
315 }
316 }
317
318 fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
321 let span = match ctx.span(&id) {
322 Some(span) => span,
323 None => return,
324 };
325
326 let mut extensions = span.extensions_mut();
327 let SentrySpanData { sentry_span, .. } = match extensions.remove::<SentrySpanData>() {
328 Some(data) => data,
329 None => return,
330 };
331
332 sentry_span.finish();
333 }
334
335 fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
337 let span = match ctx.span(span) {
338 Some(s) => s,
339 _ => return,
340 };
341
342 let mut extensions = span.extensions_mut();
343 let span = match extensions.get_mut::<SentrySpanData>() {
344 Some(t) => &t.sentry_span,
345 _ => return,
346 };
347
348 let mut data = FieldVisitor::default();
349 values.record(&mut data);
350
351 record_fields(span, data.json_values);
352 }
353}
354
355pub fn layer<S>() -> SentryLayer<S>
357where
358 S: Subscriber + for<'a> LookupSpan<'a>,
359{
360 Default::default()
361}
362
363fn extract_span_data(attrs: &span::Attributes) -> (Option<String>, BTreeMap<&'static str, Value>) {
365 let mut json_values = VISITOR_BUFFER.with_borrow_mut(|debug_buffer| {
366 let mut visitor = SpanFieldVisitor {
367 debug_buffer,
368 json_values: Default::default(),
369 };
370 attrs.record(&mut visitor);
371 visitor.json_values
372 });
373
374 let message = json_values.remove("message").and_then(|v| match v {
376 Value::String(s) => Some(s),
377 _ => None,
378 });
379
380 (message, json_values)
381}
382
383thread_local! {
384 static VISITOR_BUFFER: RefCell<String> = const { RefCell::new(String::new()) };
385}
386
387struct SpanFieldVisitor<'s> {
389 debug_buffer: &'s mut String,
390 json_values: BTreeMap<&'static str, Value>,
391}
392
393impl SpanFieldVisitor<'_> {
394 fn record<T: Into<Value>>(&mut self, field: &Field, value: T) {
395 self.json_values.insert(field.name(), value.into());
396 }
397}
398
399impl Visit for SpanFieldVisitor<'_> {
400 fn record_i64(&mut self, field: &Field, value: i64) {
401 self.record(field, value);
402 }
403
404 fn record_u64(&mut self, field: &Field, value: u64) {
405 self.record(field, value);
406 }
407
408 fn record_bool(&mut self, field: &Field, value: bool) {
409 self.record(field, value);
410 }
411
412 fn record_str(&mut self, field: &Field, value: &str) {
413 self.record(field, value);
414 }
415
416 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
417 use std::fmt::Write;
418 self.debug_buffer.reserve(128);
419 write!(self.debug_buffer, "{value:?}").unwrap();
420 self.json_values
421 .insert(field.name(), self.debug_buffer.as_str().into());
422 self.debug_buffer.clear();
423 }
424}