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 #[cfg(feature = "logs")]
27 Log,
28}
29
30#[derive(Debug)]
32#[allow(clippy::large_enum_variant)]
33pub enum EventMapping {
34 Ignore,
36 Breadcrumb(Breadcrumb),
38 Event(sentry_core::protocol::Event<'static>),
40 #[cfg(feature = "logs")]
42 Log(sentry_core::protocol::Log),
43}
44
45pub fn default_event_filter(metadata: &Metadata) -> EventFilter {
50 match metadata.level() {
51 &Level::ERROR => EventFilter::Event,
52 &Level::WARN | &Level::INFO => EventFilter::Breadcrumb,
53 &Level::DEBUG | &Level::TRACE => EventFilter::Ignore,
54 }
55}
56
57pub fn default_span_filter(metadata: &Metadata) -> bool {
62 matches!(
63 metadata.level(),
64 &Level::ERROR | &Level::WARN | &Level::INFO
65 )
66}
67
68type EventMapper<S> = Box<dyn Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync>;
69
70pub struct SentryLayer<S> {
72 event_filter: Box<dyn Fn(&Metadata) -> EventFilter + Send + Sync>,
73 event_mapper: Option<EventMapper<S>>,
74
75 span_filter: Box<dyn Fn(&Metadata) -> bool + Send + Sync>,
76
77 with_span_attributes: bool,
78}
79
80impl<S> SentryLayer<S> {
81 #[must_use]
86 pub fn event_filter<F>(mut self, filter: F) -> Self
87 where
88 F: Fn(&Metadata) -> EventFilter + Send + Sync + 'static,
89 {
90 self.event_filter = Box::new(filter);
91 self
92 }
93
94 #[must_use]
99 pub fn event_mapper<F>(mut self, mapper: F) -> Self
100 where
101 F: Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync + 'static,
102 {
103 self.event_mapper = Some(Box::new(mapper));
104 self
105 }
106
107 #[must_use]
114 pub fn span_filter<F>(mut self, filter: F) -> Self
115 where
116 F: Fn(&Metadata) -> bool + Send + Sync + 'static,
117 {
118 self.span_filter = Box::new(filter);
119 self
120 }
121
122 #[must_use]
130 pub fn enable_span_attributes(mut self) -> Self {
131 self.with_span_attributes = true;
132 self
133 }
134}
135
136impl<S> Default for SentryLayer<S>
137where
138 S: Subscriber + for<'a> LookupSpan<'a>,
139{
140 fn default() -> Self {
141 Self {
142 event_filter: Box::new(default_event_filter),
143 event_mapper: None,
144
145 span_filter: Box::new(default_span_filter),
146
147 with_span_attributes: false,
148 }
149 }
150}
151
152#[inline(always)]
153fn record_fields<'a, K: AsRef<str> + Into<Cow<'a, str>>>(
154 span: &TransactionOrSpan,
155 data: BTreeMap<K, Value>,
156) {
157 match span {
158 TransactionOrSpan::Span(span) => {
159 let mut span = span.data();
160 for (key, value) in data {
161 if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
162 match value {
163 Value::Bool(value) => {
164 span.set_tag(stripped_key.to_owned(), value.to_string())
165 }
166 Value::Number(value) => {
167 span.set_tag(stripped_key.to_owned(), value.to_string())
168 }
169 Value::String(value) => span.set_tag(stripped_key.to_owned(), value),
170 _ => span.set_data(key.into().into_owned(), value),
171 }
172 } else {
173 span.set_data(key.into().into_owned(), value);
174 }
175 }
176 }
177 TransactionOrSpan::Transaction(transaction) => {
178 let mut transaction = transaction.data();
179 for (key, value) in data {
180 if let Some(stripped_key) = key.as_ref().strip_prefix(TAGS_PREFIX) {
181 match value {
182 Value::Bool(value) => {
183 transaction.set_tag(stripped_key.into(), value.to_string())
184 }
185 Value::Number(value) => {
186 transaction.set_tag(stripped_key.into(), value.to_string())
187 }
188 Value::String(value) => transaction.set_tag(stripped_key.into(), value),
189 _ => transaction.set_data(key.into(), value),
190 }
191 } else {
192 transaction.set_data(key.into(), value);
193 }
194 }
195 }
196 }
197}
198
199pub(super) struct SentrySpanData {
203 pub(super) sentry_span: TransactionOrSpan,
204 parent_sentry_span: Option<TransactionOrSpan>,
205 hub: Arc<sentry_core::Hub>,
206 hub_switch_guard: Option<sentry_core::HubSwitchGuard>,
207}
208
209impl<S> Layer<S> for SentryLayer<S>
210where
211 S: Subscriber + for<'a> LookupSpan<'a>,
212{
213 fn on_event(&self, event: &Event, ctx: Context<'_, S>) {
214 let item = match &self.event_mapper {
215 Some(mapper) => mapper(event, ctx),
216 None => {
217 let span_ctx = self.with_span_attributes.then_some(ctx);
218 match (self.event_filter)(event.metadata()) {
219 EventFilter::Ignore => EventMapping::Ignore,
220 EventFilter::Breadcrumb => {
221 EventMapping::Breadcrumb(breadcrumb_from_event(event, span_ctx))
222 }
223 EventFilter::Event => EventMapping::Event(event_from_event(event, span_ctx)),
224 #[cfg(feature = "logs")]
225 EventFilter::Log => EventMapping::Log(log_from_event(event, span_ctx)),
226 }
227 }
228 };
229
230 match item {
231 EventMapping::Event(event) => {
232 sentry_core::capture_event(event);
233 }
234 EventMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
235 #[cfg(feature = "logs")]
236 EventMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)),
237 _ => (),
238 }
239 }
240
241 fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
244 let span = match ctx.span(id) {
245 Some(span) => span,
246 None => return,
247 };
248
249 if !(self.span_filter)(span.metadata()) {
250 return;
251 }
252
253 let (description, data) = extract_span_data(attrs);
254 let op = span.name();
255
256 let description = description.unwrap_or_else(|| {
259 let target = span.metadata().target();
260 if target.is_empty() {
261 op.to_string()
262 } else {
263 format!("{target}::{op}")
264 }
265 });
266
267 let hub = sentry_core::Hub::current();
268 let parent_sentry_span = hub.configure_scope(|scope| scope.get_span());
269
270 let sentry_span: sentry_core::TransactionOrSpan = match &parent_sentry_span {
271 Some(parent) => parent.start_child(op, &description).into(),
272 None => {
273 let ctx = sentry_core::TransactionContext::new(&description, op);
274 sentry_core::start_transaction(ctx).into()
275 }
276 };
277 record_fields(&sentry_span, data);
280
281 let mut extensions = span.extensions_mut();
282 extensions.insert(SentrySpanData {
283 sentry_span,
284 parent_sentry_span,
285 hub,
286 hub_switch_guard: None,
287 });
288 }
289
290 fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
293 let span = match ctx.span(id) {
294 Some(span) => span,
295 None => return,
296 };
297
298 let mut extensions = span.extensions_mut();
299 if let Some(data) = extensions.get_mut::<SentrySpanData>() {
300 data.hub_switch_guard = Some(sentry_core::HubSwitchGuard::new(data.hub.clone()));
301 data.hub.configure_scope(|scope| {
302 scope.set_span(Some(data.sentry_span.clone()));
303 })
304 }
305 }
306
307 fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
309 let span = match ctx.span(id) {
310 Some(span) => span,
311 None => return,
312 };
313
314 let mut extensions = span.extensions_mut();
315 if let Some(data) = extensions.get_mut::<SentrySpanData>() {
316 data.hub.configure_scope(|scope| {
317 scope.set_span(data.parent_sentry_span.clone());
318 });
319 data.hub_switch_guard.take();
320 }
321 }
322
323 fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
326 let span = match ctx.span(&id) {
327 Some(span) => span,
328 None => return,
329 };
330
331 let mut extensions = span.extensions_mut();
332 let SentrySpanData { sentry_span, .. } = match extensions.remove::<SentrySpanData>() {
333 Some(data) => data,
334 None => return,
335 };
336
337 sentry_span.finish();
338 }
339
340 fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
342 let span = match ctx.span(span) {
343 Some(s) => s,
344 _ => return,
345 };
346
347 let mut extensions = span.extensions_mut();
348 let span = match extensions.get_mut::<SentrySpanData>() {
349 Some(t) => &t.sentry_span,
350 _ => return,
351 };
352
353 let mut data = FieldVisitor::default();
354 values.record(&mut data);
355
356 record_fields(span, data.json_values);
357 }
358}
359
360pub fn layer<S>() -> SentryLayer<S>
362where
363 S: Subscriber + for<'a> LookupSpan<'a>,
364{
365 Default::default()
366}
367
368fn extract_span_data(attrs: &span::Attributes) -> (Option<String>, BTreeMap<&'static str, Value>) {
370 let mut json_values = VISITOR_BUFFER.with_borrow_mut(|debug_buffer| {
371 let mut visitor = SpanFieldVisitor {
372 debug_buffer,
373 json_values: Default::default(),
374 };
375 attrs.record(&mut visitor);
376 visitor.json_values
377 });
378
379 let message = json_values.remove("message").and_then(|v| match v {
381 Value::String(s) => Some(s),
382 _ => None,
383 });
384
385 (message, json_values)
386}
387
388thread_local! {
389 static VISITOR_BUFFER: RefCell<String> = const { RefCell::new(String::new()) };
390}
391
392struct SpanFieldVisitor<'s> {
394 debug_buffer: &'s mut String,
395 json_values: BTreeMap<&'static str, Value>,
396}
397
398impl SpanFieldVisitor<'_> {
399 fn record<T: Into<Value>>(&mut self, field: &Field, value: T) {
400 self.json_values.insert(field.name(), value.into());
401 }
402}
403
404impl Visit for SpanFieldVisitor<'_> {
405 fn record_i64(&mut self, field: &Field, value: i64) {
406 self.record(field, value);
407 }
408
409 fn record_u64(&mut self, field: &Field, value: u64) {
410 self.record(field, value);
411 }
412
413 fn record_bool(&mut self, field: &Field, value: bool) {
414 self.record(field, value);
415 }
416
417 fn record_str(&mut self, field: &Field, value: &str) {
418 self.record(field, value);
419 }
420
421 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
422 use std::fmt::Write;
423 self.debug_buffer.reserve(128);
424 write!(self.debug_buffer, "{value:?}").unwrap();
425 self.json_values
426 .insert(field.name(), self.debug_buffer.as_str().into());
427 self.debug_buffer.clear();
428 }
429}