1extern crate log;
65extern crate log4rs;
66extern crate sentry;
67
68use derivative::Derivative;
69use log::{Level, LevelFilter, Record};
70use log4rs::{
71 append::Append,
72 config::{Deserialize, Deserializers},
73 encode::{pattern::PatternEncoder, writer::simple::SimpleWriter, Encode, EncoderConfig},
74};
75use sentry::{
76 protocol::value::{Number, Value},
77 ClientInitGuard, Level as SentryLevel,
78};
79
80#[derive(Clone, Eq, PartialEq, Hash, Debug, serde::Deserialize)]
82#[serde(deny_unknown_fields)]
83pub struct SentryAppenderConfig {
84 dsn: String,
85 encoder: Option<EncoderConfig>,
86 threshold: LevelFilter,
87}
88
89#[derive(Derivative)]
91#[derivative(Debug)]
92pub struct SentryAppender {
93 #[derivative(Debug = "ignore")]
94 _sentry: ClientInitGuard,
95 encoder: Box<dyn Encode>,
96 threshold: LevelFilter,
97}
98
99impl SentryAppender {
100 pub fn builder() -> SentryAppenderBuilder {
102 SentryAppenderBuilder {
103 encoder: None,
104 dsn: String::default(),
105 threshold: None,
106 }
107 }
108
109 pub fn deserializers() -> Deserializers {
114 let mut deserializers = Deserializers::new();
115 deserializers.insert("sentry", SentryAppenderDeserializer);
116 deserializers
117 }
118}
119
120impl Append for SentryAppender {
121 fn append(&self, record: &Record) -> anyhow::Result<()> {
122 if record.level() > self.threshold {
123 return Ok(());
126 }
127
128 let level = level_mapping(record.level());
129
130 let mut buf: Vec<u8> = Vec::new();
131 self.encoder.encode(&mut SimpleWriter(&mut buf), record)?;
132 let msg = String::from_utf8(buf)?;
133
134 let mut event = sentry::protocol::Event::new();
135 event.level = level;
136 event.message = Some(msg);
137 event.logger = Some(record.metadata().target().to_owned());
138
139 if let Some(file) = record.file() {
140 event
141 .extra
142 .insert("file".to_owned(), Value::String(file.to_owned()));
143 }
144
145 if let Some(line) = record.line() {
146 event
147 .extra
148 .insert("line".to_owned(), Value::Number(Number::from(line)));
149 }
150
151 if let Some(module_path) = record.module_path() {
152 event
153 .tags
154 .insert("module_path".to_owned(), module_path.to_owned());
155 }
156
157 sentry::capture_event(event);
158 Ok(())
159 }
160
161 fn flush(&self) {}
162}
163
164pub struct SentryAppenderBuilder {
166 encoder: Option<Box<dyn Encode>>,
167 dsn: String,
168 threshold: Option<LevelFilter>,
169}
170
171impl SentryAppenderBuilder {
172 pub fn encoder(mut self, encoder: Box<dyn Encode>) -> SentryAppenderBuilder {
173 self.encoder = Some(encoder);
174 self
175 }
176
177 pub fn dsn(mut self, dsn: &str) -> SentryAppenderBuilder {
178 self.dsn = dsn.to_string();
179 self
180 }
181
182 fn dsn_string(mut self, dsn: String) -> SentryAppenderBuilder {
183 self.dsn = dsn;
184 self
185 }
186
187 pub fn threshold(mut self, threshold: LevelFilter) -> SentryAppenderBuilder {
188 self.threshold = Some(threshold);
189 self
190 }
191
192 pub fn build(self) -> SentryAppender {
193 let _sentry: ClientInitGuard = sentry::init(self.dsn);
194 SentryAppender {
195 _sentry,
196 encoder: self
197 .encoder
198 .unwrap_or_else(|| Box::new(PatternEncoder::new("{m}"))),
199 threshold: self.threshold.unwrap_or(LevelFilter::Error),
200 }
201 }
202}
203
204#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
222pub struct SentryAppenderDeserializer;
223
224impl Deserialize for SentryAppenderDeserializer {
225 type Trait = dyn Append;
226
227 type Config = SentryAppenderConfig;
228
229 fn deserialize(
230 &self,
231 config: SentryAppenderConfig,
232 deserializers: &Deserializers,
233 ) -> anyhow::Result<Box<dyn Append>> {
234 let mut appender = SentryAppender::builder();
235
236 if let Some(encoder) = config.encoder {
237 appender = appender.encoder(deserializers.deserialize(&encoder.kind, encoder.config)?);
238 }
239
240 appender = appender.dsn_string(config.dsn);
241
242 appender = appender.threshold(config.threshold);
243
244 Ok(Box::new(appender.build()))
245 }
246}
247
248fn level_mapping(level: Level) -> SentryLevel {
249 match level {
250 Level::Error => SentryLevel::Error,
251 Level::Warn => SentryLevel::Warning,
252 Level::Info => SentryLevel::Info,
253 Level::Debug => SentryLevel::Debug,
254 Level::Trace => SentryLevel::Debug,
255 }
256}