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/",
300            "src/libstd/",
301            "src/libpanic_unwind/",
302            "src/libtest/",
303        ];
304
305        // Inspect filename.
306        if let Some(ref filename) = self.filename {
307            let filename = filename.to_string_lossy();
308            if FILE_PREFIXES.iter().any(|x| filename.starts_with(x))
309                || filename.contains("/.cargo/registry/src/")
310            {
311                return true;
312            }
313        }
314
315        false
316    }
317
318    /// Heuristically determine whether a frame is likely to be a post panic
319    /// frame.
320    ///
321    /// Post panic frames are frames of a functions called after the actual panic
322    /// is already in progress and don't contain any useful information for a
323    /// reader of the backtrace.
324    pub fn is_post_panic_code(&self) -> bool {
325        const SYM_PREFIXES: &[&str] = &[
326            "_rust_begin_unwind",
327            "rust_begin_unwind",
328            "core::result::unwrap_failed",
329            "core::option::expect_none_failed",
330            "core::panicking::panic_fmt",
331            "color_backtrace::create_panic_handler",
332            "std::panicking::begin_panic",
333            "begin_panic_fmt",
334            "backtrace::capture",
335        ];
336
337        match self.name.as_ref() {
338            Some(name) => SYM_PREFIXES.iter().any(|x| name.starts_with(x)),
339            None => false,
340        }
341    }
342
343    /// Heuristically determine whether a frame is likely to be part of language
344    /// runtime.
345    pub fn is_runtime_init_code(&self) -> bool {
346        const SYM_PREFIXES: &[&str] = &[
347            "std::rt::lang_start::",
348            "test::run_test::run_test_inner::",
349            "std::sys_common::backtrace::__rust_begin_short_backtrace",
350        ];
351
352        let (name, file) = match (self.name.as_ref(), self.filename.as_ref()) {
353            (Some(name), Some(filename)) => (name, filename.to_string_lossy()),
354            _ => return false,
355        };
356
357        if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
358            return true;
359        }
360
361        // For Linux, this is the best rule for skipping test init I found.
362        if name == "{{closure}}" && file == "src/libtest/lib.rs" {
363            return true;
364        }
365
366        false
367    }
368
369    fn print_source_if_avail(&self, mut out: impl WriteColor, s: &BacktracePrinter) -> IOResult {
370        let (lineno, filename) = match (self.lineno, self.filename.as_ref()) {
371            (Some(a), Some(b)) => (a, b),
372            // Without a line number and file name, we can't sensibly proceed.
373            _ => return Ok(()),
374        };
375
376        let file = match File::open(filename) {
377            Ok(file) => file,
378            Err(ref e) if e.kind() == ErrorKind::NotFound => return Ok(()),
379            e @ Err(_) => e?,
380        };
381
382        // Extract relevant lines.
383        let reader = BufReader::new(file);
384        let start_line = lineno - 2.min(lineno - 1);
385        let surrounding_src = reader.lines().skip(start_line as usize - 1).take(5);
386        for (line, cur_line_no) in surrounding_src.zip(start_line..) {
387            if cur_line_no == lineno {
388                // Print actual source line with brighter color.
389                out.set_color(&s.colors.selected_src_ln)?;
390                writeln!(out, "{:>8} > {}", cur_line_no, line?)?;
391                out.reset()?;
392            } else {
393                writeln!(out, "{:>8} │ {}", cur_line_no, line?)?;
394            }
395        }
396
397        Ok(())
398    }
399
400    /// Get the module's name by walking /proc/self/maps
401    #[cfg(all(
402        feature = "resolve-modules",
403        unix,
404        not(any(target_os = "macos", target_os = "ios"))
405    ))]
406    fn module_info(&self) -> Option<(String, usize)> {
407        use regex::Regex;
408        use std::path::Path;
409
410        let ip = match self.ip {
411            Some(x) => x,
412            None => return None,
413        };
414
415        let re = Regex::new(
416            r"(?x)
417                ^
418                (?P<start>[0-9a-f]{8,16})
419                -
420                (?P<end>[0-9a-f]{8,16})
421                \s
422                (?P<perm>[-rwxp]{4})
423                \s
424                (?P<offset>[0-9a-f]{8})
425                \s
426                [0-9a-f]+:[0-9a-f]+
427                \s
428                [0-9]+
429                \s+
430                (?P<path>.*)
431                $
432            ",
433        )
434        .unwrap();
435
436        let mapsfile = File::open("/proc/self/maps").expect("Unable to open /proc/self/maps");
437
438        for line in BufReader::new(mapsfile).lines() {
439            let line = line.unwrap();
440            if let Some(caps) = re.captures(&line) {
441                let (start, end, path) = (
442                    usize::from_str_radix(caps.name("start").unwrap().as_str(), 16).unwrap(),
443                    usize::from_str_radix(caps.name("end").unwrap().as_str(), 16).unwrap(),
444                    caps.name("path").unwrap().as_str().to_string(),
445                );
446                if ip >= start && ip < end {
447                    return if let Some(filename) = Path::new(&path).file_name() {
448                        Some((filename.to_str().unwrap().to_string(), start))
449                    } else {
450                        None
451                    };
452                }
453            }
454        }
455
456        None
457    }
458
459    #[cfg(not(all(
460        feature = "resolve-modules",
461        unix,
462        not(any(target_os = "macos", target_os = "ios"))
463    )))]
464    fn module_info(&self) -> Option<(String, usize)> {
465        None
466    }
467
468    fn print(&self, i: usize, out: &mut impl WriteColor, s: &BacktracePrinter) -> IOResult {
469        let is_dependency_code = self.is_dependency_code();
470
471        // Print frame index.
472        write!(out, "{:>2}: ", i)?;
473
474        if let Some(ip) = self.ip {
475            if s.should_print_addresses() {
476                if let Some((module_name, module_base)) = self.module_info() {
477                    write!(out, "{}:0x{:08x} - ", module_name, ip - module_base)?;
478                } else {
479                    write!(out, "0x{:016x} - ", ip)?;
480                }
481            }
482        }
483
484        // Does the function have a hash suffix?
485        // (dodging a dep on the regex crate here)
486        let name = self.name.as_deref().unwrap_or("<unknown>");
487        let has_hash_suffix = name.len() > 19
488            && &name[name.len() - 19..name.len() - 16] == "::h"
489            && name[name.len() - 16..]
490                .chars()
491                .all(|x| x.is_ascii_hexdigit());
492
493        // Print function name.
494        out.set_color(if is_dependency_code {
495            &s.colors.dependency_code
496        } else {
497            &s.colors.crate_code
498        })?;
499
500        if has_hash_suffix {
501            write!(out, "{}", &name[..name.len() - 19])?;
502            if s.strip_function_hash {
503                writeln!(out)?;
504            } else {
505                out.set_color(if is_dependency_code {
506                    &s.colors.dependency_code_hash
507                } else {
508                    &s.colors.crate_code_hash
509                })?;
510                writeln!(out, "{}", &name[name.len() - 19..])?;
511            }
512        } else {
513            writeln!(out, "{}", name)?;
514        }
515
516        out.reset()?;
517
518        // Print source location, if known.
519        if let Some(ref file) = self.filename {
520            let filestr = file.to_str().unwrap_or("<bad utf8>");
521            let lineno = self
522                .lineno
523                .map_or("<unknown line>".to_owned(), |x| x.to_string());
524            writeln!(out, "    at {}:{}", filestr, lineno)?;
525        } else {
526            writeln!(out, "    at <unknown source file>")?;
527        }
528
529        // Maybe print source.
530        if s.current_verbosity() >= Verbosity::Full {
531            self.print_source_if_avail(out, s)?;
532        }
533
534        Ok(())
535    }
536}
537
538/// The default frame filter. Heuristically determines whether a frame is likely to be an
539/// uninteresting frame. This filters out post panic frames and runtime init frames and dependency
540/// code.
541pub fn default_frame_filter(frames: &mut Vec<&Frame>) {
542    let top_cutoff = frames
543        .iter()
544        .rposition(|x| x.is_post_panic_code())
545        .map(|x| x + 2) // indices are 1 based
546        .unwrap_or(0);
547
548    let bottom_cutoff = frames
549        .iter()
550        .position(|x| x.is_runtime_init_code())
551        .unwrap_or(frames.len());
552
553    let rng = top_cutoff..=bottom_cutoff;
554    frames.retain(|x| rng.contains(&x.n))
555}
556
557// ============================================================================================== //
558// [BacktracePrinter]                                                                             //
559// ============================================================================================== //
560
561/// Color scheme definition.
562#[derive(Debug, Clone)]
563pub struct ColorScheme {
564    pub frames_omitted_msg: ColorSpec,
565    pub header: ColorSpec,
566    pub msg_loc_prefix: ColorSpec,
567    pub src_loc: ColorSpec,
568    pub src_loc_separator: ColorSpec,
569    pub env_var: ColorSpec,
570    pub dependency_code: ColorSpec,
571    pub dependency_code_hash: ColorSpec,
572    pub crate_code: ColorSpec,
573    pub crate_code_hash: ColorSpec,
574    pub selected_src_ln: ColorSpec,
575}
576
577impl ColorScheme {
578    /// Helper to create a new `ColorSpec` & set a few properties in one wash.
579    fn cs(fg: Option<Color>, intense: bool, bold: bool) -> ColorSpec {
580        let mut cs = ColorSpec::new();
581        cs.set_fg(fg);
582        cs.set_bold(bold);
583        cs.set_intense(intense);
584        cs
585    }
586
587    /// The classic `color-backtrace` scheme, as shown in the screenshots.
588    pub fn classic() -> Self {
589        Self {
590            frames_omitted_msg: Self::cs(Some(Color::Cyan), true, false),
591            header: Self::cs(Some(Color::Red), false, false),
592            msg_loc_prefix: Self::cs(Some(Color::Cyan), false, false),
593            src_loc: Self::cs(Some(Color::Magenta), false, false),
594            src_loc_separator: Self::cs(Some(Color::White), false, false),
595            env_var: Self::cs(None, false, true),
596            dependency_code: Self::cs(Some(Color::Green), false, false),
597            dependency_code_hash: Self::cs(Some(Color::Black), true, false),
598            crate_code: Self::cs(Some(Color::Red), true, false),
599            crate_code_hash: Self::cs(Some(Color::Black), true, false),
600            selected_src_ln: Self::cs(None, false, true),
601        }
602    }
603}
604
605impl Default for ColorScheme {
606    fn default() -> Self {
607        Self::classic()
608    }
609}
610
611#[doc(hidden)]
612#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter` instead.")]
613pub type Settings = BacktracePrinter;
614
615/// Pretty-printer for backtraces and [`PanicInfo`] structs.
616#[derive(Clone)]
617pub struct BacktracePrinter {
618    message: String,
619    verbosity: Verbosity,
620    lib_verbosity: Verbosity,
621    strip_function_hash: bool,
622    is_panic_handler: bool,
623    colors: ColorScheme,
624    filters: Vec<Arc<FilterCallback>>,
625    should_print_addresses: bool,
626}
627
628impl Default for BacktracePrinter {
629    fn default() -> Self {
630        Self {
631            verbosity: Verbosity::from_env(),
632            lib_verbosity: Verbosity::lib_from_env(),
633            message: "The application panicked (crashed).".to_owned(),
634            strip_function_hash: false,
635            colors: ColorScheme::classic(),
636            is_panic_handler: false,
637            filters: vec![Arc::new(default_frame_filter)],
638            should_print_addresses: false,
639        }
640    }
641}
642
643impl std::fmt::Debug for BacktracePrinter {
644    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
645        fmt.debug_struct("Settings")
646            .field("message", &self.message)
647            .field("verbosity", &self.verbosity)
648            .field("lib_verbosity", &self.lib_verbosity)
649            .field("strip_function_hash", &self.strip_function_hash)
650            .field("is_panic_handler", &self.is_panic_handler)
651            .field("print_addresses", &self.should_print_addresses)
652            .field("colors", &self.colors)
653            .finish()
654    }
655}
656
657/// Builder functions.
658impl BacktracePrinter {
659    /// Alias for `BacktracePrinter::default`.
660    pub fn new() -> Self {
661        Self::default()
662    }
663
664    /// Alter the color scheme.
665    ///
666    /// Defaults to `ColorScheme::classic()`.
667    pub fn color_scheme(mut self, colors: ColorScheme) -> Self {
668        self.colors = colors;
669        self
670    }
671
672    /// Controls the "greeting" message of the panic.
673    ///
674    /// Defaults to `"The application panicked (crashed)"`.
675    pub fn message(mut self, message: impl Into<String>) -> Self {
676        self.message = message.into();
677        self
678    }
679
680    /// Controls the verbosity level used when installed as panic handler.
681    ///
682    /// Defaults to `Verbosity::from_env()`.
683    pub fn verbosity(mut self, v: Verbosity) -> Self {
684        self.verbosity = v;
685        self
686    }
687
688    /// Controls the lib verbosity level used when formatting user provided traces.
689    ///
690    /// Defaults to `Verbosity::lib_from_env()`.
691    pub fn lib_verbosity(mut self, v: Verbosity) -> Self {
692        self.lib_verbosity = v;
693        self
694    }
695
696    /// Controls whether the hash part of functions is stripped.
697    ///
698    /// Defaults to `false`.
699    pub fn strip_function_hash(mut self, strip: bool) -> Self {
700        self.strip_function_hash = strip;
701        self
702    }
703
704    /// Controls whether addresses (or module offsets if available) should be printed.
705    ///
706    /// Defaults to `false`.
707    pub fn print_addresses(mut self, val: bool) -> Self {
708        self.should_print_addresses = val;
709        self
710    }
711
712    /// Add a custom filter to the set of frame filters
713    ///
714    /// Filters are run in the order they are added.
715    ///
716    /// # Example
717    ///
718    /// ```rust
719    /// use color_backtrace::{default_output_stream, BacktracePrinter};
720    ///
721    /// BacktracePrinter::new()
722    ///     .add_frame_filter(Box::new(|frames| {
723    ///         frames.retain(|x| matches!(&x.name, Some(n) if !n.starts_with("blabla")))
724    ///     }))
725    ///     .install(default_output_stream());
726    /// ```
727    pub fn add_frame_filter(mut self, filter: Box<FilterCallback>) -> Self {
728        self.filters.push(filter.into());
729        self
730    }
731
732    /// Clears all filters associated with this printer, including the default filter
733    pub fn clear_frame_filters(mut self) -> Self {
734        self.filters.clear();
735        self
736    }
737}
738
739/// Routines for putting the panic printer to use.
740impl BacktracePrinter {
741    /// Install the `color_backtrace` handler with default settings.
742    ///
743    /// Output streams can be created via `default_output_stream()` or
744    /// using any other stream that implements [`termcolor::WriteColor`].
745    pub fn install(self, out: impl WriteColor + Sync + Send + 'static) {
746        std::panic::set_hook(self.into_panic_handler(out))
747    }
748
749    /// Create a `color_backtrace` panic handler from this panic printer.
750    ///
751    /// This can be used if you want to combine the handler with other handlers.
752    #[allow(deprecated, reason = "keep support for older rust versions")]
753    pub fn into_panic_handler(
754        mut self,
755        out: impl WriteColor + Sync + Send + 'static,
756    ) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
757        self.is_panic_handler = true;
758        let out_stream_mutex = Mutex::new(out);
759        Box::new(move |pi| {
760            let mut lock = out_stream_mutex.lock().unwrap();
761            if let Err(e) = self.print_panic_info(pi, &mut *lock) {
762                // Panicking while handling a panic would send us into a deadlock,
763                // so we just print the error to stderr instead.
764                eprintln!("Error while printing panic: {:?}", e);
765            }
766        })
767    }
768
769    /// Pretty-prints a [`backtrace::Backtrace`] to an output stream.
770    pub fn print_trace(&self, trace: &dyn Backtrace, out: &mut impl WriteColor) -> IOResult {
771        writeln!(out, "{:━^80}", " BACKTRACE ")?;
772
773        // Collect frame info.
774        let frames = trace.frames();
775
776        let mut filtered_frames = frames.iter().collect();
777        match env::var("COLORBT_SHOW_HIDDEN").ok().as_deref() {
778            Some("1") | Some("on") | Some("y") => (),
779            _ => {
780                for filter in &self.filters {
781                    filter(&mut filtered_frames);
782                }
783            }
784        }
785
786        if filtered_frames.is_empty() {
787            // TODO: Would probably look better centered.
788            return writeln!(out, "<empty backtrace>");
789        }
790
791        // Don't let filters mess with the order.
792        filtered_frames.sort_by_key(|x| x.n);
793
794        macro_rules! print_hidden {
795            ($n:expr) => {
796                out.set_color(&self.colors.frames_omitted_msg)?;
797                let n = $n;
798                let text = format!(
799                    "{decorator} {n} frame{plural} hidden {decorator}",
800                    n = n,
801                    plural = if n == 1 { "" } else { "s" },
802                    decorator = "⋮",
803                );
804                writeln!(out, "{:^80}", text)?;
805                out.reset()?;
806            };
807        }
808
809        let mut last_n = 0;
810        for frame in &filtered_frames {
811            let frame_delta = frame.n - last_n - 1;
812            if frame_delta != 0 {
813                print_hidden!(frame_delta);
814            }
815            frame.print(frame.n, out, self)?;
816            last_n = frame.n;
817        }
818
819        let last_filtered_n = filtered_frames.last().unwrap().n;
820        let last_unfiltered_n = frames.last().unwrap().n;
821        if last_filtered_n < last_unfiltered_n {
822            print_hidden!(last_unfiltered_n - last_filtered_n);
823        }
824
825        Ok(())
826    }
827
828    /// Pretty-print a backtrace to a `String`, using VT100 color codes.
829    pub fn format_trace_to_string(&self, trace: &dyn Backtrace) -> IOResult<String> {
830        // TODO: should we implicitly enable VT100 support on Windows here?
831        let mut ansi = Ansi::new(vec![]);
832        self.print_trace(trace, &mut ansi)?;
833        Ok(String::from_utf8(ansi.into_inner()).unwrap())
834    }
835
836    /// Pretty-prints a [`PanicInfo`] struct to an output stream.
837    #[allow(deprecated, reason = "keep support for older rust versions")]
838    pub fn print_panic_info(&self, pi: &PanicInfo, out: &mut impl WriteColor) -> IOResult {
839        out.set_color(&self.colors.header)?;
840        writeln!(out, "{}", self.message)?;
841        out.reset()?;
842
843        // Print panic message.
844        let payload = pi
845            .payload()
846            .downcast_ref::<String>()
847            .map(String::as_str)
848            .or_else(|| pi.payload().downcast_ref::<&str>().cloned())
849            .unwrap_or("<non string panic payload>");
850
851        write!(out, "Message:  ")?;
852        out.set_color(&self.colors.msg_loc_prefix)?;
853        writeln!(out, "{}", payload)?;
854        out.reset()?;
855
856        // If known, print panic location.
857        write!(out, "Location: ")?;
858        if let Some(loc) = pi.location() {
859            out.set_color(&self.colors.src_loc)?;
860            write!(out, "{}", loc.file())?;
861            out.set_color(&self.colors.src_loc_separator)?;
862            write!(out, ":")?;
863            out.set_color(&self.colors.src_loc)?;
864            writeln!(out, "{}", loc.line())?;
865            out.reset()?;
866        } else {
867            writeln!(out, "<unknown>")?;
868        }
869
870        // Print some info on how to increase verbosity.
871        if self.current_verbosity() == Verbosity::Minimal {
872            write!(out, "\nBacktrace omitted.\n\nRun with ")?;
873            out.set_color(&self.colors.env_var)?;
874            write!(out, "RUST_BACKTRACE=1")?;
875            out.reset()?;
876            writeln!(out, " environment variable to display it.")?;
877        } else {
878            // This text only makes sense if frames are displayed.
879            write!(out, "\nRun with ")?;
880            out.set_color(&self.colors.env_var)?;
881            write!(out, "COLORBT_SHOW_HIDDEN=1")?;
882            out.reset()?;
883            writeln!(out, " environment variable to disable frame filtering.")?;
884        }
885        if self.current_verbosity() <= Verbosity::Medium {
886            write!(out, "Run with ")?;
887            out.set_color(&self.colors.env_var)?;
888            write!(out, "RUST_BACKTRACE=full")?;
889            out.reset()?;
890            writeln!(out, " to include source snippets.")?;
891        }
892
893        if self.current_verbosity() >= Verbosity::Medium {
894            match capture_backtrace() {
895                Ok(trace) => self.print_trace(&*trace, out)?,
896                Err(e) => {
897                    out.set_color(&self.colors.header)?;
898                    writeln!(out, "\nFailed to capture backtrace: {e}")?;
899                    out.reset()?;
900                }
901            }
902        }
903
904        Ok(())
905    }
906
907    fn current_verbosity(&self) -> Verbosity {
908        if self.is_panic_handler {
909            self.verbosity
910        } else {
911            self.lib_verbosity
912        }
913    }
914
915    fn should_print_addresses(&self) -> bool {
916        self.should_print_addresses
917    }
918}
919
920// ============================================================================================== //
921// [Deprecated routines for backward compat]                                                      //
922// ============================================================================================== //
923
924#[doc(hidden)]
925#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::print_trace` instead`")]
926#[cfg(feature = "use-backtrace-crate")]
927pub fn print_backtrace(trace: &backtrace::Backtrace, s: &mut BacktracePrinter) -> IOResult {
928    s.print_trace(trace, &mut default_output_stream())
929}
930
931#[doc(hidden)]
932#[deprecated(
933    since = "0.4.0",
934    note = "Use `BacktracePrinter::print_panic_info` instead`"
935)]
936#[allow(deprecated, reason = "keep support for older rust versions")]
937pub fn print_panic_info(pi: &PanicInfo, s: &mut BacktracePrinter) -> IOResult {
938    s.print_panic_info(pi, &mut default_output_stream())
939}
940
941// ============================================================================================== //