#![cfg_attr(not(feature = "std"), allow(dead_code))]
#![cfg_attr(not(feature = "std"), allow(unreachable_pub))]
use alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec};
use core::{
any::{Any, TypeId},
marker::PhantomData,
mem,
};
pub(crate) use default::install_builtin_hooks;
use crate::fmt::Frame;
type Storage = BTreeMap<TypeId, BTreeMap<TypeId, Box<dyn Any>>>;
struct Counter(isize);
impl Counter {
const fn new(value: isize) -> Self {
Self(value)
}
const fn as_inner(&self) -> isize {
self.0
}
fn increment(&mut self) {
self.0 += 1;
}
fn decrement(&mut self) {
self.0 -= 1;
}
}
#[derive(Debug)]
pub(crate) struct HookContextInner {
storage: Storage,
alternate: bool,
body: Vec<String>,
appendix: Vec<String>,
}
impl HookContextInner {
fn storage(&self) -> &Storage {
&self.storage
}
fn storage_mut(&mut self) -> &mut Storage {
&mut self.storage
}
const fn alternate(&self) -> bool {
self.alternate
}
fn take_body(&mut self) -> Vec<String> {
mem::take(&mut self.body)
}
}
impl HookContextInner {
fn new(alternate: bool) -> Self {
Self {
storage: Storage::default(),
body: Vec::new(),
appendix: Vec::new(),
alternate,
}
}
}
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit.snap"))]
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit_alt.snap"))]
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_storage.snap"))]
#[repr(transparent)]
pub struct HookContext<T> {
inner: HookContextInner,
_marker: PhantomData<fn(&T)>,
}
impl<T> HookContext<T> {
pub(crate) fn new(alternate: bool) -> Self {
Self {
inner: HookContextInner::new(alternate),
_marker: PhantomData,
}
}
pub(crate) fn appendix(&self) -> &[String] {
&self.inner.appendix
}
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_emit.snap"))]
pub fn push_appendix(&mut self, content: impl Into<String>) {
self.inner.appendix.push(content.into());
}
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__diagnostics_add.snap"))]
pub fn push_body(&mut self, content: impl Into<String>) {
self.inner.body.push(content.into());
}
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_cast.snap"))]
#[must_use]
pub fn cast<U>(&mut self) -> &mut HookContext<U> {
unsafe { &mut *(self as *mut Self).cast::<HookContext<U>>() }
}
#[must_use]
pub const fn alternate(&self) -> bool {
self.inner.alternate()
}
fn storage(&self) -> &Storage {
self.inner.storage()
}
fn storage_mut(&mut self) -> &mut Storage {
self.inner.storage_mut()
}
pub(crate) fn take_body(&mut self) -> Vec<String> {
self.inner.take_body()
}
}
impl<T: 'static> HookContext<T> {
#[must_use]
pub fn get<U: 'static>(&self) -> Option<&U> {
self.storage()
.get(&TypeId::of::<T>())?
.get(&TypeId::of::<U>())?
.downcast_ref()
}
pub fn get_mut<U: 'static>(&mut self) -> Option<&mut U> {
self.storage_mut()
.get_mut(&TypeId::of::<T>())?
.get_mut(&TypeId::of::<U>())?
.downcast_mut()
}
pub fn insert<U: 'static>(&mut self, value: U) -> Option<U> {
self.storage_mut()
.entry(TypeId::of::<T>())
.or_default()
.insert(TypeId::of::<U>(), Box::new(value))?
.downcast()
.map(|boxed| *boxed)
.ok()
}
pub fn remove<U: 'static>(&mut self) -> Option<U> {
self.storage_mut()
.get_mut(&TypeId::of::<T>())?
.remove(&TypeId::of::<U>())?
.downcast()
.map(|boxed| *boxed)
.ok()
}
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_increment.snap"))]
pub fn increment_counter(&mut self) -> isize {
let counter = self.get_mut::<Counter>();
#[allow(clippy::option_if_let_else)]
match counter {
None => {
self.insert(Counter::new(0));
0
}
Some(ctr) => {
ctr.increment();
ctr.as_inner()
}
}
}
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_decrement.snap"))]
pub fn decrement_counter(&mut self) -> isize {
let counter = self.get_mut::<Counter>();
#[allow(clippy::option_if_let_else)]
match counter {
None => {
self.insert(Counter::new(-1));
-1
}
Some(ctr) => {
ctr.decrement();
ctr.as_inner()
}
}
}
}
type BoxedHook = Box<dyn Fn(&Frame, &mut HookContext<Frame>) -> bool + Send + Sync>;
fn into_boxed_hook<T: Send + Sync + 'static>(
hook: impl Fn(&T, &mut HookContext<T>) + Send + Sync + 'static,
) -> BoxedHook {
Box::new(move |frame: &Frame, context: &mut HookContext<Frame>| {
#[cfg(nightly)]
{
frame
.request_ref::<T>()
.map(|value| hook(value, context.cast()))
.or_else(|| {
frame
.request_value::<T>()
.map(|ref value| hook(value, context.cast()))
})
.is_some()
}
#[cfg(not(nightly))]
matches!(frame.kind(), crate::FrameKind::Attachment(_))
.then_some(frame)
.and_then(Frame::downcast_ref::<T>)
.map(|value| hook(value, context.cast()))
.is_some()
})
}
#[cfg(feature = "std")]
pub(crate) struct Hooks {
pub(crate) inner: Vec<(TypeId, BoxedHook)>,
}
#[cfg(feature = "std")]
impl Hooks {
pub(crate) fn insert<T: Send + Sync + 'static>(
&mut self,
hook: impl Fn(&T, &mut HookContext<T>) + Send + Sync + 'static,
) {
let type_id = TypeId::of::<T>();
self.inner.retain(|(id, _)| *id != type_id);
self.inner.push((type_id, into_boxed_hook(hook)));
}
pub(crate) fn call(&self, frame: &Frame, context: &mut HookContext<Frame>) -> bool {
let mut hit = false;
for (_, hook) in &self.inner {
hit = hook(frame, context) || hit;
}
hit
}
}
mod default {
#![allow(unused_imports)]
#[cfg(any(rust_1_65, feature = "spantrace"))]
use alloc::format;
use alloc::{vec, vec::Vec};
use core::any::TypeId;
#[cfg(rust_1_65)]
use std::backtrace::Backtrace;
use std::{
panic::Location,
sync::{
atomic::{AtomicBool, Ordering},
Once,
},
};
#[cfg(feature = "pretty-print")]
use owo_colors::{OwoColorize, Stream};
#[cfg(feature = "spantrace")]
use tracing_error::SpanTrace;
use crate::{
fmt::hook::{into_boxed_hook, BoxedHook, HookContext},
Frame, Report,
};
pub(crate) fn install_builtin_hooks() {
static INSTALL_BUILTIN: Once = Once::new();
static INSTALL_BUILTIN_RUNNING: AtomicBool = AtomicBool::new(false);
if INSTALL_BUILTIN.is_completed() || INSTALL_BUILTIN_RUNNING.load(Ordering::Acquire) {
return;
}
INSTALL_BUILTIN.call_once(|| {
INSTALL_BUILTIN_RUNNING.store(true, Ordering::Release);
Report::install_debug_hook::<Location>(location);
#[cfg(rust_1_65)]
Report::install_debug_hook::<Backtrace>(backtrace);
#[cfg(feature = "spantrace")]
Report::install_debug_hook::<SpanTrace>(span_trace);
});
}
fn location(location: &Location<'static>, context: &mut HookContext<Location<'static>>) {
#[cfg(feature = "pretty-print")]
context.push_body(format!(
"{}",
location.if_supports_color(Stream::Stdout, OwoColorize::bright_black)
));
#[cfg(not(feature = "pretty-print"))]
context.push_body(format!("at {location}"));
}
#[cfg(rust_1_65)]
fn backtrace(backtrace: &Backtrace, context: &mut HookContext<Backtrace>) {
let idx = context.increment_counter();
context.push_appendix(format!("backtrace no. {}\n{backtrace}", idx + 1));
#[cfg(nightly)]
context.push_body(format!(
"backtrace with {} frames ({})",
backtrace.frames().len(),
idx + 1
));
#[cfg(not(nightly))]
context.push_body(format!("backtrace ({})", idx + 1));
}
#[cfg(feature = "spantrace")]
fn span_trace(span_trace: &SpanTrace, context: &mut HookContext<SpanTrace>) {
let idx = context.increment_counter();
let mut span = 0;
span_trace.with_spans(|_, _| {
span += 1;
true
});
context.push_appendix(format!("span trace No. {}\n{span_trace}", idx + 1));
context.push_body(format!("span trace with {span} frames ({})", idx + 1));
}
}