use std::{
error::Error,
fmt::{self, Debug},
ops::Deref,
panic::Location,
};
use backtrace::Backtrace;
use chrono::{DateTime, Local};
use http::Extensions;
use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use crate::Problem;
static REPORTER: OnceCell<Box<dyn ProblemReporter + Send + Sync>> = OnceCell::new();
#[track_caller]
pub(crate) fn capture_handler(error: &(dyn Error + 'static)) -> Box<dyn eyre::EyreHandler> {
let mut report = Box::default();
if let Some(reporter) = global_reporter() {
reporter.capture_error_context(&mut report, error);
}
report
}
pub(crate) fn global_reporter() -> Option<&'static dyn ProblemReporter> {
REPORTER.get().map(|r| &**r as &'static dyn ProblemReporter)
}
pub fn set_global_reporter<R>(reporter: R)
where
R: ProblemReporter + Send + Sync + 'static,
{
if REPORTER.set(Box::new(reporter)).is_err() {
panic!("Global problem reporter set twice! Did you call `problem::reporter::set_global_reporter` twice?");
}
}
pub trait ProblemReporter {
fn capture_error_context(&'static self, report: &mut Report, error: &(dyn Error + 'static));
fn should_report_error(&'static self, problem: &Problem) -> bool;
fn report_error(&'static self, problem: &Problem);
}
pub struct Report {
backtrace: RwLock<Backtrace>,
location: &'static Location<'static>,
timestamp: DateTime<Local>,
extensions: Extensions,
}
impl Default for Report {
#[track_caller]
fn default() -> Self {
Self {
backtrace: RwLock::new(Backtrace::new_unresolved()),
location: Location::caller(),
timestamp: Local::now(),
extensions: Extensions::new(),
}
}
}
impl Report {
pub fn backtrace(&self) -> impl Deref<Target = Backtrace> + '_ {
self.backtrace.write().resolve();
self.backtrace.read()
}
#[inline(always)]
pub fn backtrace_unresolved(&self) -> impl Deref<Target = Backtrace> + '_ {
self.backtrace.read()
}
#[inline(always)]
pub fn location(&self) -> &'static Location<'static> {
self.location
}
#[inline(always)]
pub fn timestamp(&self) -> DateTime<Local> {
self.timestamp
}
#[inline(always)]
pub fn insert<T: Send + Sync + 'static>(&mut self, val: T) {
self.extensions.insert(val);
}
#[inline(always)]
pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
self.extensions.get()
}
}
impl eyre::EyreHandler for Report {
fn debug(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Error at {} ({}, {}): ",
self.location.file(),
self.location.line(),
self.location.column()
)?;
write!(f, "{}", error)?;
if error.source().is_some() {
writeln!(f, "\n\nCaused by:")?;
let mut curr = error.source();
let mut idx = 0;
while let Some(err) = curr {
writeln!(f, " {idx}: {err}")?;
curr = err.source();
idx += 1;
}
}
(*self.backtrace()).fmt(f)
}
fn track_caller(&mut self, location: &'static Location<'static>) {
self.location = location;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_report() {
let rep = Report::default();
assert_eq!(rep.location().line(), line!() - 2);
assert_eq!(rep.location().file(), file!());
std::thread::sleep(std::time::Duration::from_millis(10));
assert!(rep.timestamp() < Local::now());
assert!(!rep.backtrace_unresolved().frames().is_empty());
let symbols_count = rep
.backtrace()
.frames()
.iter()
.flat_map(|f| f.symbols())
.count();
assert!(symbols_count > 0);
}
#[test]
fn test_report_extensions() {
let mut rep = Report::default();
rep.insert(2usize);
assert_eq!(rep.get(), Some(&2usize));
assert!(rep.get::<String>().is_none());
}
}