Skip to main content

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}