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}