1use log::Record;
2use sentry_core::protocol::{Breadcrumb, Event};
3
4use bitflags::bitflags;
5
6#[cfg(feature = "logs")]
7use crate::converters::log_from_record;
8use crate::converters::{breadcrumb_from_record, event_from_record, exception_from_record};
9
10bitflags! {
11 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
13 pub struct LogFilter: u32 {
14 const Ignore = 0b0000;
16 const Breadcrumb = 0b0001;
18 const Event = 0b0010;
20 const Exception = 0b0100;
22 #[cfg(feature = "logs")]
24 const Log = 0b1000;
25 }
26}
27
28#[derive(Debug)]
30#[allow(clippy::large_enum_variant)]
31pub enum RecordMapping {
32 Ignore,
34 Breadcrumb(Breadcrumb),
36 Event(Event<'static>),
38 #[cfg(feature = "logs")]
40 Log(sentry_core::protocol::Log),
41}
42
43impl From<RecordMapping> for Vec<RecordMapping> {
44 fn from(mapping: RecordMapping) -> Self {
45 vec![mapping]
46 }
47}
48
49pub fn default_filter(metadata: &log::Metadata) -> LogFilter {
54 match metadata.level() {
55 #[cfg(feature = "logs")]
56 log::Level::Error => LogFilter::Exception | LogFilter::Log,
57 #[cfg(not(feature = "logs"))]
58 log::Level::Error => LogFilter::Exception,
59 #[cfg(feature = "logs")]
60 log::Level::Warn | log::Level::Info => LogFilter::Breadcrumb | LogFilter::Log,
61 #[cfg(not(feature = "logs"))]
62 log::Level::Warn | log::Level::Info => LogFilter::Breadcrumb,
63 log::Level::Debug | log::Level::Trace => LogFilter::Ignore,
64 }
65}
66
67#[derive(Debug, Default)]
69pub struct NoopLogger;
70
71impl log::Log for NoopLogger {
72 fn enabled(&self, metadata: &log::Metadata) -> bool {
73 let _ = metadata;
74 false
75 }
76
77 fn log(&self, record: &log::Record) {
78 let _ = record;
79 }
80
81 fn flush(&self) {
82 todo!()
83 }
84}
85
86pub struct SentryLogger<L: log::Log> {
89 dest: L,
90 filter: Box<dyn Fn(&log::Metadata<'_>) -> LogFilter + Send + Sync>,
91 #[allow(clippy::type_complexity)]
92 mapper: Option<Box<dyn Fn(&Record<'_>) -> Vec<RecordMapping> + Send + Sync>>,
93}
94
95impl Default for SentryLogger<NoopLogger> {
96 fn default() -> Self {
97 Self {
98 dest: NoopLogger,
99 filter: Box::new(default_filter),
100 mapper: None,
101 }
102 }
103}
104
105impl SentryLogger<NoopLogger> {
106 pub fn new() -> Self {
108 Default::default()
109 }
110}
111
112impl<L: log::Log> SentryLogger<L> {
113 pub fn with_dest(dest: L) -> Self {
115 Self {
116 dest,
117 filter: Box::new(default_filter),
118 mapper: None,
119 }
120 }
121
122 #[must_use]
127 pub fn filter<F>(mut self, filter: F) -> Self
128 where
129 F: Fn(&log::Metadata<'_>) -> LogFilter + Send + Sync + 'static,
130 {
131 self.filter = Box::new(filter);
132 self
133 }
134
135 #[must_use]
141 pub fn mapper<M, T>(mut self, mapper: M) -> Self
142 where
143 M: Fn(&Record<'_>) -> T + Send + Sync + 'static,
144 T: Into<Vec<RecordMapping>>,
145 {
146 self.mapper = Some(Box::new(move |record| mapper(record).into()));
147 self
148 }
149}
150
151impl<L: log::Log> log::Log for SentryLogger<L> {
152 fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
153 self.dest.enabled(metadata) || !((self.filter)(metadata) == LogFilter::Ignore)
154 }
155
156 fn log(&self, record: &log::Record<'_>) {
157 let items = match &self.mapper {
158 Some(mapper) => mapper(record),
159 None => {
160 let filter = (self.filter)(record.metadata());
161 let mut items = vec![];
162 if filter.contains(LogFilter::Breadcrumb) {
163 items.push(RecordMapping::Breadcrumb(breadcrumb_from_record(record)));
164 }
165 if filter.contains(LogFilter::Event) {
166 items.push(RecordMapping::Event(event_from_record(record)));
167 }
168 if filter.contains(LogFilter::Exception) {
169 items.push(RecordMapping::Event(exception_from_record(record)));
170 }
171 #[cfg(feature = "logs")]
172 if filter.contains(LogFilter::Log) {
173 items.push(RecordMapping::Log(log_from_record(record)));
174 }
175 items
176 }
177 };
178
179 for mapping in items {
180 match mapping {
181 RecordMapping::Ignore => {}
182 RecordMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
183 RecordMapping::Event(event) => {
184 sentry_core::capture_event(event);
185 }
186 #[cfg(feature = "logs")]
187 RecordMapping::Log(log) => {
188 sentry_core::Hub::with_active(|hub| hub.capture_log(log))
189 }
190 }
191 }
192
193 self.dest.log(record)
194 }
195
196 fn flush(&self) {
197 self.dest.flush()
198 }
199}