use sentry_core::protocol::{Breadcrumb, Event};
use slog::{Drain, OwnedKVList, Record};
use crate::{breadcrumb_from_record, event_from_record, exception_from_record};
#[derive(Debug)]
pub enum LevelFilter {
Ignore,
Breadcrumb,
Event,
Exception,
}
#[allow(clippy::large_enum_variant)]
pub enum RecordMapping {
Ignore,
Breadcrumb(Breadcrumb),
Event(Event<'static>),
}
pub fn default_filter(level: slog::Level) -> LevelFilter {
match level {
slog::Level::Critical => LevelFilter::Exception,
slog::Level::Error | slog::Level::Warning => LevelFilter::Event,
slog::Level::Info | slog::Level::Debug | slog::Level::Trace => LevelFilter::Breadcrumb,
}
}
pub struct SentryDrain<D: Drain> {
drain: D,
filter: Box<dyn Fn(slog::Level) -> LevelFilter + Send + Sync>,
#[allow(clippy::type_complexity)]
mapper: Option<Box<dyn Fn(&Record, &OwnedKVList) -> RecordMapping + Send + Sync>>,
}
impl<D: slog::SendSyncRefUnwindSafeDrain> std::panic::RefUnwindSafe for SentryDrain<D> {}
impl<D: slog::SendSyncUnwindSafeDrain> std::panic::UnwindSafe for SentryDrain<D> {}
impl<D: Drain> SentryDrain<D> {
pub fn new(drain: D) -> Self {
Self {
drain,
filter: Box::new(default_filter),
mapper: None,
}
}
#[must_use]
pub fn filter<F>(mut self, filter: F) -> Self
where
F: Fn(slog::Level) -> LevelFilter + Send + Sync + 'static,
{
self.filter = Box::new(filter);
self
}
#[must_use]
pub fn mapper<M>(mut self, mapper: M) -> Self
where
M: Fn(&Record, &OwnedKVList) -> RecordMapping + Send + Sync + 'static,
{
self.mapper = Some(Box::new(mapper));
self
}
}
impl<D: Drain> slog::Drain for SentryDrain<D> {
type Ok = D::Ok;
type Err = D::Err;
fn log(&self, record: &Record, values: &OwnedKVList) -> Result<Self::Ok, Self::Err> {
let item: RecordMapping = match &self.mapper {
Some(mapper) => mapper(record, values),
None => match (self.filter)(record.level()) {
LevelFilter::Ignore => RecordMapping::Ignore,
LevelFilter::Breadcrumb => {
RecordMapping::Breadcrumb(breadcrumb_from_record(record, values))
}
LevelFilter::Event => RecordMapping::Event(event_from_record(record, values)),
LevelFilter::Exception => {
RecordMapping::Event(exception_from_record(record, values))
}
},
};
match item {
RecordMapping::Ignore => {}
RecordMapping::Breadcrumb(b) => sentry_core::add_breadcrumb(b),
RecordMapping::Event(e) => {
sentry_core::capture_event(e);
}
}
self.drain.log(record, values)
}
fn is_enabled(&self, level: slog::Level) -> bool {
self.drain.is_enabled(level) || !matches!((self.filter)(level), LevelFilter::Ignore)
}
}