Skip to main content

sentry_tracing/layer/
log.rs

1use sentry_core::protocol::Log;
2use tracing_core::{Event, Subscriber};
3use tracing_subscriber::layer::{Context, Layer};
4use tracing_subscriber::registry::LookupSpan;
5
6use crate::converters::log_from_event_impl;
7
8/// Captures tracing events as Sentry logs.
9///
10/// Captures all event levels unless a filter is configured; see [`LogLayer::new`] for the recommended
11/// level filter.
12///
13/// Logs are sent by default, unless disabled.
14///
15/// Using this layer alongside the legacy [`SentryLayer`](super::SentryLayer) is unsupported.
16/// Use either the legacy layer or the individual layers, not both.
17#[cfg_attr(doc_cfg, doc(cfg(feature = "logs")))]
18#[must_use]
19#[derive(Default)]
20pub struct LogLayer {
21    with_span_attributes: bool,
22}
23
24impl LogLayer {
25    /// Creates a layer that captures Sentry logs from tracing events.
26    ///
27    /// # Filtering
28    ///
29    /// Without a filter, this layer captures logs at every level. Configure a level filter to limit
30    /// log volume. We recommend capturing logs at the `INFO` level, and more severe.
31    ///
32    /// ```rust
33    /// # use tracing_subscriber::prelude::*;
34    /// use sentry::integrations::tracing::LogLayer;
35    /// use tracing_subscriber::filter::LevelFilter;
36    ///
37    /// tracing_subscriber::registry()
38    ///     .with(LogLayer::new().with_filter(LevelFilter::INFO))
39    ///     .init();
40    ///
41    /// // This will be captured ...
42    /// tracing::info!("INFO log");
43    ///
44    /// // ... but this will not be, due to LevelFilter::INFO being set.
45    /// tracing::debug!("DEBUG log");
46    /// ```
47    #[cfg_attr(doc_cfg, doc(cfg(feature = "logs")))]
48    pub fn new() -> Self {
49        Self::default()
50    }
51
52    /// Include attributes of parent spans in logs.
53    ///
54    /// We can only include spans captured by a [`super::SpanLayer`] on the same subscriber. So,
55    /// this option only has any effect when the `SpanLayer` is installed.
56    ///
57    /// # Filtering
58    ///
59    /// Parent-span attributes are included only when the span is accepted by both `SpanLayer`
60    /// and `LogLayer`.
61    ///
62    /// If `LogLayer` uses a stricter level filter, attributes from less severe spans are omitted.
63    /// For example, this captures logs at `WARN` and more severe levels while allowing attributes
64    /// from `INFO` parent spans to appear on those logs:
65    ///
66    /// ```rust
67    /// use sentry::integrations::tracing::{LogLayer, SpanLayer};
68    /// use tracing_subscriber::prelude::*;
69    /// use tracing_subscriber::filter::{filter_fn, FilterExt as _, LevelFilter};
70    ///
71    /// tracing_subscriber::registry()
72    ///     .with(SpanLayer::new().with_filter(LevelFilter::INFO))
73    ///     .with(
74    ///         LogLayer::new()
75    ///             .enable_span_attributes()
76    ///             .with_filter(LevelFilter::WARN.or(filter_fn(|m| m.is_span()))),
77    ///     )
78    ///     .init();
79    /// ```
80    ///
81    /// No filter adjustment is needed when both layers use the same level filter, such as the
82    /// `INFO` level recommended for most applications.
83    pub fn enable_span_attributes(mut self) -> Self {
84        self.with_span_attributes = true;
85        self
86    }
87
88    /// Capture a log on the current `Hub`.
89    pub(super) fn capture(&self, log: Log) {
90        sentry_core::Hub::with_active(|hub| {
91            let enabled = hub.client().is_none_or(|client| {
92                let options = client.options();
93                #[expect(deprecated, reason = "checking a deprecated field")]
94                options.enable_logs
95            });
96            if enabled {
97                hub.capture_log(log);
98            }
99        });
100    }
101}
102
103impl LogLayer {
104    /// Capture a tracing event as a log.
105    pub(super) fn capture_tracing_event<S>(&self, event: &Event, ctx: Context<'_, S>)
106    where
107        S: Subscriber + for<'a> LookupSpan<'a>,
108    {
109        self.capture(log_from_event_impl(
110            event,
111            self.with_span_attributes.then_some(&ctx),
112        ));
113    }
114}
115
116impl<S> Layer<S> for LogLayer
117where
118    S: Subscriber + for<'a> LookupSpan<'a>,
119{
120    fn on_event(&self, event: &Event, ctx: Context<'_, S>) {
121        self.capture_tracing_event(event, ctx);
122    }
123}