From ff84554ed42da1812581dc2ae7f6b521d3530ad3 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 14 Sep 2020 18:54:35 -0700 Subject: [PATCH] Add support for customized panic message sections (#57) * (cargo-release) start next development iteration 0.5.3-alpha.0 * add support for customized panic messages * reorg a little * add example and changelog * reorder items in example * fix missing semi --- CHANGELOG.md | 3 ++ src/config.rs | 105 ++++++++++++++++++++++++++++++++++++--------- src/handler.rs | 4 +- src/section/mod.rs | 6 +++ 4 files changed, 95 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbb3ab8..784bd42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- add `panic_section` method to `HookBuilder` for overriding the printer for + the panic message at the start of panic reports ## [0.5.2] - 2020-08-31 ### Added diff --git a/src/config.rs b/src/config.rs index ecd2142..e1848c6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,9 @@ //! Configuration options for customizing the behavior of the provided panic //! and error reporting hooks -use crate::writers::{EnvSection, WriterExt}; +use crate::{ + section::PanicMessage, + writers::{EnvSection, WriterExt}, +}; use fmt::Display; use indenter::{indented, Format}; use owo_colors::OwoColorize; @@ -246,6 +249,7 @@ pub struct HookBuilder { capture_span_trace_by_default: bool, display_env_section: bool, panic_section: Option>, + panic_message: Box, } impl HookBuilder { @@ -279,6 +283,7 @@ impl HookBuilder { capture_span_trace_by_default: false, display_env_section: true, panic_section: None, + panic_message: Box::new(DefaultPanicMessage), } } @@ -298,6 +303,63 @@ impl HookBuilder { self } + /// Overrides the main error message printing section at the start of panic + /// reports + /// + /// # Examples + /// + /// ```rust + /// use std::{panic::Location, fmt}; + /// use color_eyre::section::PanicMessage; + /// use owo_colors::OwoColorize; + /// + /// struct MyPanicMessage; + /// + /// color_eyre::config::HookBuilder::default() + /// .panic_message(MyPanicMessage) + /// .install() + /// .unwrap(); + /// + /// impl PanicMessage for MyPanicMessage { + /// fn display(&self, pi: &std::panic::PanicInfo<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// writeln!(f, "{}", "The application panicked (crashed).".red())?; + /// + /// // Print panic message. + /// let payload = pi + /// .payload() + /// .downcast_ref::() + /// .map(String::as_str) + /// .or_else(|| pi.payload().downcast_ref::<&str>().cloned()) + /// .unwrap_or(""); + /// + /// write!(f, "Message: ")?; + /// writeln!(f, "{}", payload.cyan())?; + /// + /// // If known, print panic location. + /// write!(f, "Location: ")?; + /// if let Some(loc) = pi.location() { + /// write!(f, "{}", loc.file().purple())?; + /// write!(f, ":")?; + /// write!(f, "{}", loc.line().purple())?; + /// + /// write!(f, "\n\nConsider reporting the bug at {}", custom_url(loc, payload))?; + /// } else { + /// write!(f, "")?; + /// } + /// + /// Ok(()) + /// } + /// } + /// + /// fn custom_url(location: &Location<'_>, message: &str) -> impl fmt::Display { + /// "todo" + /// } + /// ``` + pub fn panic_message(mut self, section: S) -> Self { + self.panic_message = Box::new(section); + self + } + /// Configures the default capture mode for `SpanTraces` in error reports and panics pub fn capture_span_trace_by_default(mut self, cond: bool) -> Self { self.capture_span_trace_by_default = cond; @@ -367,6 +429,7 @@ impl HookBuilder { #[cfg(feature = "capture-spantrace")] capture_span_trace_by_default: self.capture_span_trace_by_default, display_env_section: self.display_env_section, + panic_message: self.panic_message, }; let eyre_hook = EyreHook { @@ -379,6 +442,7 @@ impl HookBuilder { } } +#[allow(missing_docs)] impl Default for HookBuilder { fn default() -> Self { Self::new() @@ -433,12 +497,11 @@ fn install_panic_hook() { std::panic::set_hook(Box::new(|pi| eprintln!("{}", PanicPrinter(pi)))) } -struct PanicMessage<'a>(&'a PanicPrinter<'a>); +struct DefaultPanicMessage; -impl fmt::Display for PanicMessage<'_> { - fn fmt(&self, out: &mut fmt::Formatter<'_>) -> fmt::Result { - let pi = (self.0).0; - writeln!(out, "{}", "The application panicked (crashed).".red())?; +impl PanicMessage for DefaultPanicMessage { + fn display(&self, pi: &std::panic::PanicInfo<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "{}", "The application panicked (crashed).".red())?; // Print panic message. let payload = pi @@ -448,17 +511,17 @@ impl fmt::Display for PanicMessage<'_> { .or_else(|| pi.payload().downcast_ref::<&str>().cloned()) .unwrap_or(""); - write!(out, "Message: ")?; - writeln!(out, "{}", payload.cyan())?; + write!(f, "Message: ")?; + writeln!(f, "{}", payload.cyan())?; // If known, print panic location. - write!(out, "Location: ")?; + write!(f, "Location: ")?; if let Some(loc) = pi.location() { - write!(out, "{}", loc.file().purple())?; - write!(out, ":")?; - write!(out, "{}", loc.line().purple())?; + write!(f, "{}", loc.file().purple())?; + write!(f, ":")?; + write!(f, "{}", loc.line().purple())?; } else { - write!(out, "")?; + write!(f, "")?; } Ok(()) @@ -466,14 +529,13 @@ impl fmt::Display for PanicMessage<'_> { } fn print_panic_info(printer: &PanicPrinter<'_>, out: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(out, "{}", PanicMessage(printer))?; + let hook = installed_hook(); + hook.panic_message.display(printer.0, out)?; let v = panic_verbosity(); - let printer = installed_printer(); - #[cfg(feature = "capture-spantrace")] - let span_trace = if printer.spantrace_capture_enabled() { + let span_trace = if hook.spantrace_capture_enabled() { Some(tracing_error::SpanTrace::capture()) } else { None @@ -481,7 +543,7 @@ fn print_panic_info(printer: &PanicPrinter<'_>, out: &mut fmt::Formatter<'_>) -> let mut separated = out.header("\n\n"); - if let Some(ref section) = printer.section { + if let Some(ref section) = hook.section { write!(&mut separated.ready(), "{}", section)?; } @@ -500,7 +562,7 @@ fn print_panic_info(printer: &PanicPrinter<'_>, out: &mut fmt::Formatter<'_>) -> if capture_bt { let bt = backtrace::Backtrace::new(); - let fmted_bt = printer.format_backtrace(&bt); + let fmted_bt = hook.format_backtrace(&bt); write!( indented(&mut separated.ready()).with_format(Format::Uniform { indentation: " " }), "{}", @@ -508,7 +570,7 @@ fn print_panic_info(printer: &PanicPrinter<'_>, out: &mut fmt::Formatter<'_>) -> )?; } - if printer.display_env_section { + if hook.display_env_section { let env_section = EnvSection { bt_captured: &capture_bt, #[cfg(feature = "capture-spantrace")] @@ -524,6 +586,7 @@ fn print_panic_info(printer: &PanicPrinter<'_>, out: &mut fmt::Formatter<'_>) -> pub(crate) struct PanicHook { filters: Vec>, section: Option>, + panic_message: Box, #[cfg(feature = "capture-spantrace")] capture_span_trace_by_default: bool, display_env_section: bool, @@ -671,7 +734,7 @@ impl fmt::Display for BacktraceFormatter<'_> { } } -pub(crate) fn installed_printer() -> &'static PanicHook { +pub(crate) fn installed_hook() -> &'static PanicHook { crate::CONFIG.get_or_init(default_printer) } diff --git a/src/handler.rs b/src/handler.rs index e8a4260..4711a02 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,4 +1,4 @@ -use crate::config::installed_printer; +use crate::config::installed_hook; use crate::{ section::help::HelpInfo, writers::{EnvSection, WriterExt}, @@ -86,7 +86,7 @@ impl eyre::EyreHandler for Handler { } if let Some(backtrace) = self.backtrace.as_ref() { - let fmted_bt = installed_printer().format_backtrace(&backtrace); + let fmted_bt = installed_hook().format_backtrace(&backtrace); write!( indented(&mut separated.ready()).with_format(Format::Uniform { indentation: " " }), diff --git a/src/section/mod.rs b/src/section/mod.rs index ada3380..2f39918 100644 --- a/src/section/mod.rs +++ b/src/section/mod.rs @@ -314,3 +314,9 @@ pub trait Section: crate::private::Sealed { D: Display + Send + Sync + 'static, F: FnOnce() -> D; } + +/// Trait for printing a panic error message for the given PanicInfo +pub trait PanicMessage: Send + Sync + 'static { + /// Display trait equivalent for implementing the display logic + fn display(&self, pi: &std::panic::PanicInfo<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result; +}