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#[derive(Debug)]
8pub enum LevelFilter {
9    Ignore,
11    Breadcrumb,
13    Event,
15    Exception,
17}
18
19#[allow(clippy::large_enum_variant)]
21pub enum RecordMapping {
22    Ignore,
24    Breadcrumb(Breadcrumb),
26    Event(Event<'static>),
28}
29
30pub 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
43pub 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    pub fn new(drain: D) -> Self {
57        Self {
58            drain,
59            filter: Box::new(default_filter),
60            mapper: None,
61        }
62    }
63
64    #[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    #[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}