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, Copy, Clone, Default)]
16pub struct Theme {
17 pub(crate) file: Style,
18 pub(crate) line_number: Style,
19 pub(crate) spantrace_target: Style,
20 pub(crate) spantrace_fields: Style,
21 pub(crate) active_line: Style,
22 pub(crate) error: Style,
23 pub(crate) help_info_note: Style,
24 pub(crate) help_info_warning: Style,
25 pub(crate) help_info_suggestion: Style,
26 pub(crate) help_info_error: Style,
27 pub(crate) dependency_code: Style,
28 pub(crate) crate_code: Style,
29 pub(crate) code_hash: Style,
30 pub(crate) panic_header: Style,
31 pub(crate) panic_message: Style,
32 pub(crate) panic_file: Style,
33 pub(crate) panic_line_number: Style,
34 pub(crate) hidden_frames: Style,
35}
36
37macro_rules! theme_setters {
38 ($(#[$meta:meta] $name:ident),* $(,)?) => {
39 $(
40 #[$meta]
41 pub fn $name(mut self, style: Style) -> Self {
42 self.$name = style;
43 self
44 }
45 )*
46 };
47}
48
49impl Theme {
50 pub fn new() -> Self {
52 Self::default()
53 }
54
55 pub fn dark() -> Self {
57 Self {
58 file: style().purple(),
59 line_number: style().purple(),
60 active_line: style().white().bold(),
61 error: style().bright_red(),
62 help_info_note: style().bright_cyan(),
63 help_info_warning: style().bright_yellow(),
64 help_info_suggestion: style().bright_cyan(),
65 help_info_error: style().bright_red(),
66 dependency_code: style().green(),
67 crate_code: style().bright_red(),
68 code_hash: style().bright_black(),
69 panic_header: style().red(),
70 panic_message: style().cyan(),
71 panic_file: style().purple(),
72 panic_line_number: style().purple(),
73 hidden_frames: style().bright_cyan(),
74 spantrace_target: style().bright_red(),
75 spantrace_fields: style().bright_cyan(),
76 }
77 }
78
79 pub fn light() -> Self {
83 Self {
84 file: style().purple(),
85 line_number: style().purple(),
86 spantrace_target: style().red(),
87 spantrace_fields: style().blue(),
88 active_line: style().bold(),
89 error: style().red(),
90 help_info_note: style().blue(),
91 help_info_warning: style().bright_red(),
92 help_info_suggestion: style().blue(),
93 help_info_error: style().red(),
94 dependency_code: style().green(),
95 crate_code: style().red(),
96 code_hash: style().bright_black(),
97 panic_header: style().red(),
98 panic_message: style().blue(),
99 panic_file: style().purple(),
100 panic_line_number: style().purple(),
101 hidden_frames: style().blue(),
102 }
103 }
104
105 theme_setters! {
106 file,
108 line_number,
110 spantrace_target,
112 spantrace_fields,
114 active_line,
116 error,
119 help_info_note,
121 help_info_warning,
123 help_info_suggestion,
125 help_info_error,
127 dependency_code,
129 crate_code,
131 code_hash,
133 panic_header,
135 panic_message,
137 panic_file,
139 panic_line_number,
141 hidden_frames,
143 }
144}
145
146#[derive(Debug)]
148#[non_exhaustive]
149pub struct Frame {
150 pub n: usize,
152 pub name: Option<String>,
154 pub lineno: Option<u32>,
156 pub filename: Option<PathBuf>,
158}
159
160#[derive(Debug)]
161struct StyledFrame<'a>(&'a Frame, Theme);
162
163impl fmt::Display for StyledFrame<'_> {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 let Self(frame, theme) = self;
166
167 let is_dependency_code = frame.is_dependency_code();
168
169 write!(f, "{:>2}: ", frame.n)?;
171
172 let name = frame.name.as_deref().unwrap_or("<unknown>");
175 let has_hash_suffix = name.len() > 19
176 && &name[name.len() - 19..name.len() - 16] == "::h"
177 && name[name.len() - 16..]
178 .chars()
179 .all(|x| x.is_ascii_hexdigit());
180
181 let hash_suffix = if has_hash_suffix {
182 &name[name.len() - 19..]
183 } else {
184 "<unknown>"
185 };
186
187 let name = if has_hash_suffix {
189 &name[..name.len() - 19]
190 } else {
191 name
192 };
193
194 if is_dependency_code {
195 write!(f, "{}", (name).style(theme.dependency_code))?;
196 } else {
197 write!(f, "{}", (name).style(theme.crate_code))?;
198 }
199
200 write!(f, "{}", (hash_suffix).style(theme.code_hash))?;
201
202 let mut separated = f.header("\n");
203
204 let file = frame.filename.as_ref().map(|path| path.display());
206 let file: &dyn fmt::Display = if let Some(ref filename) = file {
207 filename
208 } else {
209 &"<unknown source file>"
210 };
211 let lineno = frame
212 .lineno
213 .map_or("<unknown line>".to_owned(), |x| x.to_string());
214 write!(
215 &mut separated.ready(),
216 " at {}:{}",
217 file.style(theme.file),
218 lineno.style(theme.line_number),
219 )?;
220
221 let v = if std::thread::panicking() {
222 panic_verbosity()
223 } else {
224 lib_verbosity()
225 };
226
227 if v >= Verbosity::Full {
229 write!(&mut separated.ready(), "{}", SourceSection(frame, *theme))?;
230 }
231
232 Ok(())
233 }
234}
235
236struct SourceSection<'a>(&'a Frame, Theme);
237
238impl fmt::Display for SourceSection<'_> {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 let Self(frame, theme) = self;
241
242 let (lineno, filename) = match (frame.lineno, frame.filename.as_ref()) {
243 (Some(a), Some(b)) => (a, b),
244 _ => return Ok(()),
246 };
247
248 let file = match std::fs::File::open(filename) {
249 Ok(file) => file,
250 Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
251 e @ Err(_) => e.unwrap(),
252 };
253
254 use std::fmt::Write;
255 use std::io::BufRead;
256
257 let reader = std::io::BufReader::new(file);
259 let start_line = lineno - 2.min(lineno - 1);
260 let surrounding_src = reader.lines().skip(start_line as usize - 1).take(5);
261 let mut separated = f.header("\n");
262 let mut f = separated.in_progress();
263 for (line, cur_line_no) in surrounding_src.zip(start_line..) {
264 let line = line.unwrap();
265 if cur_line_no == lineno {
266 write!(
267 &mut f,
268 "{:>8} {} {}",
269 cur_line_no.style(theme.active_line),
270 ">".style(theme.active_line),
271 line.style(theme.active_line),
272 )?;
273 } else {
274 write!(&mut f, "{:>8} │ {}", cur_line_no, line)?;
275 }
276 f = separated.ready();
277 }
278
279 Ok(())
280 }
281}
282
283impl Frame {
284 fn is_dependency_code(&self) -> bool {
285 const SYM_PREFIXES: &[&str] = &[
286 "std::",
287 "core::",
288 "backtrace::backtrace::",
289 "_rust_begin_unwind",
290 "color_traceback::",
291 "__rust_",
292 "___rust_",
293 "__pthread",
294 "_main",
295 "main",
296 "__scrt_common_main_seh",
297 "BaseThreadInitThunk",
298 "_start",
299 "__libc_start_main",
300 "start_thread",
301 ];
302
303 if let Some(ref name) = self.name {
305 if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
306 return true;
307 }
308 }
309
310 const FILE_PREFIXES: &[&str] = &[
311 "/rustc/",
312 "src/libstd/",
313 "src/libpanic_unwind/",
314 "src/libtest/",
315 ];
316
317 if let Some(ref filename) = self.filename {
319 let filename = filename.to_string_lossy();
320 if FILE_PREFIXES.iter().any(|x| filename.starts_with(x))
321 || filename.contains("/.cargo/registry/src/")
322 {
323 return true;
324 }
325 }
326
327 false
328 }
329
330 fn is_post_panic_code(&self) -> bool {
337 const SYM_PREFIXES: &[&str] = &[
338 "_rust_begin_unwind",
339 "rust_begin_unwind",
340 "core::result::unwrap_failed",
341 "core::option::expect_none_failed",
342 "core::panicking::panic_fmt",
343 "color_backtrace::create_panic_handler",
344 "std::panicking::begin_panic",
345 "begin_panic_fmt",
346 "failure::backtrace::Backtrace::new",
347 "backtrace::capture",
348 "failure::error_message::err_msg",
349 "<failure::error::Error as core::convert::From<F>>::from",
350 ];
351
352 match self.name.as_ref() {
353 Some(name) => SYM_PREFIXES.iter().any(|x| name.starts_with(x)),
354 None => false,
355 }
356 }
357
358 fn is_runtime_init_code(&self) -> bool {
361 const SYM_PREFIXES: &[&str] = &[
362 "std::rt::lang_start::",
363 "test::run_test::run_test_inner::",
364 "std::sys_common::backtrace::__rust_begin_short_backtrace",
365 ];
366
367 let (name, file) = match (self.name.as_ref(), self.filename.as_ref()) {
368 (Some(name), Some(filename)) => (name, filename.to_string_lossy()),
369 _ => return false,
370 };
371
372 if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
373 return true;
374 }
375
376 if name == "{{closure}}" && file == "src/libtest/lib.rs" {
378 return true;
379 }
380
381 false
382 }
383}
384
385pub struct HookBuilder {
387 filters: Vec<Box<FilterCallback>>,
388 capture_span_trace_by_default: bool,
389 display_env_section: bool,
390 #[cfg(feature = "track-caller")]
391 display_location_section: bool,
392 panic_section: Option<Box<dyn Display + Send + Sync + 'static>>,
393 panic_message: Option<Box<dyn PanicMessage>>,
394 theme: Theme,
395 #[cfg(feature = "issue-url")]
396 issue_url: Option<String>,
397 #[cfg(feature = "issue-url")]
398 issue_metadata: Vec<(String, Box<dyn Display + Send + Sync + 'static>)>,
399 #[cfg(feature = "issue-url")]
400 issue_filter: Arc<IssueFilterCallback>,
401}
402
403impl HookBuilder {
404 pub fn new() -> Self {
422 Self::blank()
423 .add_default_filters()
424 .capture_span_trace_by_default(true)
425 }
426
427 pub fn blank() -> Self {
429 HookBuilder {
430 filters: vec![],
431 capture_span_trace_by_default: false,
432 display_env_section: true,
433 #[cfg(feature = "track-caller")]
434 display_location_section: true,
435 panic_section: None,
436 panic_message: None,
437 theme: Theme::dark(),
438 #[cfg(feature = "issue-url")]
439 issue_url: None,
440 #[cfg(feature = "issue-url")]
441 issue_metadata: vec![],
442 #[cfg(feature = "issue-url")]
443 issue_filter: Arc::new(|_| true),
444 }
445 }
446
447 pub fn theme(mut self, theme: Theme) -> Self {
451 self.theme = theme;
452 self
453 }
454
455 pub fn panic_section<S: Display + Send + Sync + 'static>(mut self, section: S) -> Self {
467 self.panic_section = Some(Box::new(section));
468 self
469 }
470
471 pub fn panic_message<S: PanicMessage>(mut self, section: S) -> Self {
524 self.panic_message = Some(Box::new(section));
525 self
526 }
527
528 #[cfg(feature = "issue-url")]
548 #[cfg_attr(docsrs, doc(cfg(feature = "issue-url")))]
549 pub fn issue_url<S: ToString>(mut self, url: S) -> Self {
550 self.issue_url = Some(url.to_string());
551 self
552 }
553
554 #[cfg(feature = "issue-url")]
568 #[cfg_attr(docsrs, doc(cfg(feature = "issue-url")))]
569 pub fn add_issue_metadata<K, V>(mut self, key: K, value: V) -> Self
570 where
571 K: Display,
572 V: Display + Send + Sync + 'static,
573 {
574 let pair = (key.to_string(), Box::new(value) as _);
575 self.issue_metadata.push(pair);
576 self
577 }
578
579 #[cfg(feature = "issue-url")]
604 #[cfg_attr(docsrs, doc(cfg(feature = "issue-url")))]
605 pub fn issue_filter<F>(mut self, predicate: F) -> Self
606 where
607 F: Fn(crate::ErrorKind<'_>) -> bool + Send + Sync + 'static,
608 {
609 self.issue_filter = Arc::new(predicate);
610 self
611 }
612
613 pub fn capture_span_trace_by_default(mut self, cond: bool) -> Self {
615 self.capture_span_trace_by_default = cond;
616 self
617 }
618
619 pub fn display_env_section(mut self, cond: bool) -> Self {
621 self.display_env_section = cond;
622 self
623 }
624
625 #[cfg(feature = "track-caller")]
631 #[cfg_attr(docsrs, doc(cfg(feature = "track-caller")))]
632 pub fn display_location_section(mut self, cond: bool) -> Self {
633 self.display_location_section = cond;
634 self
635 }
636
637 pub fn add_frame_filter(mut self, filter: Box<FilterCallback>) -> Self {
664 self.filters.push(filter);
665 self
666 }
667
668 pub fn install(self) -> Result<(), crate::eyre::Report> {
670 let (panic_hook, eyre_hook) = self.try_into_hooks()?;
671 eyre_hook.install()?;
672 panic_hook.install();
673 Ok(())
674 }
675
676 pub fn add_default_filters(self) -> Self {
678 self.add_frame_filter(Box::new(default_frame_filter))
679 .add_frame_filter(Box::new(eyre_frame_filters))
680 }
681
682 pub fn into_hooks(self) -> (PanicHook, EyreHook) {
685 self.try_into_hooks().expect("into_hooks should only be called when no `color_spantrace` themes have previously been set")
686 }
687
688 pub fn try_into_hooks(self) -> Result<(PanicHook, EyreHook), crate::eyre::Report> {
691 let theme = self.theme;
692 #[cfg(feature = "issue-url")]
693 let metadata = Arc::new(self.issue_metadata);
694 let panic_hook = PanicHook {
695 filters: self.filters.into(),
696 section: self.panic_section,
697 #[cfg(feature = "capture-spantrace")]
698 capture_span_trace_by_default: self.capture_span_trace_by_default,
699 display_env_section: self.display_env_section,
700 panic_message: self
701 .panic_message
702 .unwrap_or_else(|| Box::new(DefaultPanicMessage(theme))),
703 theme,
704 #[cfg(feature = "issue-url")]
705 issue_url: self.issue_url.clone(),
706 #[cfg(feature = "issue-url")]
707 issue_metadata: metadata.clone(),
708 #[cfg(feature = "issue-url")]
709 issue_filter: self.issue_filter.clone(),
710 };
711
712 let eyre_hook = EyreHook {
713 filters: panic_hook.filters.clone(),
714 #[cfg(feature = "capture-spantrace")]
715 capture_span_trace_by_default: self.capture_span_trace_by_default,
716 display_env_section: self.display_env_section,
717 #[cfg(feature = "track-caller")]
718 display_location_section: self.display_location_section,
719 theme,
720 #[cfg(feature = "issue-url")]
721 issue_url: self.issue_url,
722 #[cfg(feature = "issue-url")]
723 issue_metadata: metadata,
724 #[cfg(feature = "issue-url")]
725 issue_filter: self.issue_filter,
726 };
727
728 #[cfg(feature = "capture-spantrace")]
729 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")?;
730
731 Ok((panic_hook, eyre_hook))
732 }
733}
734
735#[cfg(feature = "capture-spantrace")]
736impl From<Theme> for color_spantrace::Theme {
737 fn from(src: Theme) -> color_spantrace::Theme {
738 color_spantrace::Theme::new()
739 .file(src.file)
740 .line_number(src.line_number)
741 .target(src.spantrace_target)
742 .fields(src.spantrace_fields)
743 .active_line(src.active_line)
744 }
745}
746
747#[allow(missing_docs)]
748impl Default for HookBuilder {
749 fn default() -> Self {
750 Self::new()
751 }
752}
753
754fn default_frame_filter(frames: &mut Vec<&Frame>) {
755 let top_cutoff = frames
756 .iter()
757 .rposition(|x| x.is_post_panic_code())
758 .map(|x| x + 2) .unwrap_or(0);
760
761 let bottom_cutoff = frames
762 .iter()
763 .position(|x| x.is_runtime_init_code())
764 .unwrap_or(frames.len());
765
766 let rng = top_cutoff..=bottom_cutoff;
767 frames.retain(|x| rng.contains(&x.n))
768}
769
770fn eyre_frame_filters(frames: &mut Vec<&Frame>) {
771 let filters = &[
772 "<color_eyre::Handler as eyre::EyreHandler>::default",
773 "eyre::",
774 "color_eyre::",
775 ];
776
777 frames.retain(|frame| {
778 !filters.iter().any(|f| {
779 let name = if let Some(name) = frame.name.as_ref() {
780 name.as_str()
781 } else {
782 return true;
783 };
784
785 name.starts_with(f)
786 })
787 });
788}
789
790struct DefaultPanicMessage(Theme);
791
792impl PanicMessage for DefaultPanicMessage {
793 fn display(&self, pi: &std::panic::PanicInfo<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
794 let theme = &self.0;
796
797 writeln!(
798 f,
799 "{}",
800 "The application panicked (crashed).".style(theme.panic_header)
801 )?;
802
803 let payload = pi
805 .payload()
806 .downcast_ref::<String>()
807 .map(String::as_str)
808 .or_else(|| pi.payload().downcast_ref::<&str>().cloned())
809 .unwrap_or("<non string panic payload>");
810
811 write!(f, "Message: ")?;
812 writeln!(f, "{}", payload.style(theme.panic_message))?;
813
814 write!(f, "Location: ")?;
816 write!(f, "{}", crate::fmt::LocationSection(pi.location(), *theme))?;
817
818 Ok(())
819 }
820}
821
822pub struct PanicReport<'a> {
824 hook: &'a PanicHook,
825 panic_info: &'a std::panic::PanicInfo<'a>,
826 backtrace: Option<backtrace::Backtrace>,
827 #[cfg(feature = "capture-spantrace")]
828 span_trace: Option<tracing_error::SpanTrace>,
829}
830
831fn print_panic_info(report: &PanicReport<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
832 report.hook.panic_message.display(report.panic_info, f)?;
833
834 let v = panic_verbosity();
835 let capture_bt = v != Verbosity::Minimal;
836
837 let mut separated = f.header("\n\n");
838
839 if let Some(ref section) = report.hook.section {
840 write!(&mut separated.ready(), "{}", section)?;
841 }
842
843 #[cfg(feature = "capture-spantrace")]
844 {
845 if let Some(span_trace) = report.span_trace.as_ref() {
846 write!(
847 &mut separated.ready(),
848 "{}",
849 crate::writers::FormattedSpanTrace(span_trace)
850 )?;
851 }
852 }
853
854 if let Some(bt) = report.backtrace.as_ref() {
855 let fmted_bt = report.hook.format_backtrace(bt);
856 write!(
857 indented(&mut separated.ready()).with_format(Format::Uniform { indentation: " " }),
858 "{}",
859 fmted_bt
860 )?;
861 }
862
863 if report.hook.display_env_section {
864 let env_section = EnvSection {
865 bt_captured: &capture_bt,
866 #[cfg(feature = "capture-spantrace")]
867 span_trace: report.span_trace.as_ref(),
868 };
869
870 write!(&mut separated.ready(), "{}", env_section)?;
871 }
872
873 #[cfg(feature = "issue-url")]
874 {
875 let payload = report.panic_info.payload();
876
877 if report.hook.issue_url.is_some()
878 && (*report.hook.issue_filter)(crate::ErrorKind::NonRecoverable(payload))
879 {
880 let url = report.hook.issue_url.as_ref().unwrap();
881 let payload = payload
882 .downcast_ref::<String>()
883 .map(String::as_str)
884 .or_else(|| payload.downcast_ref::<&str>().cloned())
885 .unwrap_or("<non string panic payload>");
886
887 let issue_section = crate::section::github::IssueSection::new(url, payload)
888 .with_backtrace(report.backtrace.as_ref())
889 .with_location(report.panic_info.location())
890 .with_metadata(&report.hook.issue_metadata);
891
892 #[cfg(feature = "capture-spantrace")]
893 let issue_section = issue_section.with_span_trace(report.span_trace.as_ref());
894
895 write!(&mut separated.ready(), "{}", issue_section)?;
896 }
897 }
898
899 Ok(())
900}
901
902impl fmt::Display for PanicReport<'_> {
903 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
904 print_panic_info(self, f)
905 }
906}
907
908pub struct PanicHook {
910 filters: Arc<[Box<FilterCallback>]>,
911 section: Option<Box<dyn Display + Send + Sync + 'static>>,
912 panic_message: Box<dyn PanicMessage>,
913 theme: Theme,
914 #[cfg(feature = "capture-spantrace")]
915 capture_span_trace_by_default: bool,
916 display_env_section: bool,
917 #[cfg(feature = "issue-url")]
918 issue_url: Option<String>,
919 #[cfg(feature = "issue-url")]
920 issue_metadata: Arc<Vec<(String, Box<dyn Display + Send + Sync + 'static>)>>,
921 #[cfg(feature = "issue-url")]
922 issue_filter: Arc<IssueFilterCallback>,
923}
924
925impl PanicHook {
926 pub(crate) fn format_backtrace<'a>(
927 &'a self,
928 trace: &'a backtrace::Backtrace,
929 ) -> BacktraceFormatter<'a> {
930 BacktraceFormatter {
931 filters: &self.filters,
932 inner: trace,
933 theme: self.theme,
934 }
935 }
936
937 #[cfg(feature = "capture-spantrace")]
938 fn spantrace_capture_enabled(&self) -> bool {
939 std::env::var("RUST_SPANTRACE")
940 .map(|val| val != "0")
941 .unwrap_or(self.capture_span_trace_by_default)
942 }
943
944 pub fn install(self) {
946 std::panic::set_hook(self.into_panic_hook());
947 }
948
949 pub fn into_panic_hook(
951 self,
952 ) -> Box<dyn Fn(&std::panic::PanicInfo<'_>) + Send + Sync + 'static> {
953 Box::new(move |panic_info| {
954 eprintln!("{}", self.panic_report(panic_info));
955 })
956 }
957
958 pub fn panic_report<'a>(
961 &'a self,
962 panic_info: &'a std::panic::PanicInfo<'_>,
963 ) -> PanicReport<'a> {
964 let v = panic_verbosity();
965 let capture_bt = v != Verbosity::Minimal;
966
967 #[cfg(feature = "capture-spantrace")]
968 let span_trace = if self.spantrace_capture_enabled() {
969 Some(tracing_error::SpanTrace::capture())
970 } else {
971 None
972 };
973
974 let backtrace = if capture_bt {
975 Some(backtrace::Backtrace::new())
976 } else {
977 None
978 };
979
980 PanicReport {
981 panic_info,
982 #[cfg(feature = "capture-spantrace")]
983 span_trace,
984 backtrace,
985 hook: self,
986 }
987 }
988}
989
990pub struct EyreHook {
992 filters: Arc<[Box<FilterCallback>]>,
993 #[cfg(feature = "capture-spantrace")]
994 capture_span_trace_by_default: bool,
995 display_env_section: bool,
996 #[cfg(feature = "track-caller")]
997 display_location_section: bool,
998 theme: Theme,
999 #[cfg(feature = "issue-url")]
1000 issue_url: Option<String>,
1001 #[cfg(feature = "issue-url")]
1002 issue_metadata: Arc<Vec<(String, Box<dyn Display + Send + Sync + 'static>)>>,
1003 #[cfg(feature = "issue-url")]
1004 issue_filter: Arc<IssueFilterCallback>,
1005}
1006
1007type HookFunc = Box<
1008 dyn Fn(&(dyn std::error::Error + 'static)) -> Box<dyn eyre::EyreHandler>
1009 + Send
1010 + Sync
1011 + 'static,
1012>;
1013
1014impl EyreHook {
1015 #[allow(unused_variables)]
1016 pub(crate) fn default(&self, error: &(dyn std::error::Error + 'static)) -> crate::Handler {
1017 let backtrace = if lib_verbosity() != Verbosity::Minimal {
1018 Some(backtrace::Backtrace::new())
1019 } else {
1020 None
1021 };
1022
1023 #[cfg(feature = "capture-spantrace")]
1024 let span_trace = if self.spantrace_capture_enabled()
1025 && crate::handler::get_deepest_spantrace(error).is_none()
1026 {
1027 Some(tracing_error::SpanTrace::capture())
1028 } else {
1029 None
1030 };
1031
1032 crate::Handler {
1033 filters: self.filters.clone(),
1034 backtrace,
1035 suppress_backtrace: false,
1036 #[cfg(feature = "capture-spantrace")]
1037 span_trace,
1038 sections: Vec::new(),
1039 display_env_section: self.display_env_section,
1040 #[cfg(feature = "track-caller")]
1041 display_location_section: self.display_location_section,
1042 #[cfg(feature = "issue-url")]
1043 issue_url: self.issue_url.clone(),
1044 #[cfg(feature = "issue-url")]
1045 issue_metadata: self.issue_metadata.clone(),
1046 #[cfg(feature = "issue-url")]
1047 issue_filter: self.issue_filter.clone(),
1048 theme: self.theme,
1049 #[cfg(feature = "track-caller")]
1050 location: None,
1051 }
1052 }
1053
1054 #[cfg(feature = "capture-spantrace")]
1055 fn spantrace_capture_enabled(&self) -> bool {
1056 std::env::var("RUST_SPANTRACE")
1057 .map(|val| val != "0")
1058 .unwrap_or(self.capture_span_trace_by_default)
1059 }
1060
1061 pub fn install(self) -> Result<(), crate::eyre::InstallError> {
1063 crate::eyre::set_hook(self.into_eyre_hook())
1064 }
1065
1066 pub fn into_eyre_hook(self) -> HookFunc {
1068 Box::new(move |e| Box::new(self.default(e)))
1069 }
1070}
1071
1072pub(crate) struct BacktraceFormatter<'a> {
1073 pub(crate) filters: &'a [Box<FilterCallback>],
1074 pub(crate) inner: &'a backtrace::Backtrace,
1075 pub(crate) theme: Theme,
1076}
1077
1078impl fmt::Display for BacktraceFormatter<'_> {
1079 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1080 write!(f, "{:━^80}", " BACKTRACE ")?;
1081
1082 let frames: Vec<_> = self
1084 .inner
1085 .frames()
1086 .iter()
1087 .flat_map(|frame| frame.symbols())
1088 .zip(1usize..)
1089 .map(|(sym, n)| Frame {
1090 name: sym.name().map(|x| x.to_string()),
1091 lineno: sym.lineno(),
1092 filename: sym.filename().map(|x| x.into()),
1093 n,
1094 })
1095 .collect();
1096
1097 let mut filtered_frames = frames.iter().collect();
1098 match env::var("COLORBT_SHOW_HIDDEN").ok().as_deref() {
1099 Some("1") | Some("on") | Some("y") => (),
1100 _ => {
1101 for filter in self.filters {
1102 filter(&mut filtered_frames);
1103 }
1104 }
1105 }
1106
1107 if filtered_frames.is_empty() {
1108 return write!(f, "\n<empty backtrace>");
1110 }
1111
1112 let mut separated = f.header("\n");
1113
1114 filtered_frames.sort_by_key(|x| x.n);
1116
1117 let mut buf = String::new();
1118
1119 macro_rules! print_hidden {
1120 ($n:expr) => {
1121 let n = $n;
1122 buf.clear();
1123 write!(
1124 &mut buf,
1125 "{decorator} {n} frame{plural} hidden {decorator}",
1126 n = n,
1127 plural = if n == 1 { "" } else { "s" },
1128 decorator = "⋮",
1129 )
1130 .expect("writing to strings doesn't panic");
1131 write!(
1132 &mut separated.ready(),
1133 "{:^80}",
1134 buf.style(self.theme.hidden_frames)
1135 )?;
1136 };
1137 }
1138
1139 let mut last_n = 0;
1140 for frame in &filtered_frames {
1141 let frame_delta = frame.n - last_n - 1;
1142 if frame_delta != 0 {
1143 print_hidden!(frame_delta);
1144 }
1145 write!(&mut separated.ready(), "{}", StyledFrame(frame, self.theme))?;
1146 last_n = frame.n;
1147 }
1148
1149 let last_filtered_n = filtered_frames.last().unwrap().n;
1150 let last_unfiltered_n = frames.last().unwrap().n;
1151 if last_filtered_n < last_unfiltered_n {
1152 print_hidden!(last_unfiltered_n - last_filtered_n);
1153 }
1154
1155 Ok(())
1156 }
1157}
1158
1159#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1160pub(crate) enum Verbosity {
1161 Minimal,
1162 Medium,
1163 Full,
1164}
1165
1166pub(crate) fn panic_verbosity() -> Verbosity {
1167 match env::var("RUST_BACKTRACE") {
1168 Ok(s) if s == "full" => Verbosity::Full,
1169 Ok(s) if s != "0" => Verbosity::Medium,
1170 _ => Verbosity::Minimal,
1171 }
1172}
1173
1174pub(crate) fn lib_verbosity() -> Verbosity {
1175 match env::var("RUST_LIB_BACKTRACE").or_else(|_| env::var("RUST_BACKTRACE")) {
1176 Ok(s) if s == "full" => Verbosity::Full,
1177 Ok(s) if s != "0" => Verbosity::Medium,
1178 _ => Verbosity::Minimal,
1179 }
1180}
1181
1182pub type FilterCallback = dyn Fn(&mut Vec<&Frame>) + Send + Sync + 'static;
1184
1185#[cfg(feature = "issue-url")]
1187#[cfg_attr(docsrs, doc(cfg(feature = "issue-url")))]
1188pub type IssueFilterCallback = dyn Fn(crate::ErrorKind<'_>) -> bool + Send + Sync + 'static;