1use crate::{
4 section::PanicMessage,
5 writers::{EnvSection, WriterExt},
6};
7use fmt::Display;
8use indenter::{indented, Format};
9use owo_colors::{style, OwoColorize, Style};
10use std::env;
11use std::fmt::Write as _;
12use std::{fmt, path::PathBuf, sync::Arc};
13
14#[derive(Debug)]
15struct InstallError;
16
17impl fmt::Display for InstallError {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 f.write_str("could not install the BacktracePrinter as another was already installed")
20 }
21}
22
23impl std::error::Error for InstallError {}
24
25#[derive(Debug)]
26struct InstallThemeError;
27
28impl fmt::Display for InstallThemeError {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 f.write_str("could not set the provided `Theme` globally as another was already set")
31 }
32}
33
34impl std::error::Error for InstallThemeError {}
35
36#[derive(Debug)]
37struct InstallColorSpantraceThemeError;
38
39impl fmt::Display for InstallColorSpantraceThemeError {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 f.write_str("could not set the provided `Theme` via `color_spantrace::set_theme` globally as another was already set")
42 }
43}
44
45impl std::error::Error for InstallColorSpantraceThemeError {}
46
47#[derive(Debug, Copy, Clone, Default)]
49pub struct Theme {
50 pub(crate) file: Style,
51 pub(crate) line_number: Style,
52 pub(crate) spantrace_target: Style,
53 pub(crate) spantrace_fields: Style,
54 pub(crate) active_line: Style,
55 pub(crate) error: Style,
56 pub(crate) help_info_note: Style,
57 pub(crate) help_info_warning: Style,
58 pub(crate) help_info_suggestion: Style,
59 pub(crate) help_info_error: Style,
60 pub(crate) dependency_code: Style,
61 pub(crate) crate_code: Style,
62 pub(crate) code_hash: Style,
63 pub(crate) panic_header: Style,
64 pub(crate) panic_message: Style,
65 pub(crate) panic_file: Style,
66 pub(crate) panic_line_number: Style,
67 pub(crate) hidden_frames: Style,
68}
69
70macro_rules! theme_setters {
71 ($(#[$meta:meta] $name:ident),* $(,)?) => {
72 $(
73 #[$meta]
74 pub fn $name(mut self, style: Style) -> Self {
75 self.$name = style;
76 self
77 }
78 )*
79 };
80}
81
82impl Theme {
83 pub fn new() -> Self {
85 Self::default()
86 }
87
88 pub fn dark() -> Self {
90 Self {
91 file: style().purple(),
92 line_number: style().purple(),
93 active_line: style().white().bold(),
94 error: style().bright_red(),
95 help_info_note: style().bright_cyan(),
96 help_info_warning: style().bright_yellow(),
97 help_info_suggestion: style().bright_cyan(),
98 help_info_error: style().bright_red(),
99 dependency_code: style().green(),
100 crate_code: style().bright_red(),
101 code_hash: style().bright_black(),
102 panic_header: style().red(),
103 panic_message: style().cyan(),
104 panic_file: style().purple(),
105 panic_line_number: style().purple(),
106 hidden_frames: style().bright_cyan(),
107 spantrace_target: style().bright_red(),
108 spantrace_fields: style().bright_cyan(),
109 }
110 }
111
112 pub fn light() -> Self {
116 Self {
117 file: style().purple(),
118 line_number: style().purple(),
119 spantrace_target: style().red(),
120 spantrace_fields: style().blue(),
121 active_line: style().bold(),
122 error: style().red(),
123 help_info_note: style().blue(),
124 help_info_warning: style().bright_red(),
125 help_info_suggestion: style().blue(),
126 help_info_error: style().red(),
127 dependency_code: style().green(),
128 crate_code: style().red(),
129 code_hash: style().bright_black(),
130 panic_header: style().red(),
131 panic_message: style().blue(),
132 panic_file: style().purple(),
133 panic_line_number: style().purple(),
134 hidden_frames: style().blue(),
135 }
136 }
137
138 theme_setters! {
139 file,
141 line_number,
143 spantrace_target,
145 spantrace_fields,
147 active_line,
149 error,
152 help_info_note,
154 help_info_warning,
156 help_info_suggestion,
158 help_info_error,
160 dependency_code,
162 crate_code,
164 code_hash,
166 panic_header,
168 panic_message,
170 panic_file,
172 panic_line_number,
174 hidden_frames,
176 }
177}
178
179#[derive(Debug)]
181#[non_exhaustive]
182pub struct Frame {
183 pub n: usize,
185 pub name: Option<String>,
187 pub lineno: Option<u32>,
189 pub filename: Option<PathBuf>,
191}
192
193#[derive(Debug)]
194struct StyledFrame<'a>(&'a Frame, Theme);
195
196impl<'a> fmt::Display for StyledFrame<'a> {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 let Self(frame, theme) = self;
199
200 let is_dependency_code = frame.is_dependency_code();
201
202 write!(f, "{:>2}: ", frame.n)?;
204
205 let name = frame.name.as_deref().unwrap_or("<unknown>");
208 let has_hash_suffix = name.len() > 19
209 && &name[name.len() - 19..name.len() - 16] == "::h"
210 && name[name.len() - 16..]
211 .chars()
212 .all(|x| x.is_ascii_hexdigit());
213
214 let hash_suffix = if has_hash_suffix {
215 &name[name.len() - 19..]
216 } else {
217 "<unknown>"
218 };
219
220 let name = if has_hash_suffix {
222 &name[..name.len() - 19]
223 } else {
224 name
225 };
226
227 if is_dependency_code {
228 write!(f, "{}", (name).style(theme.dependency_code))?;
229 } else {
230 write!(f, "{}", (name).style(theme.crate_code))?;
231 }
232
233 write!(f, "{}", (hash_suffix).style(theme.code_hash))?;
234
235 let mut separated = f.header("\n");
236
237 let file = frame.filename.as_ref().map(|path| path.display());
239 let file: &dyn fmt::Display = if let Some(ref filename) = file {
240 filename
241 } else {
242 &"<unknown source file>"
243 };
244 let lineno = frame
245 .lineno
246 .map_or("<unknown line>".to_owned(), |x| x.to_string());
247 write!(
248 &mut separated.ready(),
249 " at {}:{}",
250 file.style(theme.file),
251 lineno.style(theme.line_number),
252 )?;
253
254 let v = if std::thread::panicking() {
255 panic_verbosity()
256 } else {
257 lib_verbosity()
258 };
259
260 if v >= Verbosity::Full {
262 write!(&mut separated.ready(), "{}", SourceSection(frame, *theme))?;
263 }
264
265 Ok(())
266 }
267}
268
269struct SourceSection<'a>(&'a Frame, Theme);
270
271impl fmt::Display for SourceSection<'_> {
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 let Self(frame, theme) = self;
274
275 let (lineno, filename) = match (frame.lineno, frame.filename.as_ref()) {
276 (Some(a), Some(b)) => (a, b),
277 _ => return Ok(()),
279 };
280
281 let file = match std::fs::File::open(filename) {
282 Ok(file) => file,
283 Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
284 e @ Err(_) => e.unwrap(),
285 };
286
287 use std::fmt::Write;
288 use std::io::BufRead;
289
290 let reader = std::io::BufReader::new(file);
292 let start_line = lineno - 2.min(lineno - 1);
293 let surrounding_src = reader.lines().skip(start_line as usize - 1).take(5);
294 let mut separated = f.header("\n");
295 let mut f = separated.in_progress();
296 for (line, cur_line_no) in surrounding_src.zip(start_line..) {
297 let line = line.unwrap();
298 if cur_line_no == lineno {
299 write!(
300 &mut f,
301 "{:>8} {} {}",
302 cur_line_no.style(theme.active_line),
303 ">".style(theme.active_line),
304 line.style(theme.active_line),
305 )?;
306 } else {
307 write!(&mut f, "{:>8} │ {}", cur_line_no, line)?;
308 }
309 f = separated.ready();
310 }
311
312 Ok(())
313 }
314}
315
316impl Frame {
317 fn is_dependency_code(&self) -> bool {
318 const SYM_PREFIXES: &[&str] = &[
319 "std::",
320 "core::",
321 "backtrace::backtrace::",
322 "_rust_begin_unwind",
323 "color_traceback::",
324 "__rust_",
325 "___rust_",
326 "__pthread",
327 "_main",
328 "main",
329 "__scrt_common_main_seh",
330 "BaseThreadInitThunk",
331 "_start",
332 "__libc_start_main",
333 "start_thread",
334 ];
335
336 if let Some(ref name) = self.name {
338 if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
339 return true;
340 }
341 }
342
343 const FILE_PREFIXES: &[&str] = &[
344 "/rustc/",
345 "src/libstd/",
346 "src/libpanic_unwind/",
347 "src/libtest/",
348 ];
349
350 if let Some(ref filename) = self.filename {
352 let filename = filename.to_string_lossy();
353 if FILE_PREFIXES.iter().any(|x| filename.starts_with(x))
354 || filename.contains("/.cargo/registry/src/")
355 {
356 return true;
357 }
358 }
359
360 false
361 }
362
363 fn is_post_panic_code(&self) -> bool {
370 const SYM_PREFIXES: &[&str] = &[
371 "_rust_begin_unwind",
372 "rust_begin_unwind",
373 "core::result::unwrap_failed",
374 "core::option::expect_none_failed",
375 "core::panicking::panic_fmt",
376 "color_backtrace::create_panic_handler",
377 "std::panicking::begin_panic",
378 "begin_panic_fmt",
379 "failure::backtrace::Backtrace::new",
380 "backtrace::capture",
381 "failure::error_message::err_msg",
382 "<failure::error::Error as core::convert::From<F>>::from",
383 ];
384
385 match self.name.as_ref() {
386 Some(name) => SYM_PREFIXES.iter().any(|x| name.starts_with(x)),
387 None => false,
388 }
389 }
390
391 fn is_runtime_init_code(&self) -> bool {
394 const SYM_PREFIXES: &[&str] = &[
395 "std::rt::lang_start::",
396 "test::run_test::run_test_inner::",
397 "std::sys_common::backtrace::__rust_begin_short_backtrace",
398 ];
399
400 let (name, file) = match (self.name.as_ref(), self.filename.as_ref()) {
401 (Some(name), Some(filename)) => (name, filename.to_string_lossy()),
402 _ => return false,
403 };
404
405 if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
406 return true;
407 }
408
409 if name == "{{closure}}" && file == "src/libtest/lib.rs" {
411 return true;
412 }
413
414 false
415 }
416}
417
418pub struct HookBuilder {
420 filters: Vec<Box<FilterCallback>>,
421 capture_span_trace_by_default: bool,
422 display_env_section: bool,
423 #[cfg(feature = "track-caller")]
424 display_location_section: bool,
425 panic_section: Option<Box<dyn Display + Send + Sync + 'static>>,
426 panic_message: Option<Box<dyn PanicMessage>>,
427 theme: Theme,
428 #[cfg(feature = "issue-url")]
429 issue_url: Option<String>,
430 #[cfg(feature = "issue-url")]
431 issue_metadata: Vec<(String, Box<dyn Display + Send + Sync + 'static>)>,
432 #[cfg(feature = "issue-url")]
433 issue_filter: Arc<IssueFilterCallback>,
434}
435
436impl HookBuilder {
437 pub fn new() -> Self {
455 Self::blank()
456 .add_default_filters()
457 .capture_span_trace_by_default(true)
458 }
459
460 pub fn blank() -> Self {
462 HookBuilder {
463 filters: vec![],
464 capture_span_trace_by_default: false,
465 display_env_section: true,
466 #[cfg(feature = "track-caller")]
467 display_location_section: true,
468 panic_section: None,
469 panic_message: None,
470 theme: Theme::dark(),
471 #[cfg(feature = "issue-url")]
472 issue_url: None,
473 #[cfg(feature = "issue-url")]
474 issue_metadata: vec![],
475 #[cfg(feature = "issue-url")]
476 issue_filter: Arc::new(|_| true),
477 }
478 }
479
480 pub fn theme(mut self, theme: Theme) -> Self {
484 self.theme = theme;
485 self
486 }
487
488 pub fn panic_section<S: Display + Send + Sync + 'static>(mut self, section: S) -> Self {
500 self.panic_section = Some(Box::new(section));
501 self
502 }
503
504 pub fn panic_message<S: PanicMessage>(mut self, section: S) -> Self {
557 self.panic_message = Some(Box::new(section));
558 self
559 }
560
561 #[cfg(feature = "issue-url")]
581 #[cfg_attr(docsrs, doc(cfg(feature = "issue-url")))]
582 pub fn issue_url<S: ToString>(mut self, url: S) -> Self {
583 self.issue_url = Some(url.to_string());
584 self
585 }
586
587 #[cfg(feature = "issue-url")]
601 #[cfg_attr(docsrs, doc(cfg(feature = "issue-url")))]
602 pub fn add_issue_metadata<K, V>(mut self, key: K, value: V) -> Self
603 where
604 K: Display,
605 V: Display + Send + Sync + 'static,
606 {
607 let pair = (key.to_string(), Box::new(value) as _);
608 self.issue_metadata.push(pair);
609 self
610 }
611
612 #[cfg(feature = "issue-url")]
637 #[cfg_attr(docsrs, doc(cfg(feature = "issue-url")))]
638 pub fn issue_filter<F>(mut self, predicate: F) -> Self
639 where
640 F: Fn(crate::ErrorKind<'_>) -> bool + Send + Sync + 'static,
641 {
642 self.issue_filter = Arc::new(predicate);
643 self
644 }
645
646 pub fn capture_span_trace_by_default(mut self, cond: bool) -> Self {
648 self.capture_span_trace_by_default = cond;
649 self
650 }
651
652 pub fn display_env_section(mut self, cond: bool) -> Self {
654 self.display_env_section = cond;
655 self
656 }
657
658 #[cfg(feature = "track-caller")]
664 #[cfg_attr(docsrs, doc(cfg(feature = "track-caller")))]
665 pub fn display_location_section(mut self, cond: bool) -> Self {
666 self.display_location_section = cond;
667 self
668 }
669
670 pub fn add_frame_filter(mut self, filter: Box<FilterCallback>) -> Self {
697 self.filters.push(filter);
698 self
699 }
700
701 pub fn install(self) -> Result<(), crate::eyre::Report> {
703 let (panic_hook, eyre_hook) = self.try_into_hooks()?;
704 eyre_hook.install()?;
705 panic_hook.install();
706 Ok(())
707 }
708
709 pub fn add_default_filters(self) -> Self {
711 self.add_frame_filter(Box::new(default_frame_filter))
712 .add_frame_filter(Box::new(eyre_frame_filters))
713 }
714
715 pub fn into_hooks(self) -> (PanicHook, EyreHook) {
718 self.try_into_hooks().expect("into_hooks should only be called when no `color_spantrace` themes have previously been set")
719 }
720
721 pub fn try_into_hooks(self) -> Result<(PanicHook, EyreHook), crate::eyre::Report> {
724 let theme = self.theme;
725 #[cfg(feature = "issue-url")]
726 let metadata = Arc::new(self.issue_metadata);
727 let panic_hook = PanicHook {
728 filters: self.filters.into(),
729 section: self.panic_section,
730 #[cfg(feature = "capture-spantrace")]
731 capture_span_trace_by_default: self.capture_span_trace_by_default,
732 display_env_section: self.display_env_section,
733 panic_message: self
734 .panic_message
735 .unwrap_or_else(|| Box::new(DefaultPanicMessage(theme))),
736 theme,
737 #[cfg(feature = "issue-url")]
738 issue_url: self.issue_url.clone(),
739 #[cfg(feature = "issue-url")]
740 issue_metadata: metadata.clone(),
741 #[cfg(feature = "issue-url")]
742 issue_filter: self.issue_filter.clone(),
743 };
744
745 let eyre_hook = EyreHook {
746 filters: panic_hook.filters.clone(),
747 #[cfg(feature = "capture-spantrace")]
748 capture_span_trace_by_default: self.capture_span_trace_by_default,
749 display_env_section: self.display_env_section,
750 #[cfg(feature = "track-caller")]
751 display_location_section: self.display_location_section,
752 theme,
753 #[cfg(feature = "issue-url")]
754 issue_url: self.issue_url,
755 #[cfg(feature = "issue-url")]
756 issue_metadata: metadata,
757 #[cfg(feature = "issue-url")]
758 issue_filter: self.issue_filter,
759 };
760
761 #[cfg(feature = "capture-spantrace")]
762 eyre::WrapErr::wrap_err(color_spantrace::set_theme(self.theme.into()), "could not set the provided `Theme` via `color_spantrace::set_theme` globally as another was already set")?;
763
764 Ok((panic_hook, eyre_hook))
765 }
766}
767
768#[cfg(feature = "capture-spantrace")]
769impl From<Theme> for color_spantrace::Theme {
770 fn from(src: Theme) -> color_spantrace::Theme {
771 color_spantrace::Theme::new()
772 .file(src.file)
773 .line_number(src.line_number)
774 .target(src.spantrace_target)
775 .fields(src.spantrace_fields)
776 .active_line(src.active_line)
777 }
778}
779
780#[allow(missing_docs)]
781impl Default for HookBuilder {
782 fn default() -> Self {
783 Self::new()
784 }
785}
786
787fn default_frame_filter(frames: &mut Vec<&Frame>) {
788 let top_cutoff = frames
789 .iter()
790 .rposition(|x| x.is_post_panic_code())
791 .map(|x| x + 2) .unwrap_or(0);
793
794 let bottom_cutoff = frames
795 .iter()
796 .position(|x| x.is_runtime_init_code())
797 .unwrap_or(frames.len());
798
799 let rng = top_cutoff..=bottom_cutoff;
800 frames.retain(|x| rng.contains(&x.n))
801}
802
803fn eyre_frame_filters(frames: &mut Vec<&Frame>) {
804 let filters = &[
805 "<color_eyre::Handler as eyre::EyreHandler>::default",
806 "eyre::",
807 "color_eyre::",
808 ];
809
810 frames.retain(|frame| {
811 !filters.iter().any(|f| {
812 let name = if let Some(name) = frame.name.as_ref() {
813 name.as_str()
814 } else {
815 return true;
816 };
817
818 name.starts_with(f)
819 })
820 });
821}
822
823struct DefaultPanicMessage(Theme);
824
825impl PanicMessage for DefaultPanicMessage {
826 fn display(&self, pi: &std::panic::PanicInfo<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
827 let theme = &self.0;
829
830 writeln!(
831 f,
832 "{}",
833 "The application panicked (crashed).".style(theme.panic_header)
834 )?;
835
836 let payload = pi
838 .payload()
839 .downcast_ref::<String>()
840 .map(String::as_str)
841 .or_else(|| pi.payload().downcast_ref::<&str>().cloned())
842 .unwrap_or("<non string panic payload>");
843
844 write!(f, "Message: ")?;
845 writeln!(f, "{}", payload.style(theme.panic_message))?;
846
847 write!(f, "Location: ")?;
849 write!(f, "{}", crate::fmt::LocationSection(pi.location(), *theme))?;
850
851 Ok(())
852 }
853}
854
855pub struct PanicReport<'a> {
857 hook: &'a PanicHook,
858 panic_info: &'a std::panic::PanicInfo<'a>,
859 backtrace: Option<backtrace::Backtrace>,
860 #[cfg(feature = "capture-spantrace")]
861 span_trace: Option<tracing_error::SpanTrace>,
862}
863
864fn print_panic_info(report: &PanicReport<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
865 report.hook.panic_message.display(report.panic_info, f)?;
866
867 let v = panic_verbosity();
868 let capture_bt = v != Verbosity::Minimal;
869
870 let mut separated = f.header("\n\n");
871
872 if let Some(ref section) = report.hook.section {
873 write!(&mut separated.ready(), "{}", section)?;
874 }
875
876 #[cfg(feature = "capture-spantrace")]
877 {
878 if let Some(span_trace) = report.span_trace.as_ref() {
879 write!(
880 &mut separated.ready(),
881 "{}",
882 crate::writers::FormattedSpanTrace(span_trace)
883 )?;
884 }
885 }
886
887 if let Some(bt) = report.backtrace.as_ref() {
888 let fmted_bt = report.hook.format_backtrace(bt);
889 write!(
890 indented(&mut separated.ready()).with_format(Format::Uniform { indentation: " " }),
891 "{}",
892 fmted_bt
893 )?;
894 }
895
896 if report.hook.display_env_section {
897 let env_section = EnvSection {
898 bt_captured: &capture_bt,
899 #[cfg(feature = "capture-spantrace")]
900 span_trace: report.span_trace.as_ref(),
901 };
902
903 write!(&mut separated.ready(), "{}", env_section)?;
904 }
905
906 #[cfg(feature = "issue-url")]
907 {
908 let payload = report.panic_info.payload();
909
910 if report.hook.issue_url.is_some()
911 && (*report.hook.issue_filter)(crate::ErrorKind::NonRecoverable(payload))
912 {
913 let url = report.hook.issue_url.as_ref().unwrap();
914 let payload = payload
915 .downcast_ref::<String>()
916 .map(String::as_str)
917 .or_else(|| payload.downcast_ref::<&str>().cloned())
918 .unwrap_or("<non string panic payload>");
919
920 let issue_section = crate::section::github::IssueSection::new(url, payload)
921 .with_backtrace(report.backtrace.as_ref())
922 .with_location(report.panic_info.location())
923 .with_metadata(&report.hook.issue_metadata);
924
925 #[cfg(feature = "capture-spantrace")]
926 let issue_section = issue_section.with_span_trace(report.span_trace.as_ref());
927
928 write!(&mut separated.ready(), "{}", issue_section)?;
929 }
930 }
931
932 Ok(())
933}
934
935impl fmt::Display for PanicReport<'_> {
936 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
937 print_panic_info(self, f)
938 }
939}
940
941pub struct PanicHook {
943 filters: Arc<[Box<FilterCallback>]>,
944 section: Option<Box<dyn Display + Send + Sync + 'static>>,
945 panic_message: Box<dyn PanicMessage>,
946 theme: Theme,
947 #[cfg(feature = "capture-spantrace")]
948 capture_span_trace_by_default: bool,
949 display_env_section: bool,
950 #[cfg(feature = "issue-url")]
951 issue_url: Option<String>,
952 #[cfg(feature = "issue-url")]
953 issue_metadata: Arc<Vec<(String, Box<dyn Display + Send + Sync + 'static>)>>,
954 #[cfg(feature = "issue-url")]
955 issue_filter: Arc<IssueFilterCallback>,
956}
957
958impl PanicHook {
959 pub(crate) fn format_backtrace<'a>(
960 &'a self,
961 trace: &'a backtrace::Backtrace,
962 ) -> BacktraceFormatter<'a> {
963 BacktraceFormatter {
964 filters: &self.filters,
965 inner: trace,
966 theme: self.theme,
967 }
968 }
969
970 #[cfg(feature = "capture-spantrace")]
971 fn spantrace_capture_enabled(&self) -> bool {
972 std::env::var("RUST_SPANTRACE")
973 .map(|val| val != "0")
974 .unwrap_or(self.capture_span_trace_by_default)
975 }
976
977 pub fn install(self) {
979 std::panic::set_hook(self.into_panic_hook());
980 }
981
982 pub fn into_panic_hook(
984 self,
985 ) -> Box<dyn Fn(&std::panic::PanicInfo<'_>) + Send + Sync + 'static> {
986 Box::new(move |panic_info| {
987 eprintln!("{}", self.panic_report(panic_info));
988 })
989 }
990
991 pub fn panic_report<'a>(
994 &'a self,
995 panic_info: &'a std::panic::PanicInfo<'_>,
996 ) -> PanicReport<'a> {
997 let v = panic_verbosity();
998 let capture_bt = v != Verbosity::Minimal;
999
1000 #[cfg(feature = "capture-spantrace")]
1001 let span_trace = if self.spantrace_capture_enabled() {
1002 Some(tracing_error::SpanTrace::capture())
1003 } else {
1004 None
1005 };
1006
1007 let backtrace = if capture_bt {
1008 Some(backtrace::Backtrace::new())
1009 } else {
1010 None
1011 };
1012
1013 PanicReport {
1014 panic_info,
1015 #[cfg(feature = "capture-spantrace")]
1016 span_trace,
1017 backtrace,
1018 hook: self,
1019 }
1020 }
1021}
1022
1023pub struct EyreHook {
1025 filters: Arc<[Box<FilterCallback>]>,
1026 #[cfg(feature = "capture-spantrace")]
1027 capture_span_trace_by_default: bool,
1028 display_env_section: bool,
1029 #[cfg(feature = "track-caller")]
1030 display_location_section: bool,
1031 theme: Theme,
1032 #[cfg(feature = "issue-url")]
1033 issue_url: Option<String>,
1034 #[cfg(feature = "issue-url")]
1035 issue_metadata: Arc<Vec<(String, Box<dyn Display + Send + Sync + 'static>)>>,
1036 #[cfg(feature = "issue-url")]
1037 issue_filter: Arc<IssueFilterCallback>,
1038}
1039
1040type HookFunc = Box<
1041 dyn Fn(&(dyn std::error::Error + 'static)) -> Box<dyn eyre::EyreHandler>
1042 + Send
1043 + Sync
1044 + 'static,
1045>;
1046
1047impl EyreHook {
1048 #[allow(unused_variables)]
1049 pub(crate) fn default(&self, error: &(dyn std::error::Error + 'static)) -> crate::Handler {
1050 let backtrace = if lib_verbosity() != Verbosity::Minimal {
1051 Some(backtrace::Backtrace::new())
1052 } else {
1053 None
1054 };
1055
1056 #[cfg(feature = "capture-spantrace")]
1057 let span_trace = if self.spantrace_capture_enabled()
1058 && crate::handler::get_deepest_spantrace(error).is_none()
1059 {
1060 Some(tracing_error::SpanTrace::capture())
1061 } else {
1062 None
1063 };
1064
1065 crate::Handler {
1066 filters: self.filters.clone(),
1067 backtrace,
1068 suppress_backtrace: false,
1069 #[cfg(feature = "capture-spantrace")]
1070 span_trace,
1071 sections: Vec::new(),
1072 display_env_section: self.display_env_section,
1073 #[cfg(feature = "track-caller")]
1074 display_location_section: self.display_location_section,
1075 #[cfg(feature = "issue-url")]
1076 issue_url: self.issue_url.clone(),
1077 #[cfg(feature = "issue-url")]
1078 issue_metadata: self.issue_metadata.clone(),
1079 #[cfg(feature = "issue-url")]
1080 issue_filter: self.issue_filter.clone(),
1081 theme: self.theme,
1082 #[cfg(feature = "track-caller")]
1083 location: None,
1084 }
1085 }
1086
1087 #[cfg(feature = "capture-spantrace")]
1088 fn spantrace_capture_enabled(&self) -> bool {
1089 std::env::var("RUST_SPANTRACE")
1090 .map(|val| val != "0")
1091 .unwrap_or(self.capture_span_trace_by_default)
1092 }
1093
1094 pub fn install(self) -> Result<(), crate::eyre::InstallError> {
1096 crate::eyre::set_hook(self.into_eyre_hook())
1097 }
1098
1099 pub fn into_eyre_hook(self) -> HookFunc {
1101 Box::new(move |e| Box::new(self.default(e)))
1102 }
1103}
1104
1105pub(crate) struct BacktraceFormatter<'a> {
1106 pub(crate) filters: &'a [Box<FilterCallback>],
1107 pub(crate) inner: &'a backtrace::Backtrace,
1108 pub(crate) theme: Theme,
1109}
1110
1111impl fmt::Display for BacktraceFormatter<'_> {
1112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1113 write!(f, "{:━^80}", " BACKTRACE ")?;
1114
1115 let frames: Vec<_> = self
1117 .inner
1118 .frames()
1119 .iter()
1120 .flat_map(|frame| frame.symbols())
1121 .zip(1usize..)
1122 .map(|(sym, n)| Frame {
1123 name: sym.name().map(|x| x.to_string()),
1124 lineno: sym.lineno(),
1125 filename: sym.filename().map(|x| x.into()),
1126 n,
1127 })
1128 .collect();
1129
1130 let mut filtered_frames = frames.iter().collect();
1131 match env::var("COLORBT_SHOW_HIDDEN").ok().as_deref() {
1132 Some("1") | Some("on") | Some("y") => (),
1133 _ => {
1134 for filter in self.filters {
1135 filter(&mut filtered_frames);
1136 }
1137 }
1138 }
1139
1140 if filtered_frames.is_empty() {
1141 return write!(f, "\n<empty backtrace>");
1143 }
1144
1145 let mut separated = f.header("\n");
1146
1147 filtered_frames.sort_by_key(|x| x.n);
1149
1150 let mut buf = String::new();
1151
1152 macro_rules! print_hidden {
1153 ($n:expr) => {
1154 let n = $n;
1155 buf.clear();
1156 write!(
1157 &mut buf,
1158 "{decorator} {n} frame{plural} hidden {decorator}",
1159 n = n,
1160 plural = if n == 1 { "" } else { "s" },
1161 decorator = "⋮",
1162 )
1163 .expect("writing to strings doesn't panic");
1164 write!(
1165 &mut separated.ready(),
1166 "{:^80}",
1167 buf.style(self.theme.hidden_frames)
1168 )?;
1169 };
1170 }
1171
1172 let mut last_n = 0;
1173 for frame in &filtered_frames {
1174 let frame_delta = frame.n - last_n - 1;
1175 if frame_delta != 0 {
1176 print_hidden!(frame_delta);
1177 }
1178 write!(&mut separated.ready(), "{}", StyledFrame(frame, self.theme))?;
1179 last_n = frame.n;
1180 }
1181
1182 let last_filtered_n = filtered_frames.last().unwrap().n;
1183 let last_unfiltered_n = frames.last().unwrap().n;
1184 if last_filtered_n < last_unfiltered_n {
1185 print_hidden!(last_unfiltered_n - last_filtered_n);
1186 }
1187
1188 Ok(())
1189 }
1190}
1191
1192#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1193pub(crate) enum Verbosity {
1194 Minimal,
1195 Medium,
1196 Full,
1197}
1198
1199pub(crate) fn panic_verbosity() -> Verbosity {
1200 match env::var("RUST_BACKTRACE") {
1201 Ok(s) if s == "full" => Verbosity::Full,
1202 Ok(s) if s != "0" => Verbosity::Medium,
1203 _ => Verbosity::Minimal,
1204 }
1205}
1206
1207pub(crate) fn lib_verbosity() -> Verbosity {
1208 match env::var("RUST_LIB_BACKTRACE").or_else(|_| env::var("RUST_BACKTRACE")) {
1209 Ok(s) if s == "full" => Verbosity::Full,
1210 Ok(s) if s != "0" => Verbosity::Medium,
1211 _ => Verbosity::Minimal,
1212 }
1213}
1214
1215pub type FilterCallback = dyn Fn(&mut Vec<&Frame>) + Send + Sync + 'static;
1217
1218#[cfg(feature = "issue-url")]
1220#[cfg_attr(docsrs, doc(cfg(feature = "issue-url")))]
1221pub type IssueFilterCallback = dyn Fn(crate::ErrorKind<'_>) -> bool + Send + Sync + 'static;