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