use log::Record;
use sentry_core::protocol::{Breadcrumb, Event};
use crate::converters::{breadcrumb_from_record, event_from_record, exception_from_record};
#[derive(Debug)]
pub enum LogFilter {
Ignore,
Breadcrumb,
Event,
Exception,
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum RecordMapping {
Ignore,
Breadcrumb(Breadcrumb),
Event(Event<'static>),
}
pub fn default_filter(metadata: &log::Metadata) -> LogFilter {
match metadata.level() {
log::Level::Error => LogFilter::Exception,
log::Level::Warn | log::Level::Info => LogFilter::Breadcrumb,
log::Level::Debug | log::Level::Trace => LogFilter::Ignore,
}
}
#[derive(Debug, Default)]
pub struct NoopLogger;
impl log::Log for NoopLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
let _ = metadata;
false
}
fn log(&self, record: &log::Record) {
let _ = record;
}
fn flush(&self) {
todo!()
}
}
pub struct SentryLogger<L: log::Log> {
dest: L,
filter: Box<dyn Fn(&log::Metadata<'_>) -> LogFilter + Send + Sync>,
#[allow(clippy::type_complexity)]
mapper: Option<Box<dyn Fn(&Record<'_>) -> RecordMapping + Send + Sync>>,
}
impl Default for SentryLogger<NoopLogger> {
fn default() -> Self {
Self {
dest: NoopLogger,
filter: Box::new(default_filter),
mapper: None,
}
}
}
impl SentryLogger<NoopLogger> {
pub fn new() -> Self {
Default::default()
}
}
impl<L: log::Log> SentryLogger<L> {
pub fn with_dest(dest: L) -> Self {
Self {
dest,
filter: Box::new(default_filter),
mapper: None,
}
}
#[must_use]
pub fn filter<F>(mut self, filter: F) -> Self
where
F: Fn(&log::Metadata<'_>) -> LogFilter + Send + Sync + 'static,
{
self.filter = Box::new(filter);
self
}
#[must_use]
pub fn mapper<M>(mut self, mapper: M) -> Self
where
M: Fn(&Record<'_>) -> RecordMapping + Send + Sync + 'static,
{
self.mapper = Some(Box::new(mapper));
self
}
}
impl<L: log::Log> log::Log for SentryLogger<L> {
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
self.dest.enabled(metadata) || !matches!((self.filter)(metadata), LogFilter::Ignore)
}
fn log(&self, record: &log::Record<'_>) {
let item: RecordMapping = match &self.mapper {
Some(mapper) => mapper(record),
None => match (self.filter)(record.metadata()) {
LogFilter::Ignore => RecordMapping::Ignore,
LogFilter::Breadcrumb => RecordMapping::Breadcrumb(breadcrumb_from_record(record)),
LogFilter::Event => RecordMapping::Event(event_from_record(record)),
LogFilter::Exception => RecordMapping::Event(exception_from_record(record)),
},
};
match item {
RecordMapping::Ignore => {}
RecordMapping::Breadcrumb(b) => sentry_core::add_breadcrumb(b),
RecordMapping::Event(e) => {
sentry_core::capture_event(e);
}
}
self.dest.log(record)
}
fn flush(&self) {
self.dest.flush()
}
}