1use std::env;
40use std::fs::File;
41use std::io::{BufRead, BufReader, ErrorKind};
42use std::panic::PanicInfo;
43use std::path::PathBuf;
44use std::sync::{Arc, Mutex};
45use termcolor::{Ansi, Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
46
47pub use termcolor;
49
50type IOResult<T = ()> = Result<T, std::io::Error>;
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
62pub enum Verbosity {
63 Minimal,
65 Medium,
67 Full,
69}
70
71impl Verbosity {
72 pub fn from_env() -> Self {
74 Self::convert_env(env::var("RUST_BACKTRACE").ok())
75 }
76
77 pub fn lib_from_env() -> Self {
80 Self::convert_env(
81 env::var("RUST_LIB_BACKTRACE")
82 .or_else(|_| env::var("RUST_BACKTRACE"))
83 .ok(),
84 )
85 }
86
87 fn convert_env(env: Option<String>) -> Self {
88 match env {
89 Some(ref x) if x == "full" => Verbosity::Full,
90 Some(_) => Verbosity::Medium,
91 None => Verbosity::Minimal,
92 }
93 }
94}
95
96pub fn install() {
109 BacktracePrinter::default().install(default_output_stream());
110}
111
112pub fn default_output_stream() -> Box<StandardStream> {
117 Box::new(StandardStream::stderr(if atty::is(atty::Stream::Stderr) {
118 ColorChoice::Always
119 } else {
120 ColorChoice::Never
121 }))
122}
123
124#[deprecated(
125 since = "0.4.0",
126 note = "Use `BacktracePrinter::into_panic_handler()` instead."
127)]
128pub fn create_panic_handler(
129 printer: BacktracePrinter,
130) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
131 let out_stream_mutex = Mutex::new(default_output_stream());
132 Box::new(move |pi| {
133 let mut lock = out_stream_mutex.lock().unwrap();
134 if let Err(e) = printer.print_panic_info(pi, &mut *lock) {
135 eprintln!("Error while printing panic: {:?}", e);
138 }
139 })
140}
141
142#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::install()` instead.")]
143pub fn install_with_settings(printer: BacktracePrinter) {
144 std::panic::set_hook(printer.into_panic_handler(default_output_stream()))
145}
146
147pub type FilterCallback = dyn Fn(&mut Vec<&Frame>) + Send + Sync + 'static;
152
153#[derive(Debug)]
154pub struct Frame {
155 pub n: usize,
156 pub name: Option<String>,
157 pub lineno: Option<u32>,
158 pub filename: Option<PathBuf>,
159 pub ip: usize,
160 _private_ctor: (),
161}
162
163impl Frame {
164 fn is_dependency_code(&self) -> bool {
170 const SYM_PREFIXES: &[&str] = &[
171 "std::",
172 "core::",
173 "backtrace::backtrace::",
174 "_rust_begin_unwind",
175 "color_traceback::",
176 "__rust_",
177 "___rust_",
178 "__pthread",
179 "_main",
180 "main",
181 "__scrt_common_main_seh",
182 "BaseThreadInitThunk",
183 "_start",
184 "__libc_start_main",
185 "start_thread",
186 ];
187
188 if let Some(ref name) = self.name {
190 if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
191 return true;
192 }
193 }
194
195 const FILE_PREFIXES: &[&str] = &[
196 "/rustc/",
197 "src/libstd/",
198 "src/libpanic_unwind/",
199 "src/libtest/",
200 ];
201
202 if let Some(ref filename) = self.filename {
204 let filename = filename.to_string_lossy();
205 if FILE_PREFIXES.iter().any(|x| filename.starts_with(x))
206 || filename.contains("/.cargo/registry/src/")
207 {
208 return true;
209 }
210 }
211
212 false
213 }
214
215 fn is_post_panic_code(&self) -> bool {
222 const SYM_PREFIXES: &[&str] = &[
223 "_rust_begin_unwind",
224 "rust_begin_unwind",
225 "core::result::unwrap_failed",
226 "core::option::expect_none_failed",
227 "core::panicking::panic_fmt",
228 "color_backtrace::create_panic_handler",
229 "std::panicking::begin_panic",
230 "begin_panic_fmt",
231 "backtrace::capture",
232 ];
233
234 match self.name.as_ref() {
235 Some(name) => SYM_PREFIXES.iter().any(|x| name.starts_with(x)),
236 None => false,
237 }
238 }
239
240 fn is_runtime_init_code(&self) -> bool {
243 const SYM_PREFIXES: &[&str] = &[
244 "std::rt::lang_start::",
245 "test::run_test::run_test_inner::",
246 "std::sys_common::backtrace::__rust_begin_short_backtrace",
247 ];
248
249 let (name, file) = match (self.name.as_ref(), self.filename.as_ref()) {
250 (Some(name), Some(filename)) => (name, filename.to_string_lossy()),
251 _ => return false,
252 };
253
254 if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
255 return true;
256 }
257
258 if name == "{{closure}}" && file == "src/libtest/lib.rs" {
260 return true;
261 }
262
263 false
264 }
265
266 fn print_source_if_avail(&self, mut out: impl WriteColor, s: &BacktracePrinter) -> IOResult {
267 let (lineno, filename) = match (self.lineno, self.filename.as_ref()) {
268 (Some(a), Some(b)) => (a, b),
269 _ => return Ok(()),
271 };
272
273 let file = match File::open(filename) {
274 Ok(file) => file,
275 Err(ref e) if e.kind() == ErrorKind::NotFound => return Ok(()),
276 e @ Err(_) => e?,
277 };
278
279 let reader = BufReader::new(file);
281 let start_line = lineno - 2.min(lineno - 1);
282 let surrounding_src = reader.lines().skip(start_line as usize - 1).take(5);
283 for (line, cur_line_no) in surrounding_src.zip(start_line..) {
284 if cur_line_no == lineno {
285 out.set_color(&s.colors.selected_src_ln)?;
287 writeln!(out, "{:>8} > {}", cur_line_no, line?)?;
288 out.reset()?;
289 } else {
290 writeln!(out, "{:>8} │ {}", cur_line_no, line?)?;
291 }
292 }
293
294 Ok(())
295 }
296
297 #[cfg(all(
299 feature = "resolve-modules",
300 unix,
301 not(any(target_os = "macos", target_os = "ios"))
302 ))]
303 fn module_info(&self) -> Option<(String, usize)> {
304 use regex::Regex;
305 use std::path::Path;
306 let re = Regex::new(
307 r"(?x)
308 ^
309 (?P<start>[0-9a-f]{8,16})
310 -
311 (?P<end>[0-9a-f]{8,16})
312 \s
313 (?P<perm>[-rwxp]{4})
314 \s
315 (?P<offset>[0-9a-f]{8})
316 \s
317 [0-9a-f]+:[0-9a-f]+
318 \s
319 [0-9]+
320 \s+
321 (?P<path>.*)
322 $
323 ",
324 )
325 .unwrap();
326
327 let mapsfile = File::open("/proc/self/maps").expect("Unable to open /proc/self/maps");
328
329 for line in BufReader::new(mapsfile).lines() {
330 let line = line.unwrap();
331 if let Some(caps) = re.captures(&line) {
332 let (start, end, path) = (
333 usize::from_str_radix(caps.name("start").unwrap().as_str(), 16).unwrap(),
334 usize::from_str_radix(caps.name("end").unwrap().as_str(), 16).unwrap(),
335 caps.name("path").unwrap().as_str().to_string(),
336 );
337 if self.ip >= start && self.ip < end {
338 return if let Some(filename) = Path::new(&path).file_name() {
339 Some((filename.to_str().unwrap().to_string(), start))
340 } else {
341 None
342 };
343 }
344 }
345 }
346
347 None
348 }
349
350 #[cfg(not(all(
351 feature = "resolve-modules",
352 unix,
353 not(any(target_os = "macos", target_os = "ios"))
354 )))]
355 fn module_info(&self) -> Option<(String, usize)> {
356 None
357 }
358
359 fn print(&self, i: usize, out: &mut impl WriteColor, s: &BacktracePrinter) -> IOResult {
360 let is_dependency_code = self.is_dependency_code();
361
362 write!(out, "{:>2}: ", i)?;
364
365 if s.should_print_addresses() {
366 if let Some((module_name, module_base)) = self.module_info() {
367 write!(out, "{}:0x{:08x} - ", module_name, self.ip - module_base)?;
368 } else {
369 write!(out, "0x{:016x} - ", self.ip)?;
370 }
371 }
372
373 let name = self
376 .name
377 .as_ref()
378 .map(|s| s.as_str())
379 .unwrap_or("<unknown>");
380 let has_hash_suffix = name.len() > 19
381 && &name[name.len() - 19..name.len() - 16] == "::h"
382 && name[name.len() - 16..].chars().all(|x| x.is_digit(16));
383
384 out.set_color(if is_dependency_code {
386 &s.colors.dependency_code
387 } else {
388 &s.colors.crate_code
389 })?;
390
391 if has_hash_suffix {
392 write!(out, "{}", &name[..name.len() - 19])?;
393 if s.strip_function_hash {
394 writeln!(out)?;
395 } else {
396 out.set_color(if is_dependency_code {
397 &s.colors.dependency_code_hash
398 } else {
399 &s.colors.crate_code_hash
400 })?;
401 writeln!(out, "{}", &name[name.len() - 19..])?;
402 }
403 } else {
404 writeln!(out, "{}", name)?;
405 }
406
407 out.reset()?;
408
409 if let Some(ref file) = self.filename {
411 let filestr = file.to_str().unwrap_or("<bad utf8>");
412 let lineno = self
413 .lineno
414 .map_or("<unknown line>".to_owned(), |x| x.to_string());
415 writeln!(out, " at {}:{}", filestr, lineno)?;
416 } else {
417 writeln!(out, " at <unknown source file>")?;
418 }
419
420 if s.current_verbosity() >= Verbosity::Full {
422 self.print_source_if_avail(out, s)?;
423 }
424
425 Ok(())
426 }
427}
428
429pub fn default_frame_filter(frames: &mut Vec<&Frame>) {
433 let top_cutoff = frames
434 .iter()
435 .rposition(|x| x.is_post_panic_code())
436 .map(|x| x + 2) .unwrap_or(0);
438
439 let bottom_cutoff = frames
440 .iter()
441 .position(|x| x.is_runtime_init_code())
442 .unwrap_or_else(|| frames.len());
443
444 let rng = top_cutoff..=bottom_cutoff;
445 frames.retain(|x| rng.contains(&x.n))
446}
447
448#[derive(Debug, Clone)]
454pub struct ColorScheme {
455 pub frames_omitted_msg: ColorSpec,
456 pub header: ColorSpec,
457 pub msg_loc_prefix: ColorSpec,
458 pub src_loc: ColorSpec,
459 pub src_loc_separator: ColorSpec,
460 pub env_var: ColorSpec,
461 pub dependency_code: ColorSpec,
462 pub dependency_code_hash: ColorSpec,
463 pub crate_code: ColorSpec,
464 pub crate_code_hash: ColorSpec,
465 pub selected_src_ln: ColorSpec,
466}
467
468impl ColorScheme {
469 fn cs(fg: Option<Color>, intense: bool, bold: bool) -> ColorSpec {
471 let mut cs = ColorSpec::new();
472 cs.set_fg(fg);
473 cs.set_bold(bold);
474 cs.set_intense(intense);
475 cs
476 }
477
478 pub fn classic() -> Self {
480 Self {
481 frames_omitted_msg: Self::cs(Some(Color::Cyan), true, false),
482 header: Self::cs(Some(Color::Red), false, false),
483 msg_loc_prefix: Self::cs(Some(Color::Cyan), false, false),
484 src_loc: Self::cs(Some(Color::Magenta), false, false),
485 src_loc_separator: Self::cs(Some(Color::White), false, false),
486 env_var: Self::cs(None, false, true),
487 dependency_code: Self::cs(Some(Color::Green), false, false),
488 dependency_code_hash: Self::cs(Some(Color::Black), true, false),
489 crate_code: Self::cs(Some(Color::Red), true, false),
490 crate_code_hash: Self::cs(Some(Color::Black), true, false),
491 selected_src_ln: Self::cs(None, false, true),
492 }
493 }
494}
495
496impl Default for ColorScheme {
497 fn default() -> Self {
498 Self::classic()
499 }
500}
501
502#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter` instead.")]
503pub type Settings = BacktracePrinter;
504
505#[derive(Clone)]
507pub struct BacktracePrinter {
508 message: String,
509 verbosity: Verbosity,
510 lib_verbosity: Verbosity,
511 strip_function_hash: bool,
512 is_panic_handler: bool,
513 colors: ColorScheme,
514 filters: Vec<Arc<FilterCallback>>,
515 should_print_addresses: bool,
516}
517
518impl Default for BacktracePrinter {
519 fn default() -> Self {
520 Self {
521 verbosity: Verbosity::from_env(),
522 lib_verbosity: Verbosity::lib_from_env(),
523 message: "The application panicked (crashed).".to_owned(),
524 strip_function_hash: false,
525 colors: ColorScheme::classic(),
526 is_panic_handler: false,
527 filters: vec![Arc::new(default_frame_filter)],
528 should_print_addresses: false,
529 }
530 }
531}
532
533impl std::fmt::Debug for BacktracePrinter {
534 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
535 fmt.debug_struct("Settings")
536 .field("message", &self.message)
537 .field("verbosity", &self.verbosity)
538 .field("lib_verbosity", &self.lib_verbosity)
539 .field("strip_function_hash", &self.strip_function_hash)
540 .field("is_panic_handler", &self.is_panic_handler)
541 .field("print_addresses", &self.should_print_addresses)
542 .field("colors", &self.colors)
543 .finish()
544 }
545}
546
547impl BacktracePrinter {
549 pub fn new() -> Self {
551 Self::default()
552 }
553
554 pub fn color_scheme(mut self, colors: ColorScheme) -> Self {
558 self.colors = colors;
559 self
560 }
561
562 pub fn message(mut self, message: impl Into<String>) -> Self {
566 self.message = message.into();
567 self
568 }
569
570 pub fn verbosity(mut self, v: Verbosity) -> Self {
574 self.verbosity = v;
575 self
576 }
577
578 pub fn lib_verbosity(mut self, v: Verbosity) -> Self {
582 self.lib_verbosity = v;
583 self
584 }
585
586 pub fn strip_function_hash(mut self, strip: bool) -> Self {
590 self.strip_function_hash = strip;
591 self
592 }
593
594 pub fn print_addresses(mut self, val: bool) -> Self {
598 self.should_print_addresses = val;
599 self
600 }
601
602 pub fn add_frame_filter(mut self, filter: Box<FilterCallback>) -> Self {
618 self.filters.push(filter.into());
619 self
620 }
621
622 pub fn clear_frame_filters(mut self) -> Self {
624 self.filters.clear();
625 self
626 }
627}
628
629impl BacktracePrinter {
631 pub fn install(self, out: impl WriteColor + Sync + Send + 'static) {
637 std::panic::set_hook(self.into_panic_handler(out))
638 }
639
640 pub fn into_panic_handler(
644 mut self,
645 out: impl WriteColor + Sync + Send + 'static,
646 ) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
647 self.is_panic_handler = true;
648 let out_stream_mutex = Mutex::new(out);
649 Box::new(move |pi| {
650 let mut lock = out_stream_mutex.lock().unwrap();
651 if let Err(e) = self.print_panic_info(pi, &mut *lock) {
652 eprintln!("Error while printing panic: {:?}", e);
655 }
656 })
657 }
658
659 pub fn print_trace(&self, trace: &backtrace::Backtrace, out: &mut impl WriteColor) -> IOResult {
661 writeln!(out, "{:━^80}", " BACKTRACE ")?;
662
663 let frames: Vec<_> = trace
665 .frames()
666 .iter()
667 .flat_map(|frame| frame.symbols().iter().map(move |sym| (frame.ip(), sym)))
668 .zip(1usize..)
669 .map(|((ip, sym), n)| Frame {
670 name: sym.name().map(|x| x.to_string()),
671 lineno: sym.lineno(),
672 filename: sym.filename().map(|x| x.into()),
673 n,
674 ip: ip as usize,
675 _private_ctor: (),
676 })
677 .collect();
678
679 let mut filtered_frames = frames.iter().collect();
680 match env::var("COLORBT_SHOW_HIDDEN").ok().as_deref() {
681 Some("1") | Some("on") | Some("y") => (),
682 _ => {
683 for filter in &self.filters {
684 filter(&mut filtered_frames);
685 }
686 }
687 }
688
689 if filtered_frames.is_empty() {
690 return writeln!(out, "<empty backtrace>");
692 }
693
694 filtered_frames.sort_by_key(|x| x.n);
696
697 macro_rules! print_hidden {
698 ($n:expr) => {
699 out.set_color(&self.colors.frames_omitted_msg)?;
700 let n = $n;
701 let text = format!(
702 "{decorator} {n} frame{plural} hidden {decorator}",
703 n = n,
704 plural = if n == 1 { "" } else { "s" },
705 decorator = "⋮",
706 );
707 writeln!(out, "{:^80}", text)?;
708 out.reset()?;
709 };
710 }
711
712 let mut last_n = 0;
713 for frame in &filtered_frames {
714 let frame_delta = frame.n - last_n - 1;
715 if frame_delta != 0 {
716 print_hidden!(frame_delta);
717 }
718 if !frame.is_dependency_code() {
719 frame.print(frame.n, out, self)?;
720 }
721 last_n = frame.n;
722 }
723
724 let last_filtered_n = filtered_frames.last().unwrap().n;
725 let last_unfiltered_n = frames.last().unwrap().n;
726 if last_filtered_n < last_unfiltered_n {
727 print_hidden!(last_unfiltered_n - last_filtered_n);
728 }
729
730 Ok(())
731 }
732
733 pub fn format_trace_to_string(&self, trace: &backtrace::Backtrace) -> IOResult<String> {
735 let mut ansi = Ansi::new(vec![]);
737 self.print_trace(trace, &mut ansi)?;
738 Ok(String::from_utf8(ansi.into_inner()).unwrap())
739 }
740
741 pub fn print_panic_info(&self, pi: &PanicInfo, out: &mut impl WriteColor) -> IOResult {
743 out.set_color(&self.colors.header)?;
744 writeln!(out, "{}", self.message)?;
745 out.reset()?;
746
747 let payload = pi
749 .payload()
750 .downcast_ref::<String>()
751 .map(String::as_str)
752 .or_else(|| pi.payload().downcast_ref::<&str>().cloned())
753 .unwrap_or("<non string panic payload>");
754
755 write!(out, "Message: ")?;
756 out.set_color(&self.colors.msg_loc_prefix)?;
757 writeln!(out, "{}", payload)?;
758 out.reset()?;
759
760 write!(out, "Location: ")?;
762 if let Some(loc) = pi.location() {
763 out.set_color(&self.colors.src_loc)?;
764 write!(out, "{}", loc.file())?;
765 out.set_color(&self.colors.src_loc_separator)?;
766 write!(out, ":")?;
767 out.set_color(&self.colors.src_loc)?;
768 writeln!(out, "{}", loc.line())?;
769 out.reset()?;
770 } else {
771 writeln!(out, "<unknown>")?;
772 }
773
774 if self.current_verbosity() == Verbosity::Minimal {
776 write!(out, "\nBacktrace omitted.\n\nRun with ")?;
777 out.set_color(&self.colors.env_var)?;
778 write!(out, "RUST_BACKTRACE=1")?;
779 out.reset()?;
780 writeln!(out, " environment variable to display it.")?;
781 } else {
782 write!(out, "\nRun with ")?;
784 out.set_color(&self.colors.env_var)?;
785 write!(out, "COLORBT_SHOW_HIDDEN=1")?;
786 out.reset()?;
787 writeln!(out, " environment variable to disable frame filtering.")?;
788 }
789 if self.current_verbosity() <= Verbosity::Medium {
790 write!(out, "Run with ")?;
791 out.set_color(&self.colors.env_var)?;
792 write!(out, "RUST_BACKTRACE=full")?;
793 out.reset()?;
794 writeln!(out, " to include source snippets.")?;
795 }
796
797 if self.current_verbosity() >= Verbosity::Medium {
798 self.print_trace(&backtrace::Backtrace::new(), out)?;
799 }
800
801 Ok(())
802 }
803
804 fn current_verbosity(&self) -> Verbosity {
805 if self.is_panic_handler {
806 self.verbosity
807 } else {
808 self.lib_verbosity
809 }
810 }
811
812 fn should_print_addresses(&self) -> bool {
813 self.should_print_addresses
814 }
815}
816
817#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::print_trace` instead`")]
822pub fn print_backtrace(trace: &backtrace::Backtrace, s: &mut BacktracePrinter) -> IOResult {
823 s.print_trace(trace, &mut default_output_stream())
824}
825
826#[deprecated(
827 since = "0.4.0",
828 note = "Use `BacktracePrinter::print_panic_info` instead`"
829)]
830pub fn print_panic_info(pi: &PanicInfo, s: &mut BacktracePrinter) -> IOResult {
831 s.print_panic_info(pi, &mut default_output_stream())
832}
833
834