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