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 #[allow(clippy::type_complexity)]
68 mapper: Option<Box<dyn Fn(&Record<'_>) -> RecordMapping + Send + Sync>>,
69}
70
71impl Default for SentryLogger<NoopLogger> {
72 fn default() -> Self {
73 Self {
74 dest: NoopLogger,
75 filter: Box::new(default_filter),
76 mapper: None,
77 }
78 }
79}
80
81impl SentryLogger<NoopLogger> {
82 pub fn new() -> Self {
84 Default::default()
85 }
86}
87
88impl<L: log::Log> SentryLogger<L> {
89 pub fn with_dest(dest: L) -> Self {
91 Self {
92 dest,
93 filter: Box::new(default_filter),
94 mapper: None,
95 }
96 }
97
98 #[must_use]
103 pub fn filter<F>(mut self, filter: F) -> Self
104 where
105 F: Fn(&log::Metadata<'_>) -> LogFilter + Send + Sync + 'static,
106 {
107 self.filter = Box::new(filter);
108 self
109 }
110
111 #[must_use]
116 pub fn mapper<M>(mut self, mapper: M) -> Self
117 where
118 M: Fn(&Record<'_>) -> RecordMapping + Send + Sync + 'static,
119 {
120 self.mapper = Some(Box::new(mapper));
121 self
122 }
123}
124
125impl<L: log::Log> log::Log for SentryLogger<L> {
126 fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
127 self.dest.enabled(metadata) || !matches!((self.filter)(metadata), LogFilter::Ignore)
128 }
129
130 fn log(&self, record: &log::Record<'_>) {
131 let item: RecordMapping = match &self.mapper {
132 Some(mapper) => mapper(record),
133 None => match (self.filter)(record.metadata()) {
134 LogFilter::Ignore => RecordMapping::Ignore,
135 LogFilter::Breadcrumb => RecordMapping::Breadcrumb(breadcrumb_from_record(record)),
136 LogFilter::Event => RecordMapping::Event(event_from_record(record)),
137 LogFilter::Exception => RecordMapping::Event(exception_from_record(record)),
138 },
139 };
140
141 match item {
142 RecordMapping::Ignore => {}
143 RecordMapping::Breadcrumb(b) => sentry_core::add_breadcrumb(b),
144 RecordMapping::Event(e) => {
145 sentry_core::capture_event(e);
146 }
147 }
148
149 self.dest.log(record)
150 }
151
152 fn flush(&self) {
153 self.dest.flush()
154 }
155}