hook module¶
Three functions, all returning (), all safe to call any number of times in
any order:
pub fn install_report_handler();
pub fn install_panic_hook();
pub fn install_with_footer<F>(footer: F)
where
F: Fn() -> String + Send + Sync + 'static;
When must the hook be installed?¶
Before the first miette::Report in the process exists. This is the one
rule that catches people, and getting it wrong fails silently.
miette keeps its error hook in a process-global OnceLock. Constructing a
Report calls HOOK.get_or_init(…), which installs miette's default
handler if nothing is there yet. From that moment the slot is full and
miette::set_hook returns InstallError forever — so install_report_handler
becomes a no-op, and any footer you register is never read.
Creating the Report is enough. You do not have to render it, print it, or
even keep it:
let _ = miette::Report::new(SomeError); // hook slot is now sealed
rtb_error::hook::install_with_footer(|| "support: …".into());
// the footer will never appear
Put the install calls at the top of main, before any fallible work. If a
library you depend on builds a Report during its own initialisation, it wins
and there is nothing this crate can do about it.
install_report_handler¶
Installs rtb-error's ReportHandler as miette's global hook.
- Idempotent. Calling it twice is harmless; the second call's
miette::set_hookresult is discarded. - First caller wins. If a hook is already installed — by an earlier call,
by
install_with_footer, bymiette::set_hookin your own code, or bymietteitself as described above — this call does nothing and the existing hook stays. - No return value and no error. There is no way to ask whether it took
effect. If you need certainty, call it as the first statement in
main.
install_panic_hook¶
Calls miette::set_panic_hook, which replaces the standard panic hook with one
that builds a Report from the panic payload and prints it to stderr with
Error: {:?} — so panics render through whichever miette hook is installed,
including this crate's, footer and all.
- Idempotent, and last caller wins.
std::panic::set_hookoverwrites unconditionally, unlikemiette::set_hook. Calling this after your ownset_hookdiscards yours. - The rendered panic carries the panic message, the source location as a
cause, and the fixed help line
set the `RUST_BACKTRACE=1` environment variable to display a backtrace. SettingRUST_BACKTRACE=1adds the backtrace to the message. catch_unwindstill works. Installing the hook changes what is printed, not whether the panic unwinds.- The exit code is unchanged — a panicking process still exits 101. The
panic hook has no interaction with
exit_code.
install_with_footer¶
Installs the report handler (via install_report_handler, with the same
first-caller-wins rule) and registers a closure whose output is appended to
every rendered diagnostic.
- The closure runs on every render, not once at install time. It can return different text each time — a request ID, a support channel that depends on config loaded later.
- Replacing the footer is allowed; the most recent call wins. This is the
reason the crate wraps
miette's set-once hook rather than using it directly. - Returning an empty string suppresses the footer for that render. No blank line is emitted.
- A panicking closure is caught and the footer is dropped for that render. The diagnostic body still renders. Nothing is logged about the suppression — if the panic hook is installed it will print the panic itself, and if it is not the panic is silent. A footer closure that panics is therefore very easy to miss; keep the closure trivial.
- The footer is separated from the diagnostic by one blank line, and followed by a newline.
Footer output shape¶
Error: greet::no_name
x no name given
help: pass a name: `greet Ada`
Report bugs at https://example.invalid/greet/issues
What changes about rendering when you install this handler¶
rtb-error installs miette::GraphicalReportHandler::new() and appends the
footer. miette's own default is MietteHandler, which is configured
differently. Installing this crate's handler changes three things, and all
three are worth knowing before you do it:
| Behaviour | miette default (MietteHandler) |
After install_report_handler |
|---|---|---|
| Wrap width | terminal width, or 80 when it cannot be detected | fixed at 200 columns |
NO_GRAPHICS env var |
any value other than 0 switches to the narratable (screen-reader friendly) renderer |
ignored; always graphical |
| Terminal hyperlinks | emitted only when the terminal is detected as supporting OSC-8 | always emitted for diagnostics carrying a url(…) |
Colour and Unicode box-drawing still adapt: GraphicalTheme::default() falls
back to plain ASCII with no colour when stdout or stderr is not a terminal, and
honours NO_COLOR.
The practical effect of the width row is that long diagnostic messages wrap at
200 columns rather than at your terminal, so in an 80-column terminal they wrap
twice — once by the handler, once by the terminal. There is no option to change
it; the width is hard-coded in GraphicalReportHandler::new().
Thread safety¶
The footer slot is a static OnceLock<RwLock<Option<Footer>>>. Installs take
the write lock, renders take the read lock, and the read guard is released
before anything is written to the formatter. The footer closure is required to
be Send + Sync + 'static because it is called from whichever thread is
rendering.
If the lock is poisoned, install_with_footer panics with footer lock
poisoned — another thread panicked mid-update, and rendering silently skips
the footer rather than panicking a second time.