1use log::Record;
2use sentry_core::protocol::{Breadcrumb, Event};
3
4use crate::converters::{breadcrumb_from_record, event_from_record, exception_from_record};
5
6#[derive(Debug)]
8pub enum LogFilter {
9 Ignore,
11 Breadcrumb,
13 Event,
15 Exception,
17}
18
19#[derive(Debug)]
21#[allow(clippy::large_enum_variant)]
22pub enum RecordMapping {
23 Ignore,
25 Breadcrumb(Breadcrumb),
27 Event(Event<'static>),
29}
30
31pub fn default_filter(metadata: &log::Metadata) -> LogFilter {
36 match metadata.level() {
37 log::Level::Error => LogFilter::Exception,
38 log::Level::Warn | log::Level::Info => LogFilter::Breadcrumb,
39 log::Level::Debug | log::Level::Trace => LogFilter::Ignore,
40 }
41}
42
43#[derive(Debug, Default)]
45pub struct NoopLogger;
46
47impl log::Log for NoopLogger {
48 fn enabled(&self, metadata: &log::Metadata) -> bool {
49 let _ = metadata;
50 false
51 }
52
53 fn log(&self, record: &log::Record) {
54 let _ = record;
55 }
56
57 fn flush(&self) {
58 todo!()
59 }
60}
61
62pub struct SentryLogger<L: log::Log> {
65 dest: L,
66 filter: Box<dyn Fn(&log::Metadata<'_>) -> LogFilter + Send + Sync>,
67 mapper: Option<Box<dyn Fn(&Record<'_>) -> RecordMapping + Send + Sync>>,
68}
69
70impl Default for SentryLogger<NoopLogger> {
71 fn default() -> Self {
72 Self {
73 dest: NoopLogger,
74 filter: Box::new(default_filter),
75 mapper: None,
76 }
77 }
78}
79
80impl SentryLogger<NoopLogger> {
81 pub fn new() -> Self {
83 Default::default()
84 }
85}
86
87impl<L: log::Log> SentryLogger<L> {
88 pub fn with_dest(dest: L) -> Self {
90 Self {
91 dest,
92 filter: Box::new(default_filter),
93 mapper: None,
94 }
95 }
96
97 pub fn filter<F>(mut self, filter: F) -> Self
102 where
103 F: Fn(&log::Metadata<'_>) -> LogFilter + Send + Sync + 'static,
104 {
105 self.filter = Box::new(filter);
106 self
107 }
108
109 pub fn mapper<M>(mut self, mapper: M) -> Self
114 where
115 M: Fn(&Record<'_>) -> RecordMapping + Send + Sync + 'static,
116 {
117 self.mapper = Some(Box::new(mapper));
118 self
119 }
120}
121
122impl<L: log::Log> log::Log for SentryLogger<L> {
123 fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
124 self.dest.enabled(metadata) || !matches!((self.filter)(metadata), LogFilter::Ignore)
125 }
126
127 fn log(&self, record: &log::Record<'_>) {
128 let item: RecordMapping = match &self.mapper {
129 Some(mapper) => mapper(record),
130 None => match (self.filter)(record.metadata()) {
131 LogFilter::Ignore => RecordMapping::Ignore,
132 LogFilter::Breadcrumb => RecordMapping::Breadcrumb(breadcrumb_from_record(record)),
133 LogFilter::Event => RecordMapping::Event(event_from_record(record)),
134 LogFilter::Exception => RecordMapping::Event(exception_from_record(record)),
135 },
136 };
137
138 match item {
139 RecordMapping::Ignore => {}
140 RecordMapping::Breadcrumb(b) => sentry_core::add_breadcrumb(b),
141 RecordMapping::Event(e) => {
142 sentry_core::capture_event(e);
143 }
144 }
145
146 self.dest.log(record)
147 }
148
149 fn flush(&self) {
150 self.dest.flush()
151 }
152}