sentry_slog/
drain.rs

1use sentry_core::protocol::{Breadcrumb, Event};
2use slog::{Drain, OwnedKVList, Record};
3
4use crate::{breadcrumb_from_record, event_from_record, exception_from_record};
5
6/// The action that Sentry should perform for a [`slog::Level`].
7#[derive(Debug)]
8pub enum LevelFilter {
9    /// Ignore the [`Record`].
10    Ignore,
11    /// Create a [`Breadcrumb`] from this [`Record`].
12    Breadcrumb,
13    /// Create a message [`Event`] from this [`Record`].
14    Event,
15    /// Create an exception [`Event`] from this [`Record`].
16    Exception,
17}
18
19/// The type of Data Sentry should ingest for a [`slog::Record`].
20#[allow(clippy::large_enum_variant)]
21pub enum RecordMapping {
22    /// Ignore the [`Record`].
23    Ignore,
24    /// Adds the [`Breadcrumb`] to the Sentry scope.
25    Breadcrumb(Breadcrumb),
26    /// Captures the [`Event`] to Sentry.
27    Event(Event<'static>),
28}
29
30/// The default slog filter.
31///
32/// By default, an exception event is captured for `critical` logs,
33/// a regular event for `error` and `warning` logs and a breadcrumb for `info`,
34/// `debug` and `trace`.
35pub fn default_filter(level: slog::Level) -> LevelFilter {
36    match level {
37        slog::Level::Critical => LevelFilter::Exception,
38        slog::Level::Error | slog::Level::Warning => LevelFilter::Event,
39        slog::Level::Info | slog::Level::Debug | slog::Level::Trace => LevelFilter::Breadcrumb,
40    }
41}
42
43/// A Drain which passes all [`Record`]s to Sentry.
44pub struct SentryDrain<D: Drain> {
45    drain: D,
46    filter: Box<dyn Fn(slog::Level) -> LevelFilter + Send + Sync>,
47    #[allow(clippy::type_complexity)]
48    mapper: Option<Box<dyn Fn(&Record, &OwnedKVList) -> RecordMapping + Send + Sync>>,
49}
50
51impl<D: slog::SendSyncRefUnwindSafeDrain> std::panic::RefUnwindSafe for SentryDrain<D> {}
52impl<D: slog::SendSyncUnwindSafeDrain> std::panic::UnwindSafe for SentryDrain<D> {}
53
54impl<D: Drain> SentryDrain<D> {
55    /// Creates a new `SentryDrain`, wrapping a `slog::Drain`.
56    pub fn new(drain: D) -> Self {
57        Self {
58            drain,
59            filter: Box::new(default_filter),
60            mapper: None,
61        }
62    }
63
64    /// Sets a custom filter function.
65    ///
66    /// The filter classifies how sentry should handle [`Record`]s based on
67    /// their [`slog::Level`].
68    #[must_use]
69    pub fn filter<F>(mut self, filter: F) -> Self
70    where
71        F: Fn(slog::Level) -> LevelFilter + Send + Sync + 'static,
72    {
73        self.filter = Box::new(filter);
74        self
75    }
76
77    /// Sets a custom mapper function.
78    ///
79    /// The mapper is responsible for creating either breadcrumbs or events
80    /// from [`Record`]s.
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// use sentry_slog::{breadcrumb_from_record, RecordMapping, SentryDrain};
86    ///
87    /// let drain = SentryDrain::new(slog::Discard).mapper(|record, kv| match record.level() {
88    ///     slog::Level::Trace => RecordMapping::Ignore,
89    ///     _ => RecordMapping::Breadcrumb(breadcrumb_from_record(record, kv)),
90    /// });
91    /// ```
92    #[must_use]
93    pub fn mapper<M>(mut self, mapper: M) -> Self
94    where
95        M: Fn(&Record, &OwnedKVList) -> RecordMapping + Send + Sync + 'static,
96    {
97        self.mapper = Some(Box::new(mapper));
98        self
99    }
100}
101
102impl<D: Drain> slog::Drain for SentryDrain<D> {
103    type Ok = D::Ok;
104    type Err = D::Err;
105
106    fn log(&self, record: &Record, values: &OwnedKVList) -> Result<Self::Ok, Self::Err> {
107        let item: RecordMapping = match &self.mapper {
108            Some(mapper) => mapper(record, values),
109            None => match (self.filter)(record.level()) {
110                LevelFilter::Ignore => RecordMapping::Ignore,
111                LevelFilter::Breadcrumb => {
112                    RecordMapping::Breadcrumb(breadcrumb_from_record(record, values))
113                }
114                LevelFilter::Event => RecordMapping::Event(event_from_record(record, values)),
115                LevelFilter::Exception => {
116                    RecordMapping::Event(exception_from_record(record, values))
117                }
118            },
119        };
120
121        match item {
122            RecordMapping::Ignore => {}
123            RecordMapping::Breadcrumb(b) => sentry_core::add_breadcrumb(b),
124            RecordMapping::Event(e) => {
125                sentry_core::capture_event(e);
126            }
127        }
128
129        self.drain.log(record, values)
130    }
131
132    fn is_enabled(&self, level: slog::Level) -> bool {
133        self.drain.is_enabled(level) || !matches!((self.filter)(level), LevelFilter::Ignore)
134    }
135}