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}