color_backtrace/
lib.rs

1//! Colorful and clean backtraces on panic.
2//!
3//! This library aims to make panics a little less painful by nicely colorizing
4//! them, skipping over frames of functions called after the panic was already
5//! initiated and printing relevant source snippets. Frames of functions in your
6//! application are colored in a different color (red) than those of
7//! dependencies (green).
8//!
9//! ### Screenshot
10//!
11//! ![Screenshot](https://i.imgur.com/yzp0KH6.png)
12//!
13//! ### Features
14//!
15//! - Colorize backtraces to be easier on the eyes
16//! - Show source snippets if source files are found on disk
17//! - Print frames of application code vs dependencies in different color
18//! - Hide all the frames after the panic was already initiated
19//! - Hide language runtime initialization frames
20//!
21//! ### Installing the panic handler
22//!
23//! In your main function, just insert the following snippet. That's it!
24//! ```rust
25//! color_backtrace::install();
26//! ```
27//!
28//! If you want to customize some settings, you can instead do:
29//! ```rust
30//! use color_backtrace::{default_output_stream, BacktracePrinter};
31//! BacktracePrinter::new().message("Custom message!").install(default_output_stream());
32//! ```
33//!
34//! ### Controlling verbosity
35//!
36//! The default verbosity is configured via the `RUST_BACKTRACE` environment
37//! variable. An unset `RUST_BACKTRACE` corresponds to
38//! [minimal](Verbosity::Minimal), `RUST_BACKTRACE=1` to
39//! [medium](Verbosity::Medium) and `RUST_BACKTRACE=full` to
40//! [full](Verbosity::Full) verbosity levels.
41
42use 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
51// Re-export termcolor so users don't have to depend on it themselves.
52pub use termcolor;
53
54// Re-export btparse as it has the deserialize function
55#[cfg(feature = "use-btparse-crate")]
56pub use btparse;
57
58// ============================================================================================== //
59// [Result / Error types]                                                                         //
60// ============================================================================================== //
61
62type IOResult<T = ()> = Result<T, std::io::Error>;
63
64// ============================================================================================== //
65// [Verbosity management]                                                                         //
66// ============================================================================================== //
67
68/// Defines how verbose the backtrace is supposed to be.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
70pub enum Verbosity {
71    /// Print a small message including the panic payload and the panic location.
72    Minimal,
73    /// Everything in `Minimal` and additionally print a backtrace.
74    Medium,
75    /// Everything in `Medium` plus source snippets for all backtrace locations.
76    Full,
77}
78
79impl Verbosity {
80    /// Get the verbosity level from `RUST_BACKTRACE` env variable.
81    pub fn from_env() -> Self {
82        Self::convert_env(env::var("RUST_BACKTRACE").ok())
83    }
84
85    /// Get the verbosity level from `RUST_LIB_BACKTRACE` env variable,
86    /// falling back to the `RUST_BACKTRACE`.
87    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
104// ============================================================================================== //
105// [Panic handler and install logic]                                                              //
106// ============================================================================================== //
107
108/// Install a `BacktracePrinter` handler with `::default()` settings.
109///
110/// This currently is a convenience shortcut for writing
111///
112/// ```rust
113/// use color_backtrace::{BacktracePrinter, default_output_stream};
114/// BacktracePrinter::default().install(default_output_stream())
115/// ```
116pub fn install() {
117    BacktracePrinter::default().install(default_output_stream());
118}
119
120/// Create the default output stream.
121///
122/// Color choice is determined by [`default_color_choice`].
123pub fn default_output_stream() -> Box<StandardStream> {
124    let color_choice = default_color_choice();
125    Box::new(StandardStream::stderr(color_choice))
126}
127
128/// Determine color choice based on environment variables and terminal detection.
129///
130/// Color choice is determined by the following priority:
131/// 1. If `NO_COLOR` environment variable is set (any value), colors are disabled
132/// 2. If `FORCE_COLOR` environment variable is set (any value), colors are enabled
133/// 3. If stderr is attached to a tty, colors are enabled
134/// 4. Otherwise, colors are disabled
135///
136/// This function follows the [`NO_COLOR`](https://no-color.org/) specification
137/// and common conventions for the `FORCE_COLOR` environment variable.
138pub 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            // Panicking while handling a panic would send us into a deadlock,
168            // so we just print the error to stderr instead.
169            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
180// ============================================================================================== //
181// [Backtrace abstraction]                                                                        //
182// ============================================================================================== //
183
184/// Abstraction over backtrace library implementations.
185pub 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
224// Capture a backtrace with the most reliable available backtrace implementation.
225fn 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
250// ============================================================================================== //
251// [Backtrace frame]                                                                              //
252// ============================================================================================== //
253
254pub 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    /// Heuristically determine whether the frame is likely to be part of a
268    /// dependency.
269    ///
270    /// If it fails to detect some patterns in your code base, feel free to drop
271    /// an issue / a pull request!
272    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        // Inspect name.
292        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        // Inspect filename.
302        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    /// Heuristically determine whether a frame is likely to be a post panic
310    /// frame.
311    ///
312    /// Post panic frames are frames of a functions called after the actual panic
313    /// is already in progress and don't contain any useful information for a
314    /// reader of the backtrace.
315    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    /// Heuristically determine whether a frame is likely to be part of language
335    /// runtime.
336    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        // For Linux, this is the best rule for skipping test init I found.
353        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            // Without a line number and file name, we can't sensibly proceed.
364            _ => 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        // Extract relevant lines.
374        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                // Print actual source line with brighter color.
380                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    /// Get the module's name by walking /proc/self/maps
392    #[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        // Print frame index.
463        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        // Does the function have a hash suffix?
476        // (dodging a dep on the regex crate here)
477        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        // Print function name.
485        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        // Print source location, if known.
510        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        // Maybe print source.
521        if s.current_verbosity() >= Verbosity::Full {
522            self.print_source_if_avail(out, s)?;
523        }
524
525        Ok(())
526    }
527}
528
529/// The default frame filter. Heuristically determines whether a frame is likely to be an
530/// uninteresting frame. This filters out post panic frames and runtime init frames and dependency
531/// code.
532pub 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) // indices are 1 based
537        .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// ============================================================================================== //
549// [BacktracePrinter]                                                                             //
550// ============================================================================================== //
551
552/// Color scheme definition.
553#[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    /// Helper to create a new `ColorSpec` & set a few properties in one wash.
570    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    /// The classic `color-backtrace` scheme, as shown in the screenshots.
579    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/// Pretty-printer for backtraces and [`PanicInfo`] structs.
607#[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
648/// Builder functions.
649impl BacktracePrinter {
650    /// Alias for `BacktracePrinter::default`.
651    pub fn new() -> Self {
652        Self::default()
653    }
654
655    /// Alter the color scheme.
656    ///
657    /// Defaults to `ColorScheme::classic()`.
658    pub fn color_scheme(mut self, colors: ColorScheme) -> Self {
659        self.colors = colors;
660        self
661    }
662
663    /// Controls the "greeting" message of the panic.
664    ///
665    /// Defaults to `"The application panicked (crashed)"`.
666    pub fn message(mut self, message: impl Into<String>) -> Self {
667        self.message = message.into();
668        self
669    }
670
671    /// Controls the verbosity level used when installed as panic handler.
672    ///
673    /// Defaults to `Verbosity::from_env()`.
674    pub fn verbosity(mut self, v: Verbosity) -> Self {
675        self.verbosity = v;
676        self
677    }
678
679    /// Controls the lib verbosity level used when formatting user provided traces.
680    ///
681    /// Defaults to `Verbosity::lib_from_env()`.
682    pub fn lib_verbosity(mut self, v: Verbosity) -> Self {
683        self.lib_verbosity = v;
684        self
685    }
686
687    /// Controls whether the hash part of functions is stripped.
688    ///
689    /// Defaults to `false`.
690    pub fn strip_function_hash(mut self, strip: bool) -> Self {
691        self.strip_function_hash = strip;
692        self
693    }
694
695    /// Controls whether addresses (or module offsets if available) should be printed.
696    ///
697    /// Defaults to `false`.
698    pub fn print_addresses(mut self, val: bool) -> Self {
699        self.should_print_addresses = val;
700        self
701    }
702
703    /// Add a custom filter to the set of frame filters
704    ///
705    /// Filters are run in the order they are added.
706    ///
707    /// # Example
708    ///
709    /// ```rust
710    /// use color_backtrace::{default_output_stream, BacktracePrinter};
711    ///
712    /// BacktracePrinter::new()
713    ///     .add_frame_filter(Box::new(|frames| {
714    ///         frames.retain(|x| matches!(&x.name, Some(n) if !n.starts_with("blabla")))
715    ///     }))
716    ///     .install(default_output_stream());
717    /// ```
718    pub fn add_frame_filter(mut self, filter: Box<FilterCallback>) -> Self {
719        self.filters.push(filter.into());
720        self
721    }
722
723    /// Clears all filters associated with this printer, including the default filter
724    pub fn clear_frame_filters(mut self) -> Self {
725        self.filters.clear();
726        self
727    }
728}
729
730/// Routines for putting the panic printer to use.
731impl BacktracePrinter {
732    /// Install the `color_backtrace` handler with default settings.
733    ///
734    /// Output streams can be created via `default_output_stream()` or
735    /// using any other stream that implements [`termcolor::WriteColor`].
736    pub fn install(self, out: impl WriteColor + Sync + Send + 'static) {
737        std::panic::set_hook(self.into_panic_handler(out))
738    }
739
740    /// Create a `color_backtrace` panic handler from this panic printer.
741    ///
742    /// This can be used if you want to combine the handler with other handlers.
743    #[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                // Panicking while handling a panic would send us into a deadlock,
754                // so we just print the error to stderr instead.
755                eprintln!("Error while printing panic: {:?}", e);
756            }
757        })
758    }
759
760    /// Pretty-prints a [`backtrace::Backtrace`] to an output stream.
761    pub fn print_trace(&self, trace: &dyn Backtrace, out: &mut impl WriteColor) -> IOResult {
762        writeln!(out, "{:━^80}", " BACKTRACE ")?;
763
764        // Collect frame info.
765        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            // TODO: Would probably look better centered.
779            return writeln!(out, "<empty backtrace>");
780        }
781
782        // Don't let filters mess with the order.
783        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    /// Pretty-print a backtrace to a `String`, using VT100 color codes.
820    pub fn format_trace_to_string(&self, trace: &dyn Backtrace) -> IOResult<String> {
821        // TODO: should we implicitly enable VT100 support on Windows here?
822        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    /// Pretty-prints a [`PanicInfo`] struct to an output stream.
828    #[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        // Print panic message.
835        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        // If known, print panic location.
848        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        // Print some info on how to increase verbosity.
862        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            // This text only makes sense if frames are displayed.
870            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// ============================================================================================== //
912// [Deprecated routines for backward compat]                                                      //
913// ============================================================================================== //
914
915#[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// ============================================================================================== //