sentry_tracing/layer/mod.rs
1use bitflags::bitflags;
2use sentry_core::Breadcrumb;
3use tracing_core::{span as tracing_span, Event, Level, Metadata, Subscriber};
4use tracing_subscriber::layer::{Context, Layer};
5use tracing_subscriber::registry::LookupSpan;
6
7mod breadcrumb;
8mod error;
9#[cfg(feature = "logs")]
10mod log;
11mod span;
12mod span_guard_stack;
13
14pub use breadcrumb::BreadcrumbLayer;
15pub use error::ErrorLayer;
16#[cfg(feature = "logs")]
17pub use log::LogLayer;
18pub(super) use span::SentrySpanData;
19pub use span::SpanLayer;
20
21/// Separate submodule with expect(deprecated) needed because bitflags! uses the deprecated
22/// EventFilter.
23#[expect(deprecated)]
24mod event_filter {
25 use super::*;
26 bitflags! {
27 /// The action that Sentry should perform for a given [`Event`]
28 #[deprecated = "only used for the legacy combined layer"]
29 #[derive(Debug, Clone, Copy)]
30 pub struct EventFilter: u32 {
31 /// Ignore the [`Event`]
32 const Ignore = 0b000;
33 /// Create a [`Breadcrumb`] from this [`Event`]
34 const Breadcrumb = 0b001;
35 /// Create a [`sentry_core::protocol::Event`] from this [`Event`]
36 const Event = 0b010;
37 /// Create a [`sentry_core::protocol::Log`] from this [`Event`]
38 const Log = 0b100;
39 }
40 }
41}
42
43#[expect(deprecated)]
44pub use event_filter::EventFilter;
45
46/// The type of data Sentry should ingest for an [`Event`].
47#[deprecated = "only used for the legacy combined layer"]
48#[derive(Debug)]
49#[non_exhaustive]
50pub enum EventMapping {
51 /// Ignore the [`Event`]
52 Ignore,
53 /// Adds the [`Breadcrumb`] to the Sentry scope.
54 Breadcrumb(Breadcrumb),
55 /// Captures the [`sentry_core::protocol::Event`] to Sentry.
56 Event(Box<sentry_core::protocol::Event<'static>>),
57 /// Captures the [`sentry_core::protocol::Log`] to Sentry.
58 #[cfg(feature = "logs")]
59 #[cfg_attr(doc_cfg, doc(cfg(feature = "logs")))]
60 Log(sentry_core::protocol::Log),
61 /// Captures multiple items to Sentry.
62 /// Nesting multiple `EventMapping::Combined` inside each other will cause the inner mappings to be ignored.
63 #[expect(deprecated)]
64 Combined(CombinedEventMapping),
65}
66
67/// A list of event mappings.
68#[deprecated = "only used for the legacy combined layer"]
69#[derive(Debug)]
70pub struct CombinedEventMapping(#[expect(deprecated)] Vec<EventMapping>);
71
72#[expect(deprecated)]
73impl From<EventMapping> for CombinedEventMapping {
74 fn from(value: EventMapping) -> Self {
75 match value {
76 EventMapping::Combined(combined) => combined,
77 _ => CombinedEventMapping(vec![value]),
78 }
79 }
80}
81
82#[expect(deprecated)]
83impl From<Vec<EventMapping>> for CombinedEventMapping {
84 fn from(value: Vec<EventMapping>) -> Self {
85 Self(value)
86 }
87}
88
89/// The default event filter for the legacy combined layer.
90///
91/// Captures `ERROR` events as Sentry error events and `WARN` and `INFO` events as breadcrumbs.
92/// With the `logs` feature, these three levels are also captured as logs. `DEBUG` and `TRACE`
93/// events are ignored.
94#[deprecated = "only used for the legacy combined layer"]
95#[expect(deprecated)]
96pub fn default_event_filter(metadata: &Metadata) -> EventFilter {
97 match metadata.level() {
98 #[cfg(feature = "logs")]
99 &Level::ERROR => EventFilter::Event | EventFilter::Log,
100 #[cfg(not(feature = "logs"))]
101 &Level::ERROR => EventFilter::Event,
102 #[cfg(feature = "logs")]
103 &Level::WARN | &Level::INFO => EventFilter::Breadcrumb | EventFilter::Log,
104 #[cfg(not(feature = "logs"))]
105 &Level::WARN | &Level::INFO => EventFilter::Breadcrumb,
106 &Level::DEBUG | &Level::TRACE => EventFilter::Ignore,
107 }
108}
109
110/// The default span filter for the legacy combined layer.
111///
112/// By default, spans at the `error`, `warning`, and `info` levels are captured.
113#[deprecated = "only used for the legacy combined layer"]
114pub fn default_span_filter(metadata: &Metadata) -> bool {
115 matches!(
116 metadata.level(),
117 &Level::ERROR | &Level::WARN | &Level::INFO
118 )
119}
120
121#[expect(deprecated)]
122type EventMapper<S> = Box<dyn Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync>;
123
124/// Legacy combined tracing layer.
125///
126/// For most applications, use [`SpanLayer`] and [`LogLayer`] with explicit filters instead.
127/// Add [`ErrorLayer`] if tracing events should also be captured as Sentry error events, and
128/// [`BreadcrumbLayer`] if errors need breadcrumb context.
129///
130/// Mixing this legacy layer with any individual Sentry layer is unsupported.
131/// **Combining it with [`SpanLayer`] can panic when a span is created.** When migrating,
132/// replace the legacy layer rather than adding individual layers alongside it.
133///
134/// # Migrating the default configuration
135///
136/// The individual layers do not filter by level internally. The following configuration
137/// reproduces the default level selection of `SentryLayer` with the `logs` feature enabled:
138///
139/// - `ERROR`, `WARN`, and `INFO` spans become Sentry transactions and spans.
140/// - `ERROR`, `WARN`, and `INFO` events become structured logs.
141/// - `ERROR` events also become Sentry error events.
142/// - `WARN` and `INFO` events also become breadcrumbs on the current scope, sent with subsequent
143/// captured errors.
144///
145/// ```rust
146/// # #[cfg(feature = "logs")]
147/// # {
148/// use sentry::integrations::tracing::{BreadcrumbLayer, ErrorLayer, LogLayer, SpanLayer};
149/// use tracing::Level;
150/// use tracing_subscriber::filter::{filter_fn, LevelFilter};
151/// use tracing_subscriber::prelude::*;
152///
153/// tracing_subscriber::registry()
154/// .with(SpanLayer::new().with_filter(LevelFilter::INFO))
155/// .with(
156/// BreadcrumbLayer::new().with_filter(filter_fn(|metadata| {
157/// matches!(*metadata.level(), Level::WARN | Level::INFO)
158/// })),
159/// )
160/// .with(ErrorLayer::new().with_filter(LevelFilter::ERROR))
161/// .with(LogLayer::new().with_filter(LevelFilter::INFO))
162/// .init();
163/// # }
164/// ```
165///
166/// Without the `logs` feature, omit the log layer. Client configuration, including trace sampling,
167/// is unchanged.
168///
169/// If the legacy layer used [`enable_span_attributes`](Self::enable_span_attributes), enable
170/// span attributes on each individual event layer that needs them. See the option docs for
171/// [breadcrumbs](BreadcrumbLayer::enable_span_attributes),
172/// [errors](ErrorLayer::enable_span_attributes), and
173/// [logs](LogLayer::enable_span_attributes) for filtering requirements.
174///
175/// # Migrating custom configuration
176///
177/// - Replace [`span_filter`](Self::span_filter) with a filter on the span layer.
178/// - Split [`event_filter`](Self::event_filter) into filters on the individual event layers. An
179/// event can be accepted by multiple layers, replacing combined [`EventFilter`] flags.
180/// - Individual layers do not provide mapper callbacks. Some [`SentryLayer::event_mapper`] usages
181/// may be addressed by other existing APIs, such as filter callbacks on the layers or
182/// `before_send*` callbacks provided by the Sentry SDK. However, we recognize it may not be
183/// possible to migrate all existing usages. If you are unable to migrate to the new API due to
184/// this change, we would love to hear from you. Please [open a GitHub issue] explaining your
185/// use case, and we will work with you to find a solution.
186///
187/// If a custom configuration sends the same event to both the breadcrumb and error layers,
188/// install the breadcrumb layer before the error layer to include that breadcrumb in the error.
189/// The default configuration above excludes `ERROR` events from breadcrumbs, so the order does
190/// not affect their inclusion.
191///
192/// [open a GitHub issue]: https://github.com/getsentry/sentry-rust/issues/new?template=02-improvement.yml
193#[deprecated(
194 note = "Prefer SpanLayer::new() and LogLayer::new(); see SentryLayer docs for migration"
195)]
196pub struct SentryLayer<S> {
197 #[expect(deprecated)]
198 event_filter: Box<dyn Fn(&Metadata) -> EventFilter + Send + Sync>,
199 event_mapper: Option<EventMapper<S>>,
200 span_filter: Box<dyn Fn(&Metadata) -> bool + Send + Sync>,
201 span_layer: SpanLayer,
202 breadcrumb: BreadcrumbLayer,
203 error: ErrorLayer,
204 #[cfg(feature = "logs")]
205 log: LogLayer,
206}
207
208#[expect(deprecated, reason = "implementing the legacy layer")]
209impl<S> SentryLayer<S> {
210 /// Sets a custom event filter function.
211 ///
212 /// The filter classifies how sentry should handle [`Event`]s based
213 /// on their [`Metadata`].
214 #[must_use]
215 pub fn event_filter<F>(mut self, filter: F) -> Self
216 where
217 F: Fn(&Metadata) -> EventFilter + Send + Sync + 'static,
218 {
219 self.event_filter = Box::new(filter);
220 self
221 }
222
223 /// Sets a custom event mapper function.
224 ///
225 /// The mapper is responsible for creating either breadcrumbs or events from
226 /// [`Event`]s.
227 #[must_use]
228 pub fn event_mapper<F>(mut self, mapper: F) -> Self
229 where
230 F: Fn(&Event, Context<'_, S>) -> EventMapping + Send + Sync + 'static,
231 {
232 self.event_mapper = Some(Box::new(mapper));
233 self
234 }
235
236 /// Sets a custom span filter function.
237 ///
238 /// The filter classifies whether sentry should handle [`tracing::Span`]s based
239 /// on their [`Metadata`].
240 ///
241 /// [`tracing::Span`]: https://docs.rs/tracing/latest/tracing/struct.Span.html
242 #[must_use]
243 pub fn span_filter<F>(mut self, filter: F) -> Self
244 where
245 F: Fn(&Metadata) -> bool + Send + Sync + 'static,
246 {
247 self.span_filter = Box::new(filter);
248 self
249 }
250
251 /// Include attributes of parent spans in breadcrumbs, error events, and logs.
252 ///
253 /// Only spans accepted by this layer's span filter provide attributes. Including attributes
254 /// does not require the transaction to be sampled.
255 ///
256 /// When migrating, enable span attributes on each individual event layer that needs them;
257 /// see the [migration guide](SentryLayer#migrating-the-default-configuration).
258 #[must_use]
259 pub fn enable_span_attributes(mut self) -> Self {
260 self.breadcrumb = self.breadcrumb.enable_span_attributes();
261 self.error = self.error.enable_span_attributes();
262 #[cfg(feature = "logs")]
263 {
264 self.log = self.log.enable_span_attributes();
265 }
266 self
267 }
268}
269
270#[expect(deprecated, reason = "implementing the legacy layer")]
271impl<S> Default for SentryLayer<S>
272where
273 S: Subscriber + for<'a> LookupSpan<'a>,
274{
275 fn default() -> Self {
276 Self {
277 event_filter: Box::new(default_event_filter),
278 event_mapper: None,
279 span_filter: Box::new(default_span_filter),
280 span_layer: SpanLayer::new(),
281 breadcrumb: BreadcrumbLayer::new(),
282 error: ErrorLayer::new(),
283 #[cfg(feature = "logs")]
284 log: LogLayer::new(),
285 }
286 }
287}
288
289#[expect(deprecated, reason = "implementing the legacy layer")]
290impl<S> Layer<S> for SentryLayer<S>
291where
292 S: Subscriber + for<'a> LookupSpan<'a>,
293{
294 fn on_event(&self, event: &Event, ctx: Context<'_, S>) {
295 if let Some(mapper) = &self.event_mapper {
296 // A custom mapper overrides the filter and supplies already converted
297 // items in the order they must be captured.
298 let items = CombinedEventMapping::from(mapper(event, ctx));
299 for item in items.0 {
300 match item {
301 EventMapping::Ignore => (),
302 EventMapping::Breadcrumb(breadcrumb) => self.breadcrumb.capture(breadcrumb),
303 EventMapping::Event(event) => self.error.capture(*event),
304 #[cfg(feature = "logs")]
305 EventMapping::Log(log) => self.log.capture(log),
306 EventMapping::Combined(_) => sentry_core::sentry_debug!(
307 "[SentryLayer] found nested CombinedEventMapping, ignoring"
308 ),
309 }
310 }
311 } else {
312 let filter = (self.event_filter)(event.metadata());
313 if filter.contains(EventFilter::Breadcrumb) {
314 self.breadcrumb.capture_tracing_event(event, ctx.clone());
315 }
316 if filter.contains(EventFilter::Event) {
317 self.error.capture_tracing_event(event, ctx.clone());
318 }
319 #[cfg(feature = "logs")]
320 if filter.contains(EventFilter::Log) {
321 self.log.capture_tracing_event(event, ctx);
322 }
323 }
324 }
325
326 fn on_new_span(
327 &self,
328 attrs: &tracing_span::Attributes<'_>,
329 id: &tracing_span::Id,
330 ctx: Context<'_, S>,
331 ) {
332 if (self.span_filter)(attrs.metadata()) {
333 self.span_layer.on_new_span(attrs, id, ctx);
334 }
335 }
336
337 fn on_enter(&self, id: &tracing_span::Id, ctx: Context<'_, S>) {
338 self.span_layer.on_enter(id, ctx);
339 }
340
341 fn on_exit(&self, id: &tracing_span::Id, ctx: Context<'_, S>) {
342 self.span_layer.on_exit(id, ctx);
343 }
344
345 fn on_close(&self, id: tracing_span::Id, ctx: Context<'_, S>) {
346 self.span_layer.on_close(id, ctx);
347 }
348
349 fn on_record(
350 &self,
351 id: &tracing_span::Id,
352 values: &tracing_span::Record<'_>,
353 ctx: Context<'_, S>,
354 ) {
355 self.span_layer.on_record(id, values, ctx);
356 }
357}
358
359/// Creates a legacy combined Sentry layer.
360///
361/// For most applications, use [`SpanLayer`] and [`LogLayer`] with explicit filters instead.
362/// See [`SentryLayer`] for a migration example that preserves the legacy default level selection
363/// using all four individual layers, and for differences when migrating custom configuration.
364#[deprecated(
365 note = "Prefer SpanLayer::new() and LogLayer::new(); see SentryLayer docs for migration"
366)]
367#[expect(deprecated, reason = "returning the legacy layer")]
368pub fn layer<S>() -> SentryLayer<S>
369where
370 S: Subscriber + for<'a> LookupSpan<'a>,
371{
372 Default::default()
373}