log_panics/
lib.rs

1//! A crate which logs panics instead of writing to standard error.
2//!
3//! The format used is identical to the standard library's.
4//!
5//! Because logging with a backtrace requires additional dependencies,
6//! the `with-backtrace` feature must be enabled. You can add the
7//! following in your `Cargo.toml`:
8//!
9//! ```toml
10//! log-panics = { version = "2", features = ["with-backtrace"]}
11//! ```
12//!
13//! To use, call [`log_panics::init()`](init) somewhere early in execution,
14//! such as immediately after initializing `log`, or use the [`Config`]
15//! builder for more customization.
16
17#![doc(html_root_url = "https://docs.rs/log-panics/2.0.0")]
18#![warn(missing_docs)]
19
20// Enable feature requirements on docs.rs.
21#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
22
23#[macro_use]
24extern crate log;
25
26#[cfg(feature = "with-backtrace")]
27extern crate backtrace;
28
29use std::{fmt, panic, thread};
30
31use backtrace::Backtrace;
32
33#[cfg(not(feature = "with-backtrace"))]
34mod backtrace {
35    #[derive(Default)]
36    pub struct Backtrace;
37}
38
39struct Shim(Backtrace);
40
41impl fmt::Debug for Shim {
42    #[cfg(feature = "with-backtrace")]
43    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
44        if !self.0.frames().is_empty() {
45            write!(fmt, "\n{:?}", self.0)
46        } else {
47            Ok(())
48        }
49    }
50
51    #[inline]
52    #[cfg(not(feature = "with-backtrace"))]
53    fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
54        Ok(())
55    }
56}
57
58/// Determines how backtraces will be displayed.
59#[cfg(feature = "with-backtrace")]
60#[derive(Debug, PartialEq, Eq, Clone, Copy)]
61pub enum BacktraceMode {
62    /// Backtraces will be omitted from the log.
63    Off,
64    /// Backtraces will include addresses, but no symbol names or locations.
65    Unresolved,
66    /// Backtraces will include addresses as well as symbol names and locations when possible.
67    Resolved
68}
69
70/// Configures the panic hook, ending with initialization.
71///
72/// ## Example
73///
74/// ```
75/// # #[cfg(feature = "with-backtrace")]
76/// log_panics::Config::new()
77///     .backtrace_mode(log_panics::BacktraceMode::Unresolved)
78///     .install_panic_hook()
79/// ```
80#[derive(Debug)]
81pub struct Config {
82    // We store a constructor function instead of a BacktraceMode enum
83    // so that inlining can eliminate references to `Backtrace::default`
84    // if symbolication is not desired.
85    make_backtrace: fn() -> Backtrace,
86}
87
88impl Config {
89    /// Initializes the builder with the default set of features.
90    pub fn new() -> Self {
91        Self {
92            make_backtrace: Backtrace::default,
93        }
94    }
95
96    /// Controls how backtraces are displayed.
97    ///
98    /// The default when backtraces are enabled is [`BacktraceMode::Resolved`].
99    #[cfg(feature = "with-backtrace")]
100    pub fn backtrace_mode(mut self, mode: BacktraceMode) -> Self {
101        self.make_backtrace = match mode {
102            BacktraceMode::Off => || Backtrace::from(vec![]),
103            BacktraceMode::Unresolved => Backtrace::new_unresolved,
104            BacktraceMode::Resolved => Backtrace::default,
105        };
106        self
107    }
108
109    /// Initializes the panic hook.
110    ///
111    /// After this method is called, all panics will be logged rather than printed
112    /// to standard error.
113    pub fn install_panic_hook(self) {
114        panic::set_hook(Box::new(move |info| {
115            let backtrace = (self.make_backtrace)();
116
117            let thread = thread::current();
118            let thread = thread.name().unwrap_or("<unnamed>");
119
120            let msg = match info.payload().downcast_ref::<&'static str>() {
121                Some(s) => *s,
122                None => match info.payload().downcast_ref::<String>() {
123                    Some(s) => &**s,
124                    None => "Box<Any>",
125                },
126            };
127
128            match info.location() {
129                Some(location) => {
130                    error!(
131                        target: "panic", "thread '{}' panicked at '{}': {}:{}{:?}",
132                        thread,
133                        msg,
134                        location.file(),
135                        location.line(),
136                        Shim(backtrace)
137                    );
138                }
139                None => error!(
140                    target: "panic",
141                    "thread '{}' panicked at '{}'{:?}",
142                    thread,
143                    msg,
144                    Shim(backtrace)
145                ),
146            }
147        }));
148    }
149}
150
151impl Default for Config {
152    fn default() -> Self {
153        Self::new()
154    }
155}
156
157/// Initializes the panic hook with the default settings.
158///
159/// After this method is called, all panics will be logged rather than printed
160/// to standard error.
161///
162/// See [`Config`] for more information.
163pub fn init() {
164    Config::new().install_panic_hook()
165}