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", "src/libstd", "src/libpanic_unwind", "src/libtest"];
300
301 self.filename.as_deref().is_some_and(|filename| {
303 FILE_PREFIXES.iter().any(|x| {
304 filename.starts_with(x) || filename.components().any(|c| c.as_os_str() == ".cargo")
305 })
306 })
307 }
308
309 pub fn is_post_panic_code(&self) -> bool {
316 const SYM_PREFIXES: &[&str] = &[
317 "_rust_begin_unwind",
318 "rust_begin_unwind",
319 "core::result::unwrap_failed",
320 "core::option::expect_none_failed",
321 "core::panicking::panic_fmt",
322 "color_backtrace::create_panic_handler",
323 "std::panicking::begin_panic",
324 "begin_panic_fmt",
325 "backtrace::capture",
326 ];
327
328 match self.name.as_ref() {
329 Some(name) => SYM_PREFIXES.iter().any(|x| name.starts_with(x)),
330 None => false,
331 }
332 }
333
334 pub fn is_runtime_init_code(&self) -> bool {
337 const SYM_PREFIXES: &[&str] = &[
338 "std::rt::lang_start::",
339 "test::run_test::run_test_inner::",
340 "std::sys_common::backtrace::__rust_begin_short_backtrace",
341 ];
342
343 let (name, file) = match (self.name.as_ref(), self.filename.as_ref()) {
344 (Some(name), Some(filename)) => (name, filename.to_string_lossy()),
345 _ => return false,
346 };
347
348 if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
349 return true;
350 }
351
352 if name == "{{closure}}" && file == "src/libtest/lib.rs" {
354 return true;
355 }
356
357 false
358 }
359
360 fn print_source_if_avail(&self, mut out: impl WriteColor, s: &BacktracePrinter) -> IOResult {
361 let (lineno, filename) = match (self.lineno, self.filename.as_ref()) {
362 (Some(a), Some(b)) => (a, b),
363 _ => return Ok(()),
365 };
366
367 let file = match File::open(filename) {
368 Ok(file) => file,
369 Err(ref e) if e.kind() == ErrorKind::NotFound => return Ok(()),
370 e @ Err(_) => e?,
371 };
372
373 let reader = BufReader::new(file);
375 let start_line = lineno - 2.min(lineno - 1);
376 let surrounding_src = reader.lines().skip(start_line as usize - 1).take(5);
377 for (line, cur_line_no) in surrounding_src.zip(start_line..) {
378 if cur_line_no == lineno {
379 out.set_color(&s.colors.selected_src_ln)?;
381 writeln!(out, "{:>8} > {}", cur_line_no, line?)?;
382 out.reset()?;
383 } else {
384 writeln!(out, "{:>8} │ {}", cur_line_no, line?)?;
385 }
386 }
387
388 Ok(())
389 }
390
391 #[cfg(all(
393 feature = "resolve-modules",
394 unix,
395 not(any(target_os = "macos", target_os = "ios"))
396 ))]
397 fn module_info(&self) -> Option<(String, usize)> {
398 use regex::Regex;
399 use std::path::Path;
400
401 let ip = match self.ip {
402 Some(x) => x,
403 None => return None,
404 };
405
406 let re = Regex::new(
407 r"(?x)
408 ^
409 (?P<start>[0-9a-f]{8,16})
410 -
411 (?P<end>[0-9a-f]{8,16})
412 \s
413 (?P<perm>[-rwxp]{4})
414 \s
415 (?P<offset>[0-9a-f]{8})
416 \s
417 [0-9a-f]+:[0-9a-f]+
418 \s
419 [0-9]+
420 \s+
421 (?P<path>.*)
422 $
423 ",
424 )
425 .unwrap();
426
427 let mapsfile = File::open("/proc/self/maps").expect("Unable to open /proc/self/maps");
428
429 for line in BufReader::new(mapsfile).lines() {
430 let line = line.unwrap();
431 if let Some(caps) = re.captures(&line) {
432 let (start, end, path) = (
433 usize::from_str_radix(caps.name("start").unwrap().as_str(), 16).unwrap(),
434 usize::from_str_radix(caps.name("end").unwrap().as_str(), 16).unwrap(),
435 caps.name("path").unwrap().as_str().to_string(),
436 );
437 if ip >= start && ip < end {
438 return if let Some(filename) = Path::new(&path).file_name() {
439 Some((filename.to_str().unwrap().to_string(), start))
440 } else {
441 None
442 };
443 }
444 }
445 }
446
447 None
448 }
449
450 #[cfg(not(all(
451 feature = "resolve-modules",
452 unix,
453 not(any(target_os = "macos", target_os = "ios"))
454 )))]
455 fn module_info(&self) -> Option<(String, usize)> {
456 None
457 }
458
459 fn print(&self, i: usize, out: &mut impl WriteColor, s: &BacktracePrinter) -> IOResult {
460 let is_dependency_code = self.is_dependency_code();
461
462 write!(out, "{:>2}: ", i)?;
464
465 if let Some(ip) = self.ip {
466 if s.should_print_addresses() {
467 if let Some((module_name, module_base)) = self.module_info() {
468 write!(out, "{}:0x{:08x} - ", module_name, ip - module_base)?;
469 } else {
470 write!(out, "0x{:016x} - ", ip)?;
471 }
472 }
473 }
474
475 let name = self.name.as_deref().unwrap_or("<unknown>");
478 let has_hash_suffix = name.len() > 19
479 && &name[name.len() - 19..name.len() - 16] == "::h"
480 && name[name.len() - 16..]
481 .chars()
482 .all(|x| x.is_ascii_hexdigit());
483
484 out.set_color(if is_dependency_code {
486 &s.colors.dependency_code
487 } else {
488 &s.colors.crate_code
489 })?;
490
491 if has_hash_suffix {
492 write!(out, "{}", &name[..name.len() - 19])?;
493 if s.strip_function_hash {
494 writeln!(out)?;
495 } else {
496 out.set_color(if is_dependency_code {
497 &s.colors.dependency_code_hash
498 } else {
499 &s.colors.crate_code_hash
500 })?;
501 writeln!(out, "{}", &name[name.len() - 19..])?;
502 }
503 } else {
504 writeln!(out, "{}", name)?;
505 }
506
507 out.reset()?;
508
509 if let Some(ref file) = self.filename {
511 let filestr = file.to_str().unwrap_or("<bad utf8>");
512 let lineno = self
513 .lineno
514 .map_or("<unknown line>".to_owned(), |x| x.to_string());
515 writeln!(out, " at {}:{}", filestr, lineno)?;
516 } else {
517 writeln!(out, " at <unknown source file>")?;
518 }
519
520 if s.current_verbosity() >= Verbosity::Full {
522 self.print_source_if_avail(out, s)?;
523 }
524
525 Ok(())
526 }
527}
528
529pub fn default_frame_filter(frames: &mut Vec<&Frame>) {
533 let top_cutoff = frames
534 .iter()
535 .rposition(|x| x.is_post_panic_code())
536 .map(|x| x + 2) .unwrap_or(0);
538
539 let bottom_cutoff = frames
540 .iter()
541 .position(|x| x.is_runtime_init_code())
542 .unwrap_or(frames.len());
543
544 let rng = top_cutoff..=bottom_cutoff;
545 frames.retain(|x| rng.contains(&x.n))
546}
547
548#[derive(Debug, Clone)]
554pub struct ColorScheme {
555 pub frames_omitted_msg: ColorSpec,
556 pub header: ColorSpec,
557 pub msg_loc_prefix: ColorSpec,
558 pub src_loc: ColorSpec,
559 pub src_loc_separator: ColorSpec,
560 pub env_var: ColorSpec,
561 pub dependency_code: ColorSpec,
562 pub dependency_code_hash: ColorSpec,
563 pub crate_code: ColorSpec,
564 pub crate_code_hash: ColorSpec,
565 pub selected_src_ln: ColorSpec,
566}
567
568impl ColorScheme {
569 fn cs(fg: Option<Color>, intense: bool, bold: bool) -> ColorSpec {
571 let mut cs = ColorSpec::new();
572 cs.set_fg(fg);
573 cs.set_bold(bold);
574 cs.set_intense(intense);
575 cs
576 }
577
578 pub fn classic() -> Self {
580 Self {
581 frames_omitted_msg: Self::cs(Some(Color::Cyan), true, false),
582 header: Self::cs(Some(Color::Red), false, false),
583 msg_loc_prefix: Self::cs(Some(Color::Cyan), false, false),
584 src_loc: Self::cs(Some(Color::Magenta), false, false),
585 src_loc_separator: Self::cs(Some(Color::White), false, false),
586 env_var: Self::cs(None, false, true),
587 dependency_code: Self::cs(Some(Color::Green), false, false),
588 dependency_code_hash: Self::cs(Some(Color::Black), true, false),
589 crate_code: Self::cs(Some(Color::Red), true, false),
590 crate_code_hash: Self::cs(Some(Color::Black), true, false),
591 selected_src_ln: Self::cs(None, false, true),
592 }
593 }
594}
595
596impl Default for ColorScheme {
597 fn default() -> Self {
598 Self::classic()
599 }
600}
601
602#[doc(hidden)]
603#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter` instead.")]
604pub type Settings = BacktracePrinter;
605
606#[derive(Clone)]
608pub struct BacktracePrinter {
609 message: String,
610 verbosity: Verbosity,
611 lib_verbosity: Verbosity,
612 strip_function_hash: bool,
613 is_panic_handler: bool,
614 colors: ColorScheme,
615 filters: Vec<Arc<FilterCallback>>,
616 should_print_addresses: bool,
617}
618
619impl Default for BacktracePrinter {
620 fn default() -> Self {
621 Self {
622 verbosity: Verbosity::from_env(),
623 lib_verbosity: Verbosity::lib_from_env(),
624 message: "The application panicked (crashed).".to_owned(),
625 strip_function_hash: false,
626 colors: ColorScheme::classic(),
627 is_panic_handler: false,
628 filters: vec![Arc::new(default_frame_filter)],
629 should_print_addresses: false,
630 }
631 }
632}
633
634impl std::fmt::Debug for BacktracePrinter {
635 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
636 fmt.debug_struct("Settings")
637 .field("message", &self.message)
638 .field("verbosity", &self.verbosity)
639 .field("lib_verbosity", &self.lib_verbosity)
640 .field("strip_function_hash", &self.strip_function_hash)
641 .field("is_panic_handler", &self.is_panic_handler)
642 .field("print_addresses", &self.should_print_addresses)
643 .field("colors", &self.colors)
644 .finish()
645 }
646}
647
648impl BacktracePrinter {
650 pub fn new() -> Self {
652 Self::default()
653 }
654
655 pub fn color_scheme(mut self, colors: ColorScheme) -> Self {
659 self.colors = colors;
660 self
661 }
662
663 pub fn message(mut self, message: impl Into<String>) -> Self {
667 self.message = message.into();
668 self
669 }
670
671 pub fn verbosity(mut self, v: Verbosity) -> Self {
675 self.verbosity = v;
676 self
677 }
678
679 pub fn lib_verbosity(mut self, v: Verbosity) -> Self {
683 self.lib_verbosity = v;
684 self
685 }
686
687 pub fn strip_function_hash(mut self, strip: bool) -> Self {
691 self.strip_function_hash = strip;
692 self
693 }
694
695 pub fn print_addresses(mut self, val: bool) -> Self {
699 self.should_print_addresses = val;
700 self
701 }
702
703 pub fn add_frame_filter(mut self, filter: Box<FilterCallback>) -> Self {
719 self.filters.push(filter.into());
720 self
721 }
722
723 pub fn clear_frame_filters(mut self) -> Self {
725 self.filters.clear();
726 self
727 }
728}
729
730impl BacktracePrinter {
732 pub fn install(self, out: impl WriteColor + Sync + Send + 'static) {
737 std::panic::set_hook(self.into_panic_handler(out))
738 }
739
740 #[allow(deprecated, reason = "keep support for older rust versions")]
744 pub fn into_panic_handler(
745 mut self,
746 out: impl WriteColor + Sync + Send + 'static,
747 ) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
748 self.is_panic_handler = true;
749 let out_stream_mutex = Mutex::new(out);
750 Box::new(move |pi| {
751 let mut lock = out_stream_mutex.lock().unwrap();
752 if let Err(e) = self.print_panic_info(pi, &mut *lock) {
753 eprintln!("Error while printing panic: {:?}", e);
756 }
757 })
758 }
759
760 pub fn print_trace(&self, trace: &dyn Backtrace, out: &mut impl WriteColor) -> IOResult {
762 writeln!(out, "{:━^80}", " BACKTRACE ")?;
763
764 let frames = trace.frames();
766
767 let mut filtered_frames = frames.iter().collect();
768 match env::var("COLORBT_SHOW_HIDDEN").ok().as_deref() {
769 Some("1") | Some("on") | Some("y") => (),
770 _ => {
771 for filter in &self.filters {
772 filter(&mut filtered_frames);
773 }
774 }
775 }
776
777 if filtered_frames.is_empty() {
778 return writeln!(out, "<empty backtrace>");
780 }
781
782 filtered_frames.sort_by_key(|x| x.n);
784
785 macro_rules! print_hidden {
786 ($n:expr) => {
787 out.set_color(&self.colors.frames_omitted_msg)?;
788 let n = $n;
789 let text = format!(
790 "{decorator} {n} frame{plural} hidden {decorator}",
791 n = n,
792 plural = if n == 1 { "" } else { "s" },
793 decorator = "⋮",
794 );
795 writeln!(out, "{:^80}", text)?;
796 out.reset()?;
797 };
798 }
799
800 let mut last_n = 0;
801 for frame in &filtered_frames {
802 let frame_delta = frame.n - last_n - 1;
803 if frame_delta != 0 {
804 print_hidden!(frame_delta);
805 }
806 frame.print(frame.n, out, self)?;
807 last_n = frame.n;
808 }
809
810 let last_filtered_n = filtered_frames.last().unwrap().n;
811 let last_unfiltered_n = frames.last().unwrap().n;
812 if last_filtered_n < last_unfiltered_n {
813 print_hidden!(last_unfiltered_n - last_filtered_n);
814 }
815
816 Ok(())
817 }
818
819 pub fn format_trace_to_string(&self, trace: &dyn Backtrace) -> IOResult<String> {
821 let mut ansi = Ansi::new(vec![]);
823 self.print_trace(trace, &mut ansi)?;
824 Ok(String::from_utf8(ansi.into_inner()).unwrap())
825 }
826
827 #[allow(deprecated, reason = "keep support for older rust versions")]
829 pub fn print_panic_info(&self, pi: &PanicInfo, out: &mut impl WriteColor) -> IOResult {
830 out.set_color(&self.colors.header)?;
831 writeln!(out, "{}", self.message)?;
832 out.reset()?;
833
834 let payload = pi
836 .payload()
837 .downcast_ref::<String>()
838 .map(String::as_str)
839 .or_else(|| pi.payload().downcast_ref::<&str>().cloned())
840 .unwrap_or("<non string panic payload>");
841
842 write!(out, "Message: ")?;
843 out.set_color(&self.colors.msg_loc_prefix)?;
844 writeln!(out, "{}", payload)?;
845 out.reset()?;
846
847 write!(out, "Location: ")?;
849 if let Some(loc) = pi.location() {
850 out.set_color(&self.colors.src_loc)?;
851 write!(out, "{}", loc.file())?;
852 out.set_color(&self.colors.src_loc_separator)?;
853 write!(out, ":")?;
854 out.set_color(&self.colors.src_loc)?;
855 writeln!(out, "{}", loc.line())?;
856 out.reset()?;
857 } else {
858 writeln!(out, "<unknown>")?;
859 }
860
861 if self.current_verbosity() == Verbosity::Minimal {
863 write!(out, "\nBacktrace omitted.\n\nRun with ")?;
864 out.set_color(&self.colors.env_var)?;
865 write!(out, "RUST_BACKTRACE=1")?;
866 out.reset()?;
867 writeln!(out, " environment variable to display it.")?;
868 } else {
869 write!(out, "\nRun with ")?;
871 out.set_color(&self.colors.env_var)?;
872 write!(out, "COLORBT_SHOW_HIDDEN=1")?;
873 out.reset()?;
874 writeln!(out, " environment variable to disable frame filtering.")?;
875 }
876 if self.current_verbosity() <= Verbosity::Medium {
877 write!(out, "Run with ")?;
878 out.set_color(&self.colors.env_var)?;
879 write!(out, "RUST_BACKTRACE=full")?;
880 out.reset()?;
881 writeln!(out, " to include source snippets.")?;
882 }
883
884 if self.current_verbosity() >= Verbosity::Medium {
885 match capture_backtrace() {
886 Ok(trace) => self.print_trace(&*trace, out)?,
887 Err(e) => {
888 out.set_color(&self.colors.header)?;
889 writeln!(out, "\nFailed to capture backtrace: {e}")?;
890 out.reset()?;
891 }
892 }
893 }
894
895 Ok(())
896 }
897
898 fn current_verbosity(&self) -> Verbosity {
899 if self.is_panic_handler {
900 self.verbosity
901 } else {
902 self.lib_verbosity
903 }
904 }
905
906 fn should_print_addresses(&self) -> bool {
907 self.should_print_addresses
908 }
909}
910
911#[doc(hidden)]
916#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::print_trace` instead`")]
917#[cfg(feature = "use-backtrace-crate")]
918pub fn print_backtrace(trace: &backtrace::Backtrace, s: &mut BacktracePrinter) -> IOResult {
919 s.print_trace(trace, &mut default_output_stream())
920}
921
922#[doc(hidden)]
923#[deprecated(
924 since = "0.4.0",
925 note = "Use `BacktracePrinter::print_panic_info` instead`"
926)]
927#[allow(deprecated, reason = "keep support for older rust versions")]
928pub fn print_panic_info(pi: &PanicInfo, s: &mut BacktracePrinter) -> IOResult {
929 s.print_panic_info(pi, &mut default_output_stream())
930}
931
932