panic_log/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{backtrace, panic, thread};
4
5pub struct Configuration {
6    /// Always force capture a backtrace.
7    ///
8    /// If false, the presence of a backtrace will depend on the value of `RUST_BACKTRACE`.
9    /// See [`std::backtrace::Backtrace`] for more info
10    pub force_capture: bool,
11
12    /// Keep the originally set panic hook, continuing any normal panic behaviour
13    /// and custom panic behaviour set.
14    pub keep_original_hook: bool,
15
16    /// When set, flush the given logger after we invoke `log::error!()`
17    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}