use backtrace::Backtrace;
use std::borrow::Cow;
use std::fmt::Debug;
use std::panic::{PanicInfo, UnwindSafe};
use std::thread::ThreadId;
use std::cmp::Ordering;
use std::ops::DerefMut;
#[cfg(feature = "use-parking-lot")]
use parking_lot::Mutex;
#[cfg(not(feature = "use-parking-lot"))]
use std::sync::Mutex;
const DEFAULT_BACKTRACE_RESOLUTION_LIMIT: usize = 8;
lazy_static::lazy_static! {
static ref STATE: Mutex<State> = Mutex::new(State::default());
}
macro_rules! log_warn {
($state:expr, $($arg:tt)+) => {
#[cfg(feature = "use-slog")]
slog::warn!(&$state.slogger, $($arg)+);
#[cfg(feature = "use-log")]
log::warn!($($arg)+);
#[cfg(feature = "use-stderr")]
eprintln!($($arg)+);
}
}
macro_rules! log_error {
($state:expr, $($arg:tt)+) => {
#[cfg(feature = "use-slog")]
slog::error!(&$state.slogger, $($arg)+);
#[cfg(feature = "use-log")]
log::error!($($arg)+);
#[cfg(feature = "use-stderr")]
eprintln!($($arg)+);
}
}
macro_rules! log_crit {
($state:expr, $($arg:tt)+) => {
#[cfg(feature = "use-slog")]
slog::crit!(&$state.slogger, $($arg)+);
#[cfg(feature = "use-log")]
log::error!($($arg)+);
#[cfg(feature = "use-stderr")]
eprintln!($($arg)+);
}
}
struct State {
panics: Vec<Panic>,
backtrace_resolution_limit: usize,
is_running: bool,
#[cfg(feature = "use-slog")]
slogger: slog::Logger,
}
#[derive(Debug, Clone)]
pub struct Panic {
message: String,
thread_id: ThreadId,
thread: String,
backtrace: Backtrace,
backtrace_resolved: bool,
}
#[derive(Clone)]
pub struct Builder {
#[cfg(feature = "use-slog")]
slogger: Option<slog::Logger>,
backtrace_resolution_limit: usize,
}
struct GlobalStateGuard;
impl Builder {
pub fn new() -> Self {
Builder {
#[cfg(feature = "use-slog")]
slogger: None,
backtrace_resolution_limit: DEFAULT_BACKTRACE_RESOLUTION_LIMIT,
}
}
#[cfg(feature = "use-slog")]
pub fn slogger(mut self, slogger: impl Into<slog::Logger>) -> Self {
self.slogger = Some(slogger.into());
self
}
pub fn backtrace_resolution_limit(mut self, n: usize) -> Self {
self.backtrace_resolution_limit = n;
self
}
fn apply_settings(&mut self) {
let mut state = state_mutex();
#[cfg(feature = "use-slog")]
{
state.slogger = self.slogger.take().unwrap_or_else(default_slogger);
}
state.backtrace_resolution_limit = self.backtrace_resolution_limit;
}
pub fn run_and_handle_panics<R: Debug>(
mut self,
do_me: impl FnOnce() -> R + UnwindSafe,
) -> Option<R> {
self.apply_settings();
run_and_handle_panics(do_me)
}
pub fn run_and_handle_panics_no_debug<R>(
mut self,
do_me: impl FnOnce() -> R + UnwindSafe,
) -> Option<R> {
self.apply_settings();
run_and_handle_panics_no_debug(do_me)
}
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
fn register_panic(panic: &PanicInfo) {
let (thread, tid) = {
let t = std::thread::current();
let name = t.name().unwrap_or("<unnamed>");
(format!("{:?} ({})", t.id(), name), t.id())
};
let message = panic
.payload()
.downcast_ref::<&str>()
.map(|s| Cow::Borrowed(*s))
.unwrap_or_else(|| Cow::from(format!("{}", panic)));
let backtrace = Backtrace::new_unresolved();
let mut state = state_mutex();
log_error!(&state, "handling panic on thread {}: '{}'", thread, message);
state.panics.push(Panic {
message: message.into_owned(),
thread_id: tid,
thread,
backtrace,
backtrace_resolved: false,
});
}
fn state_mutex() -> impl DerefMut<Target = State> {
#[cfg(feature = "use-parking-lot")]
return STATE.lock();
#[cfg(not(feature = "use-parking-lot"))]
STATE.lock().unwrap()
}
pub fn run_and_handle_panics_no_debug<R>(do_me: impl FnOnce() -> R + UnwindSafe) -> Option<R> {
run_and_handle_panics_with_maybe_debug(do_me, |_| Cow::Borrowed("<unprintable>"))
}
pub fn run_and_handle_panics<R: Debug>(do_me: impl FnOnce() -> R + UnwindSafe) -> Option<R> {
run_and_handle_panics_with_maybe_debug(do_me, |res| Cow::Owned(format!("{:?}", res)))
}
fn run_and_handle_panics_with_maybe_debug<R>(
do_me: impl FnOnce() -> R + UnwindSafe,
format_swallowed: impl FnOnce(R) -> Cow<'static, str>,
) -> Option<R> {
let _guard = GlobalStateGuard::init();
let result = std::panic::catch_unwind(|| do_me());
let mut state = state_mutex();
match (result, state.panics.is_empty()) {
(Ok(res), true) => {
return Some(res);
}
(Ok(res), false) => {
let swallowed = format_swallowed(res);
log_warn!(
&state,
"panic occurred in another thread, swallowing unpanicked result: {}",
swallowed
);
}
(Err(_), false) => {}
(Err(_), true) => unreachable!(),
};
log_error!(
&state,
"{count} threads panicked",
count = state.panics.len()
);
let backtrace_resolution_limit = state.backtrace_resolution_limit;
let mut panics = std::mem::take(&mut state.panics);
debug_assert!(!panics.is_empty(), "panics vec should not be empty");
for (
i,
Panic {
message,
thread,
ref mut backtrace,
backtrace_resolved,
..
},
) in panics.iter_mut().enumerate()
{
match i.cmp(&backtrace_resolution_limit) {
Ordering::Less => {
backtrace.resolve();
*backtrace_resolved = true;
}
Ordering::Equal => {
#[cfg(feature = "use-log")]
log::warn!(
"handling more than {limit} panics, no longer resolving backtraces",
limit = backtrace_resolution_limit
);
#[cfg(feature = "use-stderr")]
eprintln!(
"handling more than {limit} panics, no longer resolving backtraces",
limit = backtrace_resolution_limit
);
}
_ => {}
};
if *backtrace_resolved {
log_crit!(
&state,
"panic on thread {:?}: {:?}\n{:?}",
thread,
message,
backtrace
);
} else {
log_crit!(&state, "panic on thread {:?}: {:?}", thread, message,);
}
}
let empty = std::mem::replace(&mut state.panics, panics);
debug_assert!(empty.is_empty());
std::mem::forget(empty);
None
}
pub fn panics() -> Vec<Panic> {
let state = state_mutex();
state.panics.clone() }
pub fn has_panicked() -> bool {
!state_mutex().panics.is_empty()
}
impl Panic {
pub fn is_backtrace_resolved(&self) -> bool {
self.backtrace_resolved
}
pub fn message(&self) -> &str {
&self.message
}
pub fn thread_id(&self) -> ThreadId {
self.thread_id
}
pub fn thread_name(&self) -> &str {
&self.thread
}
pub fn backtrace(&self) -> &Backtrace {
&self.backtrace
}
}
impl GlobalStateGuard {
fn init() -> Self {
let mut state = state_mutex();
if state.is_running {
drop(state); panic!("nested calls to panik::run_and_handle_panics are not supported")
}
state.panics.clear();
state.is_running = true;
std::panic::set_hook(Box::new(|panic| {
register_panic(panic);
}));
Self
}
}
impl Drop for GlobalStateGuard {
fn drop(&mut self) {
let _ = std::panic::take_hook();
let mut state = state_mutex();
state.backtrace_resolution_limit = DEFAULT_BACKTRACE_RESOLUTION_LIMIT;
state.is_running = false;
#[cfg(feature = "use-slog")]
{
state.slogger = default_slogger();
}
}
}
impl Default for State {
fn default() -> Self {
State {
panics: Vec::new(),
backtrace_resolution_limit: DEFAULT_BACKTRACE_RESOLUTION_LIMIT,
is_running: false,
#[cfg(feature = "use-slog")]
slogger: default_slogger(),
}
}
}
#[cfg(feature = "use-slog")]
fn default_slogger() -> slog::Logger {
use slog::Drain;
slog::Logger::root(slog_stdlog::StdLog.fuse(), slog::o!())
}