use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader, ErrorKind};
use std::panic::PanicInfo;
use std::path::PathBuf;
use std::sync::Mutex;
use termcolor::{Ansi, Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
#[cfg(feature = "failure-bt")]
pub mod failure;
pub use termcolor;
type IOResult<T = ()> = Result<T, std::io::Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Verbosity {
Minimal,
Medium,
Full,
}
impl Verbosity {
pub fn from_env() -> Self {
Self::convert_env(env::var("RUST_BACKTRACE").ok())
}
pub fn lib_from_env() -> Self {
Self::convert_env(
env::var("RUST_LIB_BACKTRACE")
.or_else(|_| env::var("RUST_BACKTRACE"))
.ok(),
)
}
fn convert_env(env: Option<String>) -> Self {
match env {
Some(ref x) if x == "full" => Verbosity::Full,
Some(_) => Verbosity::Medium,
None => Verbosity::Minimal,
}
}
}
pub fn install() {
BacktracePrinter::default().install(default_output_stream());
}
pub fn default_output_stream() -> Box<StandardStream> {
Box::new(StandardStream::stderr(if atty::is(atty::Stream::Stderr) {
ColorChoice::Always
} else {
ColorChoice::Never
}))
}
#[deprecated(
since = "0.4.0",
note = "Use `BacktracePrinter::into_panic_handler()` instead."
)]
pub fn create_panic_handler(
printer: BacktracePrinter,
) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
let out_stream_mutex = Mutex::new(default_output_stream());
Box::new(move |pi| {
let mut lock = out_stream_mutex.lock().unwrap();
if let Err(e) = printer.print_panic_info(pi, &mut *lock) {
eprintln!("Error while printing panic: {:?}", e);
}
})
}
#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::install()` instead.")]
pub fn install_with_settings(printer: BacktracePrinter) {
std::panic::set_hook(printer.into_panic_handler(default_output_stream()))
}
pub type FilterCallback = dyn Fn(&mut Vec<&Frame>) + Send + Sync + 'static;
#[derive(Debug)]
pub struct Frame {
pub n: usize,
pub name: Option<String>,
pub lineno: Option<u32>,
pub filename: Option<PathBuf>,
_private_ctor: (),
}
impl Frame {
fn is_dependency_code(&self) -> bool {
const SYM_PREFIXES: &[&str] = &[
"std::",
"core::",
"backtrace::backtrace::",
"_rust_begin_unwind",
"color_traceback::",
"__rust_",
"___rust_",
"__pthread",
"_main",
"main",
"__scrt_common_main_seh",
"BaseThreadInitThunk",
"_start",
"__libc_start_main",
"start_thread",
];
if let Some(ref name) = self.name {
if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
return true;
}
}
const FILE_PREFIXES: &[&str] = &[
"/rustc/",
"src/libstd/",
"src/libpanic_unwind/",
"src/libtest/",
];
if let Some(ref filename) = self.filename {
let filename = filename.to_string_lossy();
if FILE_PREFIXES.iter().any(|x| filename.starts_with(x))
|| filename.contains("/.cargo/registry/src/")
{
return true;
}
}
false
}
fn is_post_panic_code(&self) -> bool {
const SYM_PREFIXES: &[&str] = &[
"_rust_begin_unwind",
"rust_begin_unwind",
"core::result::unwrap_failed",
"core::option::expect_none_failed",
"core::panicking::panic_fmt",
"color_backtrace::create_panic_handler",
"std::panicking::begin_panic",
"begin_panic_fmt",
"failure::backtrace::Backtrace::new",
"backtrace::capture",
"failure::error_message::err_msg",
"<failure::error::Error as core::convert::From<F>>::from",
];
match self.name.as_ref() {
Some(name) => SYM_PREFIXES.iter().any(|x| name.starts_with(x)),
None => false,
}
}
fn is_runtime_init_code(&self) -> bool {
const SYM_PREFIXES: &[&str] =
&["std::rt::lang_start::", "test::run_test::run_test_inner::"];
let (name, file) = match (self.name.as_ref(), self.filename.as_ref()) {
(Some(name), Some(filename)) => (name, filename.to_string_lossy()),
_ => return false,
};
if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
return true;
}
if name == "{{closure}}" && file == "src/libtest/lib.rs" {
return true;
}
false
}
fn print_source_if_avail(&self, mut out: impl WriteColor, s: &BacktracePrinter) -> IOResult {
let (lineno, filename) = match (self.lineno, self.filename.as_ref()) {
(Some(a), Some(b)) => (a, b),
_ => return Ok(()),
};
let file = match File::open(filename) {
Ok(file) => file,
Err(ref e) if e.kind() == ErrorKind::NotFound => return Ok(()),
e @ Err(_) => e?,
};
let reader = BufReader::new(file);
let start_line = lineno - 2.min(lineno - 1);
let surrounding_src = reader.lines().skip(start_line as usize - 1).take(5);
for (line, cur_line_no) in surrounding_src.zip(start_line..) {
if cur_line_no == lineno {
out.set_color(&s.colors.selected_src_ln)?;
writeln!(out, "{:>8} > {}", cur_line_no, line?)?;
out.reset()?;
} else {
writeln!(out, "{:>8} │ {}", cur_line_no, line?)?;
}
}
Ok(())
}
fn print(&self, i: usize, out: &mut impl WriteColor, s: &BacktracePrinter) -> IOResult {
let is_dependency_code = self.is_dependency_code();
write!(out, "{:>2}: ", i)?;
let name = self.name.as_deref().unwrap_or("<unknown>");
let has_hash_suffix = name.len() > 19
&& &name[name.len() - 19..name.len() - 16] == "::h"
&& name[name.len() - 16..].chars().all(|x| x.is_digit(16));
out.set_color(if is_dependency_code {
&s.colors.dependency_code
} else {
&s.colors.crate_code
})?;
if has_hash_suffix {
write!(out, "{}", &name[..name.len() - 19])?;
if s.strip_function_hash {
writeln!(out)?;
} else {
out.set_color(if is_dependency_code {
&s.colors.dependency_code_hash
} else {
&s.colors.crate_code_hash
})?;
writeln!(out, "{}", &name[name.len() - 19..])?;
}
} else {
writeln!(out, "{}", name)?;
}
out.reset()?;
if let Some(ref file) = self.filename {
let filestr = file.to_str().unwrap_or("<bad utf8>");
let lineno = self
.lineno
.map_or("<unknown line>".to_owned(), |x| x.to_string());
writeln!(out, " at {}:{}", filestr, lineno)?;
} else {
writeln!(out, " at <unknown source file>")?;
}
if s.current_verbosity() >= Verbosity::Full {
self.print_source_if_avail(out, s)?;
}
Ok(())
}
}
pub fn default_frame_filter(frames: &mut Vec<&Frame>) {
let top_cutoff = frames
.iter()
.rposition(|x| x.is_post_panic_code())
.map(|x| x + 2)
.unwrap_or(0);
let bottom_cutoff = frames
.iter()
.position(|x| x.is_runtime_init_code())
.unwrap_or_else(|| frames.len());
let rng = top_cutoff..=bottom_cutoff;
frames.retain(|x| rng.contains(&x.n))
}
#[derive(Debug)]
pub struct ColorScheme {
pub frames_omitted_msg: ColorSpec,
pub header: ColorSpec,
pub msg_loc_prefix: ColorSpec,
pub src_loc: ColorSpec,
pub src_loc_separator: ColorSpec,
pub env_var: ColorSpec,
pub dependency_code: ColorSpec,
pub dependency_code_hash: ColorSpec,
pub crate_code: ColorSpec,
pub crate_code_hash: ColorSpec,
pub selected_src_ln: ColorSpec,
}
impl ColorScheme {
fn cs(fg: Option<Color>, intense: bool, bold: bool) -> ColorSpec {
let mut cs = ColorSpec::new();
cs.set_fg(fg);
cs.set_bold(bold);
cs.set_intense(intense);
cs
}
pub fn classic() -> Self {
Self {
frames_omitted_msg: Self::cs(Some(Color::Cyan), true, false),
header: Self::cs(Some(Color::Red), false, false),
msg_loc_prefix: Self::cs(Some(Color::Cyan), false, false),
src_loc: Self::cs(Some(Color::Magenta), false, false),
src_loc_separator: Self::cs(Some(Color::White), false, false),
env_var: Self::cs(None, false, true),
dependency_code: Self::cs(Some(Color::Green), false, false),
dependency_code_hash: Self::cs(Some(Color::Black), true, false),
crate_code: Self::cs(Some(Color::Red), true, false),
crate_code_hash: Self::cs(Some(Color::Black), true, false),
selected_src_ln: Self::cs(None, false, true),
}
}
}
impl Default for ColorScheme {
fn default() -> Self {
Self::classic()
}
}
#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter` instead.")]
pub type Settings = BacktracePrinter;
pub struct BacktracePrinter {
message: String,
verbosity: Verbosity,
lib_verbosity: Verbosity,
strip_function_hash: bool,
is_panic_handler: bool,
colors: ColorScheme,
filters: Vec<Box<FilterCallback>>,
}
impl Default for BacktracePrinter {
fn default() -> Self {
Self {
verbosity: Verbosity::from_env(),
lib_verbosity: Verbosity::lib_from_env(),
message: "The application panicked (crashed).".to_owned(),
strip_function_hash: false,
colors: ColorScheme::classic(),
is_panic_handler: false,
filters: vec![Box::new(default_frame_filter)],
}
}
}
impl BacktracePrinter {
pub fn new() -> Self {
Self::default()
}
pub fn color_scheme(mut self, colors: ColorScheme) -> Self {
self.colors = colors;
self
}
pub fn message(mut self, message: impl Into<String>) -> Self {
self.message = message.into();
self
}
pub fn verbosity(mut self, v: Verbosity) -> Self {
self.verbosity = v;
self
}
pub fn lib_verbosity(mut self, v: Verbosity) -> Self {
self.lib_verbosity = v;
self
}
pub fn strip_function_hash(mut self, strip: bool) -> Self {
self.strip_function_hash = strip;
self
}
pub fn add_frame_filter(mut self, filter: Box<FilterCallback>) -> Self {
self.filters.push(filter);
self
}
pub fn clear_frame_filters(mut self) -> Self {
self.filters.clear();
self
}
}
impl BacktracePrinter {
pub fn install(self, out: impl WriteColor + Sync + Send + 'static) {
std::panic::set_hook(self.into_panic_handler(out))
}
pub fn into_panic_handler(
mut self,
out: impl WriteColor + Sync + Send + 'static,
) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
self.is_panic_handler = true;
let out_stream_mutex = Mutex::new(out);
Box::new(move |pi| {
let mut lock = out_stream_mutex.lock().unwrap();
if let Err(e) = self.print_panic_info(pi, &mut *lock) {
eprintln!("Error while printing panic: {:?}", e);
}
})
}
pub fn print_trace(&self, trace: &backtrace::Backtrace, out: &mut impl WriteColor) -> IOResult {
writeln!(out, "{:━^80}", " BACKTRACE ")?;
let frames: Vec<_> = trace
.frames()
.iter()
.flat_map(|frame| frame.symbols())
.zip(1usize..)
.map(|(sym, n)| Frame {
name: sym.name().map(|x| x.to_string()),
lineno: sym.lineno(),
filename: sym.filename().map(|x| x.into()),
n,
_private_ctor: (),
})
.collect();
let mut filtered_frames = frames.iter().collect();
for filter in &self.filters {
filter(&mut filtered_frames);
}
if filtered_frames.is_empty() {
return writeln!(out, "<empty backtrace>");
}
filtered_frames.sort_by_key(|x| x.n);
macro_rules! print_hidden {
($n:expr) => {
out.set_color(&self.colors.frames_omitted_msg)?;
let n = $n;
let text = format!(
"{decorator} {n} frame{plural} hidden {decorator}",
n = n,
plural = if n == 1 { "" } else { "s" },
decorator = "⋮",
);
writeln!(out, "{:^80}", text)?;
out.reset()?;
};
}
let mut last_n = 0;
for frame in &filtered_frames {
let frame_delta = frame.n - last_n - 1;
if frame_delta != 0 {
print_hidden!(frame_delta);
}
frame.print(frame.n, out, self)?;
last_n = frame.n;
}
let last_filtered_n = filtered_frames.last().unwrap().n;
let last_unfiltered_n = frames.last().unwrap().n;
if last_filtered_n < last_unfiltered_n {
print_hidden!(last_unfiltered_n - last_filtered_n);
}
Ok(())
}
#[cfg(feature = "failure-bt")]
pub unsafe fn print_failure_trace(
&self,
trace: &::failure::Backtrace,
out: &mut impl WriteColor,
) -> IOResult {
let internal = failure::backdoortrace(trace);
if let Some(internal) = internal {
self.print_trace(internal, out)
} else {
writeln!(out, "<failure backtrace not captured>")
}
}
pub fn format_trace_to_string(&self, trace: &backtrace::Backtrace) -> IOResult<String> {
let mut ansi = Ansi::new(vec![]);
self.print_trace(trace, &mut ansi)?;
Ok(String::from_utf8(ansi.into_inner()).unwrap())
}
pub fn print_panic_info(&self, pi: &PanicInfo, out: &mut impl WriteColor) -> IOResult {
out.set_color(&self.colors.header)?;
writeln!(out, "{}", self.message)?;
out.reset()?;
let payload = pi
.payload()
.downcast_ref::<String>()
.map(String::as_str)
.or_else(|| pi.payload().downcast_ref::<&str>().cloned())
.unwrap_or("<non string panic payload>");
write!(out, "Message: ")?;
out.set_color(&self.colors.msg_loc_prefix)?;
writeln!(out, "{}", payload)?;
out.reset()?;
write!(out, "Location: ")?;
if let Some(loc) = pi.location() {
out.set_color(&self.colors.src_loc)?;
write!(out, "{}", loc.file())?;
out.set_color(&self.colors.src_loc_separator)?;
write!(out, ":")?;
out.set_color(&self.colors.src_loc)?;
writeln!(out, "{}", loc.line())?;
out.reset()?;
} else {
writeln!(out, "<unknown>")?;
}
if self.current_verbosity() == Verbosity::Minimal {
write!(out, "\nBacktrace omitted. Run with ")?;
out.set_color(&self.colors.env_var)?;
write!(out, "RUST_BACKTRACE=1")?;
out.reset()?;
writeln!(out, " environment variable to display it.")?;
}
if self.current_verbosity() <= Verbosity::Medium {
if self.current_verbosity() == Verbosity::Medium {
writeln!(out)?;
}
write!(out, "Run with ")?;
out.set_color(&self.colors.env_var)?;
write!(out, "RUST_BACKTRACE=full")?;
out.reset()?;
writeln!(out, " to include source snippets.")?;
}
if self.current_verbosity() >= Verbosity::Medium {
self.print_trace(&backtrace::Backtrace::new(), out)?;
}
Ok(())
}
fn current_verbosity(&self) -> Verbosity {
if self.is_panic_handler {
self.verbosity
} else {
self.lib_verbosity
}
}
}
#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::print_trace` instead`")]
pub fn print_backtrace(trace: &backtrace::Backtrace, s: &mut BacktracePrinter) -> IOResult {
s.print_trace(trace, &mut default_output_stream())
}
#[deprecated(
since = "0.4.0",
note = "Use `BacktracePrinter::print_panic_info` instead`"
)]
pub fn print_panic_info(pi: &PanicInfo, s: &mut BacktracePrinter) -> IOResult {
s.print_panic_info(pi, &mut default_output_stream())
}