1use std::env;
43use std::fs::File;
44use std::io::{BufRead, BufReader, ErrorKind, IsTerminal as _};
45#[allow(deprecated, reason = "keep support for older rust versions")]
46use std::panic::PanicInfo;
47use std::path::PathBuf;
48use std::sync::{Arc, Mutex};
49use termcolor::{Ansi, Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
50
51pub use termcolor;
53
54#[cfg(feature = "use-btparse-crate")]
56pub use btparse;
57
58type IOResult<T = ()> = Result<T, std::io::Error>;
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
70pub enum Verbosity {
71 Minimal,
73 Medium,
75 Full,
77}
78
79impl Verbosity {
80 pub fn from_env() -> Self {
82 Self::convert_env(env::var("RUST_BACKTRACE").ok())
83 }
84
85 pub fn lib_from_env() -> Self {
88 Self::convert_env(
89 env::var("RUST_LIB_BACKTRACE")
90 .or_else(|_| env::var("RUST_BACKTRACE"))
91 .ok(),
92 )
93 }
94
95 fn convert_env(env: Option<String>) -> Self {
96 match env {
97 Some(ref x) if x == "full" => Verbosity::Full,
98 Some(_) => Verbosity::Medium,
99 None => Verbosity::Minimal,
100 }
101 }
102}
103
104pub fn install() {
117 BacktracePrinter::default().install(default_output_stream());
118}
119
120pub fn default_output_stream() -> Box<StandardStream> {
124 let color_choice = default_color_choice();
125 Box::new(StandardStream::stderr(color_choice))
126}
127
128pub fn default_color_choice() -> ColorChoice {
139 if env::var("NO_COLOR").is_ok() {
140 return ColorChoice::Never;
141 }
142
143 if env::var("FORCE_COLOR").is_ok() {
144 return ColorChoice::Always;
145 }
146
147 if std::io::stderr().is_terminal() {
148 ColorChoice::Always
149 } else {
150 ColorChoice::Never
151 }
152}
153
154#[doc(hidden)]
155#[deprecated(
156 since = "0.4.0",
157 note = "Use `BacktracePrinter::into_panic_handler()` instead."
158)]
159#[allow(deprecated, reason = "keep support for older rust versions")]
160pub fn create_panic_handler(
161 printer: BacktracePrinter,
162) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
163 let out_stream_mutex = Mutex::new(default_output_stream());
164 Box::new(move |pi| {
165 let mut lock = out_stream_mutex.lock().unwrap();
166 if let Err(e) = printer.print_panic_info(pi, &mut *lock) {
167 eprintln!("Error while printing panic: {:?}", e);
170 }
171 })
172}
173
174#[doc(hidden)]
175#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::install()` instead.")]
176pub fn install_with_settings(printer: BacktracePrinter) {
177 std::panic::set_hook(printer.into_panic_handler(default_output_stream()))
178}
179
180pub trait Backtrace {
186 fn frames(&self) -> Vec<Frame>;
187}
188
189#[cfg(feature = "use-backtrace-crate")]
190impl Backtrace for backtrace::Backtrace {
191 fn frames(&self) -> Vec<Frame> {
192 backtrace::Backtrace::frames(self)
193 .iter()
194 .flat_map(|frame| frame.symbols().iter().map(move |sym| (frame.ip(), sym)))
195 .zip(1usize..)
196 .map(|((ip, sym), n)| Frame {
197 name: sym.name().map(|x| x.to_string()),
198 lineno: sym.lineno(),
199 filename: sym.filename().map(|x| x.into()),
200 n,
201 ip: Some(ip as usize),
202 })
203 .collect()
204 }
205}
206
207#[cfg(feature = "use-btparse-crate")]
208impl Backtrace for btparse::Backtrace {
209 fn frames(&self) -> Vec<Frame> {
210 self.frames
211 .iter()
212 .zip(1usize..)
213 .map(|(frame, n)| Frame {
214 n,
215 name: Some(frame.function.clone()),
216 lineno: frame.line.map(|x| x as u32),
217 filename: frame.file.as_ref().map(|x| x.clone().into()),
218 ip: None,
219 })
220 .collect()
221 }
222}
223
224fn capture_backtrace() -> Result<Box<dyn Backtrace>, Box<dyn std::error::Error>> {
226 #[cfg(all(feature = "use-backtrace-crate", feature = "use-btparse-crate"))]
227 return Ok(Box::new(backtrace::Backtrace::new()));
228
229 #[cfg(all(feature = "use-backtrace-crate", not(feature = "use-btparse-crate")))]
230 return Ok(Box::new(backtrace::Backtrace::new()));
231
232 #[cfg(all(not(feature = "use-backtrace-crate"), feature = "use-btparse-crate"))]
233 {
234 let bt = std::backtrace::Backtrace::force_capture();
235 return Ok(Box::new(btparse::deserialize(&bt).map_err(Box::new)?));
236 }
237
238 #[cfg(all(
239 not(feature = "use-backtrace-crate"),
240 not(feature = "use-btparse-crate")
241 ))]
242 {
243 return Err(Box::new(std::io::Error::new(
244 std::io::ErrorKind::Other,
245 "need to enable at least one backtrace crate selector feature",
246 )));
247 }
248}
249
250pub type FilterCallback = dyn Fn(&mut Vec<&Frame>) + Send + Sync + 'static;
255
256#[derive(Debug)]
257#[non_exhaustive]
258pub struct Frame {
259 pub n: usize,
260 pub name: Option<String>,
261 pub lineno: Option<u32>,
262 pub filename: Option<PathBuf>,
263 pub ip: Option<usize>,
264}
265
266impl Frame {
267 pub fn is_dependency_code(&self) -> bool {
273 const SYM_PREFIXES: &[&str] = &[
274 "std::",
275 "core::",
276 "backtrace::backtrace::",
277 "_rust_begin_unwind",
278 "color_traceback::",
279 "__rust_",
280 "___rust_",
281 "__pthread",
282 "_main",
283 "main",
284 "__scrt_common_main_seh",
285 "BaseThreadInitThunk",
286 "_start",
287 "__libc_start_main",
288 "start_thread",
289 ];
290
291 if let Some(ref name) = self.name {
293 if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
294 return true;
295 }
296 }
297
298 const FILE_PREFIXES: &[&str] = &[
299 "/rustc/",
300 "src/libstd/",
301 "src/libpanic_unwind/",
302 "src/libtest/",
303 ];
304
305 if let Some(ref filename) = self.filename {
307 let filename = filename.to_string_lossy();
308 if FILE_PREFIXES.iter().any(|x| filename.starts_with(x))
309 || filename.contains("/.cargo/registry/src/")
310 {
311 return true;
312 }
313 }
314
315 false
316 }
317
318 pub fn is_post_panic_code(&self) -> bool {
325 const SYM_PREFIXES: &[&str] = &[
326 "_rust_begin_unwind",
327 "rust_begin_unwind",
328 "core::result::unwrap_failed",
329 "core::option::expect_none_failed",
330 "core::panicking::panic_fmt",
331 "color_backtrace::create_panic_handler",
332 "std::panicking::begin_panic",
333 "begin_panic_fmt",
334 "backtrace::capture",
335 ];
336
337 match self.name.as_ref() {
338 Some(name) => SYM_PREFIXES.iter().any(|x| name.starts_with(x)),
339 None => false,
340 }
341 }
342
343 pub fn is_runtime_init_code(&self) -> bool {
346 const SYM_PREFIXES: &[&str] = &[
347 "std::rt::lang_start::",
348 "test::run_test::run_test_inner::",
349 "std::sys_common::backtrace::__rust_begin_short_backtrace",
350 ];
351
352 let (name, file) = match (self.name.as_ref(), self.filename.as_ref()) {
353 (Some(name), Some(filename)) => (name, filename.to_string_lossy()),
354 _ => return false,
355 };
356
357 if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
358 return true;
359 }
360
361 if name == "{{closure}}" && file == "src/libtest/lib.rs" {
363 return true;
364 }
365
366 false
367 }
368
369 fn print_source_if_avail(&self, mut out: impl WriteColor, s: &BacktracePrinter) -> IOResult {
370 let (lineno, filename) = match (self.lineno, self.filename.as_ref()) {
371 (Some(a), Some(b)) => (a, b),
372 _ => return Ok(()),
374 };
375
376 let file = match File::open(filename) {
377 Ok(file) => file,
378 Err(ref e) if e.kind() == ErrorKind::NotFound => return Ok(()),
379 e @ Err(_) => e?,
380 };
381
382 let reader = BufReader::new(file);
384 let start_line = lineno - 2.min(lineno - 1);
385 let surrounding_src = reader.lines().skip(start_line as usize - 1).take(5);
386 for (line, cur_line_no) in surrounding_src.zip(start_line..) {
387 if cur_line_no == lineno {
388 out.set_color(&s.colors.selected_src_ln)?;
390 writeln!(out, "{:>8} > {}", cur_line_no, line?)?;
391 out.reset()?;
392 } else {
393 writeln!(out, "{:>8} │ {}", cur_line_no, line?)?;
394 }
395 }
396
397 Ok(())
398 }
399
400 #[cfg(all(
402 feature = "resolve-modules",
403 unix,
404 not(any(target_os = "macos", target_os = "ios"))
405 ))]
406 fn module_info(&self) -> Option<(String, usize)> {
407 use regex::Regex;
408 use std::path::Path;
409
410 let ip = match self.ip {
411 Some(x) => x,
412 None => return None,
413 };
414
415 let re = Regex::new(
416 r"(?x)
417 ^
418 (?P<start>[0-9a-f]{8,16})
419 -
420 (?P<end>[0-9a-f]{8,16})
421 \s
422 (?P<perm>[-rwxp]{4})
423 \s
424 (?P<offset>[0-9a-f]{8})
425 \s
426 [0-9a-f]+:[0-9a-f]+
427 \s
428 [0-9]+
429 \s+
430 (?P<path>.*)
431 $
432 ",
433 )
434 .unwrap();
435
436 let mapsfile = File::open("/proc/self/maps").expect("Unable to open /proc/self/maps");
437
438 for line in BufReader::new(mapsfile).lines() {
439 let line = line.unwrap();
440 if let Some(caps) = re.captures(&line) {
441 let (start, end, path) = (
442 usize::from_str_radix(caps.name("start").unwrap().as_str(), 16).unwrap(),
443 usize::from_str_radix(caps.name("end").unwrap().as_str(), 16).unwrap(),
444 caps.name("path").unwrap().as_str().to_string(),
445 );
446 if ip >= start && ip < end {
447 return if let Some(filename) = Path::new(&path).file_name() {
448 Some((filename.to_str().unwrap().to_string(), start))
449 } else {
450 None
451 };
452 }
453 }
454 }
455
456 None
457 }
458
459 #[cfg(not(all(
460 feature = "resolve-modules",
461 unix,
462 not(any(target_os = "macos", target_os = "ios"))
463 )))]
464 fn module_info(&self) -> Option<(String, usize)> {
465 None
466 }
467
468 fn print(&self, i: usize, out: &mut impl WriteColor, s: &BacktracePrinter) -> IOResult {
469 let is_dependency_code = self.is_dependency_code();
470
471 write!(out, "{:>2}: ", i)?;
473
474 if let Some(ip) = self.ip {
475 if s.should_print_addresses() {
476 if let Some((module_name, module_base)) = self.module_info() {
477 write!(out, "{}:0x{:08x} - ", module_name, ip - module_base)?;
478 } else {
479 write!(out, "0x{:016x} - ", ip)?;
480 }
481 }
482 }
483
484 let name = self.name.as_deref().unwrap_or("<unknown>");
487 let has_hash_suffix = name.len() > 19
488 && &name[name.len() - 19..name.len() - 16] == "::h"
489 && name[name.len() - 16..]
490 .chars()
491 .all(|x| x.is_ascii_hexdigit());
492
493 out.set_color(if is_dependency_code {
495 &s.colors.dependency_code
496 } else {
497 &s.colors.crate_code
498 })?;
499
500 if has_hash_suffix {
501 write!(out, "{}", &name[..name.len() - 19])?;
502 if s.strip_function_hash {
503 writeln!(out)?;
504 } else {
505 out.set_color(if is_dependency_code {
506 &s.colors.dependency_code_hash
507 } else {
508 &s.colors.crate_code_hash
509 })?;
510 writeln!(out, "{}", &name[name.len() - 19..])?;
511 }
512 } else {
513 writeln!(out, "{}", name)?;
514 }
515
516 out.reset()?;
517
518 if let Some(ref file) = self.filename {
520 let filestr = file.to_str().unwrap_or("<bad utf8>");
521 let lineno = self
522 .lineno
523 .map_or("<unknown line>".to_owned(), |x| x.to_string());
524 writeln!(out, " at {}:{}", filestr, lineno)?;
525 } else {
526 writeln!(out, " at <unknown source file>")?;
527 }
528
529 if s.current_verbosity() >= Verbosity::Full {
531 self.print_source_if_avail(out, s)?;
532 }
533
534 Ok(())
535 }
536}
537
538pub fn default_frame_filter(frames: &mut Vec<&Frame>) {
542 let top_cutoff = frames
543 .iter()
544 .rposition(|x| x.is_post_panic_code())
545 .map(|x| x + 2) .unwrap_or(0);
547
548 let bottom_cutoff = frames
549 .iter()
550 .position(|x| x.is_runtime_init_code())
551 .unwrap_or(frames.len());
552
553 let rng = top_cutoff..=bottom_cutoff;
554 frames.retain(|x| rng.contains(&x.n))
555}
556
557#[derive(Debug, Clone)]
563pub struct ColorScheme {
564 pub frames_omitted_msg: ColorSpec,
565 pub header: ColorSpec,
566 pub msg_loc_prefix: ColorSpec,
567 pub src_loc: ColorSpec,
568 pub src_loc_separator: ColorSpec,
569 pub env_var: ColorSpec,
570 pub dependency_code: ColorSpec,
571 pub dependency_code_hash: ColorSpec,
572 pub crate_code: ColorSpec,
573 pub crate_code_hash: ColorSpec,
574 pub selected_src_ln: ColorSpec,
575}
576
577impl ColorScheme {
578 fn cs(fg: Option<Color>, intense: bool, bold: bool) -> ColorSpec {
580 let mut cs = ColorSpec::new();
581 cs.set_fg(fg);
582 cs.set_bold(bold);
583 cs.set_intense(intense);
584 cs
585 }
586
587 pub fn classic() -> Self {
589 Self {
590 frames_omitted_msg: Self::cs(Some(Color::Cyan), true, false),
591 header: Self::cs(Some(Color::Red), false, false),
592 msg_loc_prefix: Self::cs(Some(Color::Cyan), false, false),
593 src_loc: Self::cs(Some(Color::Magenta), false, false),
594 src_loc_separator: Self::cs(Some(Color::White), false, false),
595 env_var: Self::cs(None, false, true),
596 dependency_code: Self::cs(Some(Color::Green), false, false),
597 dependency_code_hash: Self::cs(Some(Color::Black), true, false),
598 crate_code: Self::cs(Some(Color::Red), true, false),
599 crate_code_hash: Self::cs(Some(Color::Black), true, false),
600 selected_src_ln: Self::cs(None, false, true),
601 }
602 }
603}
604
605impl Default for ColorScheme {
606 fn default() -> Self {
607 Self::classic()
608 }
609}
610
611#[doc(hidden)]
612#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter` instead.")]
613pub type Settings = BacktracePrinter;
614
615#[derive(Clone)]
617pub struct BacktracePrinter {
618 message: String,
619 verbosity: Verbosity,
620 lib_verbosity: Verbosity,
621 strip_function_hash: bool,
622 is_panic_handler: bool,
623 colors: ColorScheme,
624 filters: Vec<Arc<FilterCallback>>,
625 should_print_addresses: bool,
626}
627
628impl Default for BacktracePrinter {
629 fn default() -> Self {
630 Self {
631 verbosity: Verbosity::from_env(),
632 lib_verbosity: Verbosity::lib_from_env(),
633 message: "The application panicked (crashed).".to_owned(),
634 strip_function_hash: false,
635 colors: ColorScheme::classic(),
636 is_panic_handler: false,
637 filters: vec![Arc::new(default_frame_filter)],
638 should_print_addresses: false,
639 }
640 }
641}
642
643impl std::fmt::Debug for BacktracePrinter {
644 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
645 fmt.debug_struct("Settings")
646 .field("message", &self.message)
647 .field("verbosity", &self.verbosity)
648 .field("lib_verbosity", &self.lib_verbosity)
649 .field("strip_function_hash", &self.strip_function_hash)
650 .field("is_panic_handler", &self.is_panic_handler)
651 .field("print_addresses", &self.should_print_addresses)
652 .field("colors", &self.colors)
653 .finish()
654 }
655}
656
657impl BacktracePrinter {
659 pub fn new() -> Self {
661 Self::default()
662 }
663
664 pub fn color_scheme(mut self, colors: ColorScheme) -> Self {
668 self.colors = colors;
669 self
670 }
671
672 pub fn message(mut self, message: impl Into<String>) -> Self {
676 self.message = message.into();
677 self
678 }
679
680 pub fn verbosity(mut self, v: Verbosity) -> Self {
684 self.verbosity = v;
685 self
686 }
687
688 pub fn lib_verbosity(mut self, v: Verbosity) -> Self {
692 self.lib_verbosity = v;
693 self
694 }
695
696 pub fn strip_function_hash(mut self, strip: bool) -> Self {
700 self.strip_function_hash = strip;
701 self
702 }
703
704 pub fn print_addresses(mut self, val: bool) -> Self {
708 self.should_print_addresses = val;
709 self
710 }
711
712 pub fn add_frame_filter(mut self, filter: Box<FilterCallback>) -> Self {
728 self.filters.push(filter.into());
729 self
730 }
731
732 pub fn clear_frame_filters(mut self) -> Self {
734 self.filters.clear();
735 self
736 }
737}
738
739impl BacktracePrinter {
741 pub fn install(self, out: impl WriteColor + Sync + Send + 'static) {
746 std::panic::set_hook(self.into_panic_handler(out))
747 }
748
749 #[allow(deprecated, reason = "keep support for older rust versions")]
753 pub fn into_panic_handler(
754 mut self,
755 out: impl WriteColor + Sync + Send + 'static,
756 ) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
757 self.is_panic_handler = true;
758 let out_stream_mutex = Mutex::new(out);
759 Box::new(move |pi| {
760 let mut lock = out_stream_mutex.lock().unwrap();
761 if let Err(e) = self.print_panic_info(pi, &mut *lock) {
762 eprintln!("Error while printing panic: {:?}", e);
765 }
766 })
767 }
768
769 pub fn print_trace(&self, trace: &dyn Backtrace, out: &mut impl WriteColor) -> IOResult {
771 writeln!(out, "{:━^80}", " BACKTRACE ")?;
772
773 let frames = trace.frames();
775
776 let mut filtered_frames = frames.iter().collect();
777 match env::var("COLORBT_SHOW_HIDDEN").ok().as_deref() {
778 Some("1") | Some("on") | Some("y") => (),
779 _ => {
780 for filter in &self.filters {
781 filter(&mut filtered_frames);
782 }
783 }
784 }
785
786 if filtered_frames.is_empty() {
787 return writeln!(out, "<empty backtrace>");
789 }
790
791 filtered_frames.sort_by_key(|x| x.n);
793
794 macro_rules! print_hidden {
795 ($n:expr) => {
796 out.set_color(&self.colors.frames_omitted_msg)?;
797 let n = $n;
798 let text = format!(
799 "{decorator} {n} frame{plural} hidden {decorator}",
800 n = n,
801 plural = if n == 1 { "" } else { "s" },
802 decorator = "⋮",
803 );
804 writeln!(out, "{:^80}", text)?;
805 out.reset()?;
806 };
807 }
808
809 let mut last_n = 0;
810 for frame in &filtered_frames {
811 let frame_delta = frame.n - last_n - 1;
812 if frame_delta != 0 {
813 print_hidden!(frame_delta);
814 }
815 frame.print(frame.n, out, self)?;
816 last_n = frame.n;
817 }
818
819 let last_filtered_n = filtered_frames.last().unwrap().n;
820 let last_unfiltered_n = frames.last().unwrap().n;
821 if last_filtered_n < last_unfiltered_n {
822 print_hidden!(last_unfiltered_n - last_filtered_n);
823 }
824
825 Ok(())
826 }
827
828 pub fn format_trace_to_string(&self, trace: &dyn Backtrace) -> IOResult<String> {
830 let mut ansi = Ansi::new(vec![]);
832 self.print_trace(trace, &mut ansi)?;
833 Ok(String::from_utf8(ansi.into_inner()).unwrap())
834 }
835
836 #[allow(deprecated, reason = "keep support for older rust versions")]
838 pub fn print_panic_info(&self, pi: &PanicInfo, out: &mut impl WriteColor) -> IOResult {
839 out.set_color(&self.colors.header)?;
840 writeln!(out, "{}", self.message)?;
841 out.reset()?;
842
843 let payload = pi
845 .payload()
846 .downcast_ref::<String>()
847 .map(String::as_str)
848 .or_else(|| pi.payload().downcast_ref::<&str>().cloned())
849 .unwrap_or("<non string panic payload>");
850
851 write!(out, "Message: ")?;
852 out.set_color(&self.colors.msg_loc_prefix)?;
853 writeln!(out, "{}", payload)?;
854 out.reset()?;
855
856 write!(out, "Location: ")?;
858 if let Some(loc) = pi.location() {
859 out.set_color(&self.colors.src_loc)?;
860 write!(out, "{}", loc.file())?;
861 out.set_color(&self.colors.src_loc_separator)?;
862 write!(out, ":")?;
863 out.set_color(&self.colors.src_loc)?;
864 writeln!(out, "{}", loc.line())?;
865 out.reset()?;
866 } else {
867 writeln!(out, "<unknown>")?;
868 }
869
870 if self.current_verbosity() == Verbosity::Minimal {
872 write!(out, "\nBacktrace omitted.\n\nRun with ")?;
873 out.set_color(&self.colors.env_var)?;
874 write!(out, "RUST_BACKTRACE=1")?;
875 out.reset()?;
876 writeln!(out, " environment variable to display it.")?;
877 } else {
878 write!(out, "\nRun with ")?;
880 out.set_color(&self.colors.env_var)?;
881 write!(out, "COLORBT_SHOW_HIDDEN=1")?;
882 out.reset()?;
883 writeln!(out, " environment variable to disable frame filtering.")?;
884 }
885 if self.current_verbosity() <= Verbosity::Medium {
886 write!(out, "Run with ")?;
887 out.set_color(&self.colors.env_var)?;
888 write!(out, "RUST_BACKTRACE=full")?;
889 out.reset()?;
890 writeln!(out, " to include source snippets.")?;
891 }
892
893 if self.current_verbosity() >= Verbosity::Medium {
894 match capture_backtrace() {
895 Ok(trace) => self.print_trace(&*trace, out)?,
896 Err(e) => {
897 out.set_color(&self.colors.header)?;
898 writeln!(out, "\nFailed to capture backtrace: {e}")?;
899 out.reset()?;
900 }
901 }
902 }
903
904 Ok(())
905 }
906
907 fn current_verbosity(&self) -> Verbosity {
908 if self.is_panic_handler {
909 self.verbosity
910 } else {
911 self.lib_verbosity
912 }
913 }
914
915 fn should_print_addresses(&self) -> bool {
916 self.should_print_addresses
917 }
918}
919
920#[doc(hidden)]
925#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::print_trace` instead`")]
926#[cfg(feature = "use-backtrace-crate")]
927pub fn print_backtrace(trace: &backtrace::Backtrace, s: &mut BacktracePrinter) -> IOResult {
928 s.print_trace(trace, &mut default_output_stream())
929}
930
931#[doc(hidden)]
932#[deprecated(
933 since = "0.4.0",
934 note = "Use `BacktracePrinter::print_panic_info` instead`"
935)]
936#[allow(deprecated, reason = "keep support for older rust versions")]
937pub fn print_panic_info(pi: &PanicInfo, s: &mut BacktracePrinter) -> IOResult {
938 s.print_panic_info(pi, &mut default_output_stream())
939}
940
941