#![cfg_attr(not(feature = "std"), allow(unreachable_pub))]
use alloc::{boxed::Box, string::String, vec::Vec};
use core::{any::TypeId, mem};
pub(crate) use default::install_builtin_hooks;
use crate::fmt::{ColorMode, Frame, charset::Charset};
pub(crate) struct Format {
alternate: bool,
color: ColorMode,
charset: Charset,
body: Vec<String>,
appendix: Vec<String>,
}
impl Format {
pub(crate) const fn new(alternate: bool, color: ColorMode, charset: Charset) -> Self {
Self {
alternate,
color,
charset,
body: Vec::new(),
appendix: Vec::new(),
}
}
fn appendix(&self) -> &[String] {
&self.appendix
}
fn take_body(&mut self) -> Vec<String> {
mem::take(&mut self.body)
}
}
crate::hook::context::impl_hook_context! {
#[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"))]
pub struct HookContext<Format> { .. }
}
impl<T> HookContext<T> {
pub(crate) fn appendix(&self) -> &[String] {
self.inner().extra().appendix()
}
#[must_use]
pub const fn color_mode(&self) -> ColorMode {
self.inner().extra().color
}
#[must_use]
pub const fn charset(&self) -> Charset {
self.inner().extra().charset
}
#[cfg_attr(doc, 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_mut().extra_mut().appendix.push(content.into());
}
#[cfg_attr(doc, 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_mut().extra_mut().body.push(content.into());
}
#[must_use]
pub const fn alternate(&self) -> bool {
self.inner().extra().alternate
}
pub(crate) fn take_body(&mut self) -> Vec<String> {
self.inner_mut().extra_mut().take_body()
}
}
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()
})
}
#[expect(clippy::field_scoped_visibility_modifiers)]
pub(crate) struct Hooks {
pub(crate) inner: Vec<(TypeId, BoxedHook)>,
}
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 {
#[cfg(any(feature = "backtrace", feature = "spantrace"))]
use alloc::format;
use alloc::string::ToString as _;
use core::{
panic::Location,
sync::atomic::{AtomicBool, Ordering},
};
#[cfg(feature = "backtrace")]
use std::backtrace::Backtrace;
#[cfg(feature = "std")]
use std::sync::Once;
#[cfg(all(not(feature = "std"), feature = "hooks"))]
use spin::once::Once;
#[cfg(feature = "spantrace")]
use tracing_error::SpanTrace;
use crate::{
Report,
fmt::{hook::HookContext, location::LocationAttachment},
};
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(feature = "backtrace")]
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>>) {
context.push_body(LocationAttachment::new(location, context.color_mode()).to_string());
}
#[cfg(feature = "backtrace")]
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));
}
}