1#![doc = include_str!("../README.md")]
2
3use std::{backtrace, panic, thread};
4
5pub struct Configuration {
6 pub force_capture: bool,
11
12 pub keep_original_hook: bool,
15
16 pub logger: Option<&'static dyn log::Log>,
18}
19
20impl Default for Configuration {
21 fn default() -> Self {
22 Self {
23 force_capture: false,
24 keep_original_hook: true,
25 logger: None,
26 }
27 }
28}
29
30pub fn initialize_hook(config: Configuration) {
31 let original_hook = if config.keep_original_hook {
32 Some(panic::take_hook())
33 } else {
34 None
35 };
36 panic::set_hook(Box::new(move |info| {
37 let thread_name = thread::current()
38 .name()
39 .unwrap_or("<unnamed thread>")
40 .to_owned();
41
42 let location = if let Some(panic_location) = info.location() {
43 format!(
44 "{}:{}:{}",
45 panic_location.file(),
46 panic_location.line(),
47 panic_location.column()
48 )
49 } else {
50 "<unknown location>".to_owned()
51 };
52 let message = info
53 .payload()
54 .downcast_ref::<&str>()
55 .copied()
56 .or_else(|| info.payload().downcast_ref::<String>().map(|s| s.as_str()))
57 .unwrap_or("<message is not a string>");
58
59 let backtrace = if config.force_capture {
60 backtrace::Backtrace::force_capture()
61 } else {
62 backtrace::Backtrace::capture()
63 };
64
65 log::error!("thread '{thread_name}' panicked at {location}:\n{message}\nstack backtrace:\n{backtrace}");
66
67 if let Some(logger) = config.logger {
68 logger.flush();
69 }
70
71 if let Some(original_hook) = &original_hook {
72 original_hook(info);
73 }
74 }));
75}