use std::any::{Any, type_name};
use std::backtrace::{Backtrace, BacktraceStatus};
use std::error::Error;
use std::sync::{Arc, Mutex, PoisonError};
use serde_json::{Map, Value};
use crate::call::PLATFORM;
#[cfg(test)]
mod tests;
tokio::task_local! {
static SLOT: CaptureSlot;
}
const PANIC_TYPE: &str = "panic";
const OPAQUE_PAYLOAD: &str = "Box<dyn Any>";
const MAX_CHAINED_ERRORS: usize = 10;
pub fn capture_error<E: Error + ?Sized + 'static>(error: &E) {
let _outside = SLOT.try_with(|slot| {
slot.put((error_shape(error), captured_backtrace(Backtrace::capture())));
});
}
pub(crate) type Captured = (Map<String, Value>, Option<Backtrace>);
#[derive(Debug, Clone, Default)]
pub(crate) struct CaptureSlot(Arc<Mutex<Option<Captured>>>);
impl CaptureSlot {
pub(crate) fn take(&self) -> Option<Captured> {
self.0.lock().unwrap_or_else(PoisonError::into_inner).take()
}
fn put(&self, captured: Captured) {
*self.0.lock().unwrap_or_else(PoisonError::into_inner) = Some(captured);
}
}
pub(crate) fn captured_backtrace(backtrace: Backtrace) -> Option<Backtrace> {
(backtrace.status() == BacktraceStatus::Captured).then_some(backtrace)
}
pub(crate) async fn scope<F: Future>(slot: CaptureSlot, future: F) -> F::Output {
SLOT.scope(slot, future).await
}
pub(crate) fn panic_error(payload: &(dyn Any + Send)) -> Map<String, Value> {
let mut error = Map::new();
error.insert("message".to_owned(), Value::String(panic_message(payload)));
error.insert("type".to_owned(), Value::String(PANIC_TYPE.to_owned()));
error.insert("platform".to_owned(), Value::String(PLATFORM.to_owned()));
error
}
pub(crate) fn panic_message(payload: &(dyn Any + Send)) -> String {
if let Some(text) = payload.downcast_ref::<&str>() {
(*text).to_owned()
} else if let Some(text) = payload.downcast_ref::<String>() {
text.clone()
} else {
OPAQUE_PAYLOAD.to_owned()
}
}
pub(crate) fn error_shape<E: Error + ?Sized + 'static>(error: &E) -> Map<String, Value> {
let mut shape = Map::new();
shape.insert("message".to_owned(), Value::String(error.to_string()));
shape.insert(
"type".to_owned(),
Value::String(type_name::<E>().to_owned()),
);
shape.insert("platform".to_owned(), Value::String(PLATFORM.to_owned()));
let chain: Vec<Value> = std::iter::successors(error.source(), |&cause| cause.source())
.take(MAX_CHAINED_ERRORS)
.map(|cause| {
let mut entry = Map::new();
entry.insert("message".to_owned(), Value::String(cause.to_string()));
Value::Object(entry)
})
.collect();
if !chain.is_empty() {
shape.insert("chained_errors".to_owned(), Value::Array(chain));
}
shape
}