1use log::Record;
2use sentry_core::protocol::{Breadcrumb, Event};
3
4#[cfg(feature = "logs")]
5use crate::converters::log_from_record;
6use crate::converters::{breadcrumb_from_record, event_from_record, exception_from_record};
7
8#[derive(Debug)]
10pub enum LogFilter {
11 Ignore,
13 Breadcrumb,
15 Event,
17 Exception,
19 #[cfg(feature = "logs")]
21 Log,
22}
23
24#[derive(Debug)]
26#[allow(clippy::large_enum_variant)]
27pub enum RecordMapping {
28 Ignore,
30 Breadcrumb(Breadcrumb),
32 Event(Event<'static>),
34 #[cfg(feature = "logs")]
36 Log(sentry_core::protocol::Log),
37}
38
39pub fn default_filter(metadata: &log::Metadata) -> LogFilter {
44 match metadata.level() {
45 log::Level::Error => LogFilter::Exception,
46 log::Level::Warn | log::Level::Info => LogFilter::Breadcrumb,
47 log::Level::Debug | log::Level::Trace => LogFilter::Ignore,
48 }
49}
50
51#[derive(Debug, Default)]
53pub struct NoopLogger;
54
55impl log::Log for NoopLogger {
56 fn enabled(&self, metadata: &log::Metadata) -> bool {
57 let _ = metadata;
58 false
59 }
60
61 fn log(&self, record: &log::Record) {
62 let _ = record;
63 }
64
65 fn flush(&self) {
66 todo!()
67 }
68}
69
70pub struct SentryLogger<L: log::Log> {
73 dest: L,
74 filter: Box<dyn Fn(&log::Metadata<'_>) -> LogFilter + Send + Sync>,
75 #[allow(clippy::type_complexity)]
76 mapper: Option<Box<dyn Fn(&Record<'_>) -> RecordMapping + Send + Sync>>,
77}
78
79impl Default for SentryLogger<NoopLogger> {
80 fn default() -> Self {
81 Self {
82 dest: NoopLogger,
83 filter: Box::new(default_filter),
84 mapper: None,
85 }
86 }
87}
88
89impl SentryLogger<NoopLogger> {
90 pub fn new() -> Self {
92 Default::default()
93 }
94}
95
96impl<L: log::Log> SentryLogger<L> {
97 pub fn with_dest(dest: L) -> Self {
99 Self {
100 dest,
101 filter: Box::new(default_filter),
102 mapper: None,
103 }
104 }
105
106 #[must_use]
111 pub fn filter<F>(mut self, filter: F) -> Self
112 where
113 F: Fn(&log::Metadata<'_>) -> LogFilter + Send + Sync + 'static,
114 {
115 self.filter = Box::new(filter);
116 self
117 }
118
119 #[must_use]
124 pub fn mapper<M>(mut self, mapper: M) -> Self
125 where
126 M: Fn(&Record<'_>) -> RecordMapping + Send + Sync + 'static,
127 {
128 self.mapper = Some(Box::new(mapper));
129 self
130 }
131}
132
133impl<L: log::Log> log::Log for SentryLogger<L> {
134 fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
135 self.dest.enabled(metadata) || !matches!((self.filter)(metadata), LogFilter::Ignore)
136 }
137
138 fn log(&self, record: &log::Record<'_>) {
139 let item: RecordMapping = match &self.mapper {
140 Some(mapper) => mapper(record),
141 None => match (self.filter)(record.metadata()) {
142 LogFilter::Ignore => RecordMapping::Ignore,
143 LogFilter::Breadcrumb => RecordMapping::Breadcrumb(breadcrumb_from_record(record)),
144 LogFilter::Event => RecordMapping::Event(event_from_record(record)),
145 LogFilter::Exception => RecordMapping::Event(exception_from_record(record)),
146 #[cfg(feature = "logs")]
147 LogFilter::Log => RecordMapping::Log(log_from_record(record)),
148 },
149 };
150
151 match item {
152 RecordMapping::Ignore => {}
153 RecordMapping::Breadcrumb(b) => sentry_core::add_breadcrumb(b),
154 RecordMapping::Event(e) => {
155 sentry_core::capture_event(e);
156 }
157 #[cfg(feature = "logs")]
158 RecordMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)),
159 }
160
161 self.dest.log(record)
162 }
163
164 fn flush(&self) {
165 self.dest.flush()
166 }
167}