1use std::{
2 error::Error,
3 fmt::{self, Debug},
4 ops::Deref,
5 panic::Location,
6};
7
8use backtrace::Backtrace;
9use chrono::{DateTime, Local};
10use http::Extensions;
11use once_cell::sync::OnceCell;
12use parking_lot::RwLock;
13
14use crate::Problem;
15
16static REPORTER: OnceCell<Box<dyn ProblemReporter + Send + Sync>> = OnceCell::new();
17
18#[track_caller]
19pub(crate) fn capture_handler(error: &(dyn Error + 'static)) -> Box<dyn eyre::EyreHandler> {
20 let mut report = Box::default();
21
22 if let Some(reporter) = global_reporter() {
23 reporter.capture_error_context(&mut report, error);
24 }
25
26 report
27}
28
29pub(crate) fn global_reporter() -> Option<&'static dyn ProblemReporter> {
30 REPORTER.get().map(|r| &**r as &'static dyn ProblemReporter)
31}
32
33pub fn set_global_reporter<R>(reporter: R)
39where
40 R: ProblemReporter + Send + Sync + 'static,
41{
42 if REPORTER.set(Box::new(reporter)).is_err() {
44 panic!("Global problem reporter set twice! Did you call `problem::reporter::set_global_reporter` twice?");
45 }
46}
47
48pub trait ProblemReporter {
53 fn capture_error_context(&'static self, report: &mut Report, error: &(dyn Error + 'static));
62
63 fn should_report_error(&'static self, problem: &Problem) -> bool;
65
66 fn report_error(&'static self, problem: &Problem);
71}
72
73pub struct Report {
82 backtrace: RwLock<Backtrace>,
83 location: &'static Location<'static>,
84 timestamp: DateTime<Local>,
85 extensions: Extensions,
86}
87
88impl Default for Report {
89 #[track_caller]
90 fn default() -> Self {
91 Self {
92 backtrace: RwLock::new(Backtrace::new_unresolved()),
93 location: Location::caller(),
94 timestamp: Local::now(),
95 extensions: Extensions::new(),
96 }
97 }
98}
99
100impl Report {
101 pub fn backtrace(&self) -> impl Deref<Target = Backtrace> + '_ {
103 self.backtrace.write().resolve();
104 self.backtrace.read()
105 }
106
107 #[inline(always)]
109 pub fn backtrace_unresolved(&self) -> impl Deref<Target = Backtrace> + '_ {
110 self.backtrace.read()
111 }
112
113 #[inline(always)]
118 pub fn location(&self) -> &'static Location<'static> {
119 self.location
120 }
121
122 #[inline(always)]
124 pub fn timestamp(&self) -> DateTime<Local> {
125 self.timestamp
126 }
127
128 #[inline(always)]
130 pub fn insert<T: Send + Sync + 'static>(&mut self, val: T) {
131 self.extensions.insert(val);
132 }
133
134 #[inline(always)]
138 pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
139 self.extensions.get()
140 }
141}
142
143impl eyre::EyreHandler for Report {
144 fn debug(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 write!(
146 f,
147 "Error at {} ({}, {}): ",
148 self.location.file(),
149 self.location.line(),
150 self.location.column()
151 )?;
152
153 write!(f, "{error}")?;
154
155 if error.source().is_some() {
157 writeln!(f, "\n\nCaused by:")?;
158
159 let mut curr = error.source();
160 let mut idx = 0;
161 while let Some(err) = curr {
162 writeln!(f, " {idx}: {err}")?;
163 curr = err.source();
164 idx += 1;
165 }
166 }
167
168 (*self.backtrace()).fmt(f)
178 }
179
180 fn track_caller(&mut self, location: &'static Location<'static>) {
181 self.location = location;
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_report() {
191 let rep = Report::default();
192
193 assert_eq!(rep.location().line(), line!() - 2);
194 assert_eq!(rep.location().file(), file!());
195
196 std::thread::sleep(std::time::Duration::from_millis(10));
197
198 assert!(rep.timestamp() < Local::now());
199 assert!(!rep.backtrace_unresolved().frames().is_empty());
200
201 let symbols_count = rep
202 .backtrace()
203 .frames()
204 .iter()
205 .flat_map(|f| f.symbols())
206 .count();
207
208 assert!(symbols_count > 0);
209 }
210
211 #[test]
212 fn test_report_extensions() {
213 let mut rep = Report::default();
214
215 rep.insert(2usize);
216
217 assert_eq!(rep.get(), Some(&2usize));
218
219 assert!(rep.get::<String>().is_none());
220 }
221}