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#[non_exhaustive]
21pub enum RecordMapping {
22 Ignore,
24 Breadcrumb(Breadcrumb),
26 Event(Box<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 #[expect(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 => {
115 RecordMapping::Event(event_from_record(record, values).into())
116 }
117 LevelFilter::Exception => {
118 RecordMapping::Event(exception_from_record(record, values).into())
119 }
120 },
121 };
122
123 match item {
124 RecordMapping::Ignore => {}
125 RecordMapping::Breadcrumb(b) => sentry_core::add_breadcrumb(b),
126 RecordMapping::Event(e) => {
127 sentry_core::capture_event(*e);
128 }
129 }
130
131 self.drain.log(record, values)
132 }
133
134 fn is_enabled(&self, level: slog::Level) -> bool {
135 self.drain.is_enabled(level) || !matches!((self.filter)(level), LevelFilter::Ignore)
136 }
137}