use log::Record;
use sentry_core::protocol::{Breadcrumb, Event};
use bitflags::bitflags;
#[cfg(feature = "logs")]
use crate::converters::log_from_record;
use crate::converters::{breadcrumb_from_record, event_from_record, exception_from_record};
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LogFilter: u32 {
const Ignore = 0b0000;
const Breadcrumb = 0b0001;
const Event = 0b0010;
const Exception = 0b0100;
#[cfg(feature = "logs")]
const Log = 0b1000;
}
}
#[derive(Debug)]
#[non_exhaustive]
#[allow(clippy::large_enum_variant)]
pub enum RecordMapping {
Ignore,
Breadcrumb(Breadcrumb),
Event(Event<'static>),
#[cfg(feature = "logs")]
Log(sentry_core::protocol::Log),
}
impl From<RecordMapping> for Vec<RecordMapping> {
fn from(mapping: RecordMapping) -> Self {
vec![mapping]
}
}
pub fn default_filter(metadata: &log::Metadata) -> LogFilter {
match metadata.level() {
#[cfg(feature = "logs")]
log::Level::Error => LogFilter::Exception | LogFilter::Log,
#[cfg(not(feature = "logs"))]
log::Level::Error => LogFilter::Exception,
#[cfg(feature = "logs")]
log::Level::Warn | log::Level::Info => LogFilter::Breadcrumb | LogFilter::Log,
#[cfg(not(feature = "logs"))]
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<'_>) -> Vec<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, T>(mut self, mapper: M) -> Self
where
M: Fn(&Record<'_>) -> T + Send + Sync + 'static,
T: Into<Vec<RecordMapping>>,
{
self.mapper = Some(Box::new(move |record| mapper(record).into()));
self
}
}
impl<L: log::Log> log::Log for SentryLogger<L> {
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
self.dest.enabled(metadata) || !((self.filter)(metadata) == LogFilter::Ignore)
}
fn log(&self, record: &log::Record<'_>) {
let items = match &self.mapper {
Some(mapper) => mapper(record),
None => {
let filter = (self.filter)(record.metadata());
let mut items = vec![];
if filter.contains(LogFilter::Breadcrumb) {
items.push(RecordMapping::Breadcrumb(breadcrumb_from_record(record)));
}
if filter.contains(LogFilter::Event) {
items.push(RecordMapping::Event(event_from_record(record)));
}
if filter.contains(LogFilter::Exception) {
items.push(RecordMapping::Event(exception_from_record(record)));
}
#[cfg(feature = "logs")]
if filter.contains(LogFilter::Log) {
items.push(RecordMapping::Log(log_from_record(record)));
}
items
}
};
for mapping in items {
match mapping {
RecordMapping::Ignore => {}
RecordMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
RecordMapping::Event(event) => {
sentry_core::capture_event(event);
}
#[cfg(feature = "logs")]
RecordMapping::Log(log) => {
sentry_core::Hub::with_active(|hub| hub.capture_log(log))
}
}
}
self.dest.log(record)
}
fn flush(&self) {
self.dest.flush()
}
}