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#[non_exhaustive]
31#[allow(clippy::large_enum_variant)]
32pub enum RecordMapping {
33 Ignore,
35 Breadcrumb(Breadcrumb),
37 Event(Event<'static>),
39 #[cfg(feature = "logs")]
41 Log(sentry_core::protocol::Log),
42}
43
44impl From<RecordMapping> for Vec<RecordMapping> {
45 fn from(mapping: RecordMapping) -> Self {
46 vec![mapping]
47 }
48}
49
50pub fn default_filter(metadata: &log::Metadata) -> LogFilter {
55 match metadata.level() {
56 #[cfg(feature = "logs")]
57 log::Level::Error => LogFilter::Exception | LogFilter::Log,
58 #[cfg(not(feature = "logs"))]
59 log::Level::Error => LogFilter::Exception,
60 #[cfg(feature = "logs")]
61 log::Level::Warn | log::Level::Info => LogFilter::Breadcrumb | LogFilter::Log,
62 #[cfg(not(feature = "logs"))]
63 log::Level::Warn | log::Level::Info => LogFilter::Breadcrumb,
64 log::Level::Debug | log::Level::Trace => LogFilter::Ignore,
65 }
66}
67
68#[derive(Debug, Default)]
70pub struct NoopLogger;
71
72impl log::Log for NoopLogger {
73 fn enabled(&self, metadata: &log::Metadata) -> bool {
74 let _ = metadata;
75 false
76 }
77
78 fn log(&self, record: &log::Record) {
79 let _ = record;
80 }
81
82 fn flush(&self) {
83 todo!()
84 }
85}
86
87pub struct SentryLogger<L: log::Log> {
90 dest: L,
91 filter: Box<dyn Fn(&log::Metadata<'_>) -> LogFilter + Send + Sync>,
92 #[allow(clippy::type_complexity)]
93 mapper: Option<Box<dyn Fn(&Record<'_>) -> Vec<RecordMapping> + Send + Sync>>,
94}
95
96impl Default for SentryLogger<NoopLogger> {
97 fn default() -> Self {
98 Self {
99 dest: NoopLogger,
100 filter: Box::new(default_filter),
101 mapper: None,
102 }
103 }
104}
105
106impl SentryLogger<NoopLogger> {
107 pub fn new() -> Self {
109 Default::default()
110 }
111}
112
113impl<L: log::Log> SentryLogger<L> {
114 pub fn with_dest(dest: L) -> Self {
116 Self {
117 dest,
118 filter: Box::new(default_filter),
119 mapper: None,
120 }
121 }
122
123 #[must_use]
128 pub fn filter<F>(mut self, filter: F) -> Self
129 where
130 F: Fn(&log::Metadata<'_>) -> LogFilter + Send + Sync + 'static,
131 {
132 self.filter = Box::new(filter);
133 self
134 }
135
136 #[must_use]
142 pub fn mapper<M, T>(mut self, mapper: M) -> Self
143 where
144 M: Fn(&Record<'_>) -> T + Send + Sync + 'static,
145 T: Into<Vec<RecordMapping>>,
146 {
147 self.mapper = Some(Box::new(move |record| mapper(record).into()));
148 self
149 }
150}
151
152impl<L: log::Log> log::Log for SentryLogger<L> {
153 fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
154 self.dest.enabled(metadata) || !((self.filter)(metadata) == LogFilter::Ignore)
155 }
156
157 fn log(&self, record: &log::Record<'_>) {
158 let items = match &self.mapper {
159 Some(mapper) => mapper(record),
160 None => {
161 let filter = (self.filter)(record.metadata());
162 let mut items = vec![];
163 if filter.contains(LogFilter::Breadcrumb) {
164 items.push(RecordMapping::Breadcrumb(breadcrumb_from_record(record)));
165 }
166 if filter.contains(LogFilter::Event) {
167 items.push(RecordMapping::Event(event_from_record(record)));
168 }
169 if filter.contains(LogFilter::Exception) {
170 items.push(RecordMapping::Event(exception_from_record(record)));
171 }
172 #[cfg(feature = "logs")]
173 if filter.contains(LogFilter::Log) {
174 items.push(RecordMapping::Log(log_from_record(record)));
175 }
176 items
177 }
178 };
179
180 for mapping in items {
181 match mapping {
182 RecordMapping::Ignore => {}
183 RecordMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
184 RecordMapping::Event(event) => {
185 sentry_core::capture_event(event);
186 }
187 #[cfg(feature = "logs")]
188 RecordMapping::Log(log) => {
189 sentry_core::Hub::with_active(|hub| hub.capture_log(log))
190 }
191 }
192 }
193
194 self.dest.log(record)
195 }
196
197 fn flush(&self) {
198 self.dest.flush()
199 }
200}