comfy_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//! ![Screenshot](https://i.imgur.com/jLznHxp.png)
11//!
12//! ### Features
13//! - Colorize backtraces to be easier on the eyes
14//! - Show source snippets if source files are found on disk
15//! - Print frames of application code vs dependencies in different color
16//! - Hide all the frames after the panic was already initiated
17//! - Hide language runtime initialization frames
18//!
19//! ### Installing the panic handler
20//!
21//! In your main function, just insert the following snippet. That's it!
22//! ```rust
23//! color_backtrace::install();
24//! ```
25//!
26//! If you want to customize some settings, you can instead do:
27//! ```rust
28//! use color_backtrace::{default_output_stream, BacktracePrinter};
29//! BacktracePrinter::new().message("Custom message!").install(default_output_stream());
30//! ```
31//!
32//! ### Controlling verbosity
33//! The default verbosity is configured via the `RUST_BACKTRACE` environment
34//! variable. An unset `RUST_BACKTRACE` corresponds to
35//! [minimal](Verbosity::Minimal), `RUST_BACKTRACE=1` to
36//! [medium](Verbosity::Medium) and `RUST_BACKTRACE=full` to
37//! [full](Verbosity::Full) verbosity levels.
38
39use std::env;
40use std::fs::File;
41use std::io::{BufRead, BufReader, ErrorKind};
42use std::panic::PanicInfo;
43use std::path::PathBuf;
44use std::sync::{Arc, Mutex};
45use termcolor::{Ansi, Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
46
47// Re-export termcolor so users don't have to depend on it themselves.
48pub use termcolor;
49
50// ============================================================================================== //
51// [Result / Error types]                                                                         //
52// ============================================================================================== //
53
54type IOResult<T = ()> = Result<T, std::io::Error>;
55
56// ============================================================================================== //
57// [Verbosity management]                                                                         //
58// ============================================================================================== //
59
60/// Defines how verbose the backtrace is supposed to be.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
62pub enum Verbosity {
63    /// Print a small message including the panic payload and the panic location.
64    Minimal,
65    /// Everything in `Minimal` and additionally print a backtrace.
66    Medium,
67    /// Everything in `Medium` plus source snippets for all backtrace locations.
68    Full,
69}
70
71impl Verbosity {
72    /// Get the verbosity level from `RUST_BACKTRACE` env variable.
73    pub fn from_env() -> Self {
74        Self::convert_env(env::var("RUST_BACKTRACE").ok())
75    }
76
77    /// Get the verbosity level from `RUST_LIB_BACKTRACE` env variable,
78    /// falling back to the `RUST_BACKTRACE`.
79    pub fn lib_from_env() -> Self {
80        Self::convert_env(
81            env::var("RUST_LIB_BACKTRACE")
82                .or_else(|_| env::var("RUST_BACKTRACE"))
83                .ok(),
84        )
85    }
86
87    fn convert_env(env: Option<String>) -> Self {
88        match env {
89            Some(ref x) if x == "full" => Verbosity::Full,
90            Some(_) => Verbosity::Medium,
91            None => Verbosity::Minimal,
92        }
93    }
94}
95
96// ============================================================================================== //
97// [Panic handler and install logic]                                                              //
98// ============================================================================================== //
99
100/// Install a `BacktracePrinter` handler with `::default()` settings.
101///
102/// This currently is a convenience shortcut for writing
103///
104/// ```rust
105/// use color_backtrace::{BacktracePrinter, default_output_stream};
106/// BacktracePrinter::default().install(default_output_stream())
107/// ```
108pub fn install() {
109    BacktracePrinter::default().install(default_output_stream());
110}
111
112/// Create the default output stream.
113///
114/// If stderr is attached to a tty, this is a colorized stderr, else it's
115/// a plain (colorless) stderr.
116pub fn default_output_stream() -> Box<StandardStream> {
117    Box::new(StandardStream::stderr(if atty::is(atty::Stream::Stderr) {
118        ColorChoice::Always
119    } else {
120        ColorChoice::Never
121    }))
122}
123
124#[deprecated(
125    since = "0.4.0",
126    note = "Use `BacktracePrinter::into_panic_handler()` instead."
127)]
128pub fn create_panic_handler(
129    printer: BacktracePrinter,
130) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
131    let out_stream_mutex = Mutex::new(default_output_stream());
132    Box::new(move |pi| {
133        let mut lock = out_stream_mutex.lock().unwrap();
134        if let Err(e) = printer.print_panic_info(pi, &mut *lock) {
135            // Panicking while handling a panic would send us into a deadlock,
136            // so we just print the error to stderr instead.
137            eprintln!("Error while printing panic: {:?}", e);
138        }
139    })
140}
141
142#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::install()` instead.")]
143pub fn install_with_settings(printer: BacktracePrinter) {
144    std::panic::set_hook(printer.into_panic_handler(default_output_stream()))
145}
146
147// ============================================================================================== //
148// [Backtrace frame]                                                                              //
149// ============================================================================================== //
150
151pub type FilterCallback = dyn Fn(&mut Vec<&Frame>) + Send + Sync + 'static;
152
153#[derive(Debug)]
154pub struct Frame {
155    pub n: usize,
156    pub name: Option<String>,
157    pub lineno: Option<u32>,
158    pub filename: Option<PathBuf>,
159    pub ip: usize,
160    _private_ctor: (),
161}
162
163impl Frame {
164    /// Heuristically determine whether the frame is likely to be part of a
165    /// dependency.
166    ///
167    /// If it fails to detect some patterns in your code base, feel free to drop
168    /// an issue / a pull request!
169    fn is_dependency_code(&self) -> bool {
170        const SYM_PREFIXES: &[&str] = &[
171            "std::",
172            "core::",
173            "backtrace::backtrace::",
174            "_rust_begin_unwind",
175            "color_traceback::",
176            "__rust_",
177            "___rust_",
178            "__pthread",
179            "_main",
180            "main",
181            "__scrt_common_main_seh",
182            "BaseThreadInitThunk",
183            "_start",
184            "__libc_start_main",
185            "start_thread",
186        ];
187
188        // Inspect name.
189        if let Some(ref name) = self.name {
190            if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
191                return true;
192            }
193        }
194
195        const FILE_PREFIXES: &[&str] = &[
196            "/rustc/",
197            "src/libstd/",
198            "src/libpanic_unwind/",
199            "src/libtest/",
200        ];
201
202        // Inspect filename.
203        if let Some(ref filename) = self.filename {
204            let filename = filename.to_string_lossy();
205            if FILE_PREFIXES.iter().any(|x| filename.starts_with(x))
206                || filename.contains("/.cargo/registry/src/")
207            {
208                return true;
209            }
210        }
211
212        false
213    }
214
215    /// Heuristically determine whether a frame is likely to be a post panic
216    /// frame.
217    ///
218    /// Post panic frames are frames of a functions called after the actual panic
219    /// is already in progress and don't contain any useful information for a
220    /// reader of the backtrace.
221    fn is_post_panic_code(&self) -> bool {
222        const SYM_PREFIXES: &[&str] = &[
223            "_rust_begin_unwind",
224            "rust_begin_unwind",
225            "core::result::unwrap_failed",
226            "core::option::expect_none_failed",
227            "core::panicking::panic_fmt",
228            "color_backtrace::create_panic_handler",
229            "std::panicking::begin_panic",
230            "begin_panic_fmt",
231            "backtrace::capture",
232        ];
233
234        match self.name.as_ref() {
235            Some(name) => SYM_PREFIXES.iter().any(|x| name.starts_with(x)),
236            None => false,
237        }
238    }
239
240    /// Heuristically determine whether a frame is likely to be part of language
241    /// runtime.
242    fn is_runtime_init_code(&self) -> bool {
243        const SYM_PREFIXES: &[&str] = &[
244            "std::rt::lang_start::",
245            "test::run_test::run_test_inner::",
246            "std::sys_common::backtrace::__rust_begin_short_backtrace",
247        ];
248
249        let (name, file) = match (self.name.as_ref(), self.filename.as_ref()) {
250            (Some(name), Some(filename)) => (name, filename.to_string_lossy()),
251            _ => return false,
252        };
253
254        if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
255            return true;
256        }
257
258        // For Linux, this is the best rule for skipping test init I found.
259        if name == "{{closure}}" && file == "src/libtest/lib.rs" {
260            return true;
261        }
262
263        false
264    }
265
266    fn print_source_if_avail(&self, mut out: impl WriteColor, s: &BacktracePrinter) -> IOResult {
267        let (lineno, filename) = match (self.lineno, self.filename.as_ref()) {
268            (Some(a), Some(b)) => (a, b),
269            // Without a line number and file name, we can't sensibly proceed.
270            _ => return Ok(()),
271        };
272
273        let file = match File::open(filename) {
274            Ok(file) => file,
275            Err(ref e) if e.kind() == ErrorKind::NotFound => return Ok(()),
276            e @ Err(_) => e?,
277        };
278
279        // Extract relevant lines.
280        let reader = BufReader::new(file);
281        let start_line = lineno - 2.min(lineno - 1);
282        let surrounding_src = reader.lines().skip(start_line as usize - 1).take(5);
283        for (line, cur_line_no) in surrounding_src.zip(start_line..) {
284            if cur_line_no == lineno {
285                // Print actual source line with brighter color.
286                out.set_color(&s.colors.selected_src_ln)?;
287                writeln!(out, "{:>8} > {}", cur_line_no, line?)?;
288                out.reset()?;
289            } else {
290                writeln!(out, "{:>8} │ {}", cur_line_no, line?)?;
291            }
292        }
293
294        Ok(())
295    }
296
297    /// Get the module's name by walking /proc/self/maps
298    #[cfg(all(
299        feature = "resolve-modules",
300        unix,
301        not(any(target_os = "macos", target_os = "ios"))
302    ))]
303    fn module_info(&self) -> Option<(String, usize)> {
304        use regex::Regex;
305        use std::path::Path;
306        let re = Regex::new(
307            r"(?x)
308                ^
309                (?P<start>[0-9a-f]{8,16})
310                -
311                (?P<end>[0-9a-f]{8,16})
312                \s
313                (?P<perm>[-rwxp]{4})
314                \s
315                (?P<offset>[0-9a-f]{8})
316                \s
317                [0-9a-f]+:[0-9a-f]+
318                \s
319                [0-9]+
320                \s+
321                (?P<path>.*)
322                $
323            ",
324        )
325        .unwrap();
326
327        let mapsfile = File::open("/proc/self/maps").expect("Unable to open /proc/self/maps");
328
329        for line in BufReader::new(mapsfile).lines() {
330            let line = line.unwrap();
331            if let Some(caps) = re.captures(&line) {
332                let (start, end, path) = (
333                    usize::from_str_radix(caps.name("start").unwrap().as_str(), 16).unwrap(),
334                    usize::from_str_radix(caps.name("end").unwrap().as_str(), 16).unwrap(),
335                    caps.name("path").unwrap().as_str().to_string(),
336                );
337                if self.ip >= start && self.ip < end {
338                    return if let Some(filename) = Path::new(&path).file_name() {
339                        Some((filename.to_str().unwrap().to_string(), start))
340                    } else {
341                        None
342                    };
343                }
344            }
345        }
346
347        None
348    }
349
350    #[cfg(not(all(
351        feature = "resolve-modules",
352        unix,
353        not(any(target_os = "macos", target_os = "ios"))
354    )))]
355    fn module_info(&self) -> Option<(String, usize)> {
356        None
357    }
358
359    fn print(&self, i: usize, out: &mut impl WriteColor, s: &BacktracePrinter) -> IOResult {
360        let is_dependency_code = self.is_dependency_code();
361
362        // Print frame index.
363        write!(out, "{:>2}: ", i)?;
364
365        if s.should_print_addresses() {
366            if let Some((module_name, module_base)) = self.module_info() {
367                write!(out, "{}:0x{:08x} - ", module_name, self.ip - module_base)?;
368            } else {
369                write!(out, "0x{:016x} - ", self.ip)?;
370            }
371        }
372
373        // Does the function have a hash suffix?
374        // (dodging a dep on the regex crate here)
375        let name = self
376            .name
377            .as_ref()
378            .map(|s| s.as_str())
379            .unwrap_or("<unknown>");
380        let has_hash_suffix = name.len() > 19
381            && &name[name.len() - 19..name.len() - 16] == "::h"
382            && name[name.len() - 16..].chars().all(|x| x.is_digit(16));
383
384        // Print function name.
385        out.set_color(if is_dependency_code {
386            &s.colors.dependency_code
387        } else {
388            &s.colors.crate_code
389        })?;
390
391        if has_hash_suffix {
392            write!(out, "{}", &name[..name.len() - 19])?;
393            if s.strip_function_hash {
394                writeln!(out)?;
395            } else {
396                out.set_color(if is_dependency_code {
397                    &s.colors.dependency_code_hash
398                } else {
399                    &s.colors.crate_code_hash
400                })?;
401                writeln!(out, "{}", &name[name.len() - 19..])?;
402            }
403        } else {
404            writeln!(out, "{}", name)?;
405        }
406
407        out.reset()?;
408
409        // Print source location, if known.
410        if let Some(ref file) = self.filename {
411            let filestr = file.to_str().unwrap_or("<bad utf8>");
412            let lineno = self
413                .lineno
414                .map_or("<unknown line>".to_owned(), |x| x.to_string());
415            writeln!(out, "    at {}:{}", filestr, lineno)?;
416        } else {
417            writeln!(out, "    at <unknown source file>")?;
418        }
419
420        // Maybe print source.
421        if s.current_verbosity() >= Verbosity::Full {
422            self.print_source_if_avail(out, s)?;
423        }
424
425        Ok(())
426    }
427}
428
429/// The default frame filter. Heuristically determines whether a frame is likely to be an
430/// uninteresting frame. This filters out post panic frames and runtime init frames and dependency
431/// code.
432pub fn default_frame_filter(frames: &mut Vec<&Frame>) {
433    let top_cutoff = frames
434        .iter()
435        .rposition(|x| x.is_post_panic_code())
436        .map(|x| x + 2) // indices are 1 based
437        .unwrap_or(0);
438
439    let bottom_cutoff = frames
440        .iter()
441        .position(|x| x.is_runtime_init_code())
442        .unwrap_or_else(|| frames.len());
443
444    let rng = top_cutoff..=bottom_cutoff;
445    frames.retain(|x| rng.contains(&x.n))
446}
447
448// ============================================================================================== //
449// [BacktracePrinter]                                                                             //
450// ============================================================================================== //
451
452/// Color scheme definition.
453#[derive(Debug, Clone)]
454pub struct ColorScheme {
455    pub frames_omitted_msg: ColorSpec,
456    pub header: ColorSpec,
457    pub msg_loc_prefix: ColorSpec,
458    pub src_loc: ColorSpec,
459    pub src_loc_separator: ColorSpec,
460    pub env_var: ColorSpec,
461    pub dependency_code: ColorSpec,
462    pub dependency_code_hash: ColorSpec,
463    pub crate_code: ColorSpec,
464    pub crate_code_hash: ColorSpec,
465    pub selected_src_ln: ColorSpec,
466}
467
468impl ColorScheme {
469    /// Helper to create a new `ColorSpec` & set a few properties in one wash.
470    fn cs(fg: Option<Color>, intense: bool, bold: bool) -> ColorSpec {
471        let mut cs = ColorSpec::new();
472        cs.set_fg(fg);
473        cs.set_bold(bold);
474        cs.set_intense(intense);
475        cs
476    }
477
478    /// The classic `color-backtrace` scheme, as shown in the screenshots.
479    pub fn classic() -> Self {
480        Self {
481            frames_omitted_msg: Self::cs(Some(Color::Cyan), true, false),
482            header: Self::cs(Some(Color::Red), false, false),
483            msg_loc_prefix: Self::cs(Some(Color::Cyan), false, false),
484            src_loc: Self::cs(Some(Color::Magenta), false, false),
485            src_loc_separator: Self::cs(Some(Color::White), false, false),
486            env_var: Self::cs(None, false, true),
487            dependency_code: Self::cs(Some(Color::Green), false, false),
488            dependency_code_hash: Self::cs(Some(Color::Black), true, false),
489            crate_code: Self::cs(Some(Color::Red), true, false),
490            crate_code_hash: Self::cs(Some(Color::Black), true, false),
491            selected_src_ln: Self::cs(None, false, true),
492        }
493    }
494}
495
496impl Default for ColorScheme {
497    fn default() -> Self {
498        Self::classic()
499    }
500}
501
502#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter` instead.")]
503pub type Settings = BacktracePrinter;
504
505/// Pretty-printer for backtraces and [`PanicInfo`](PanicInfo) structs.
506#[derive(Clone)]
507pub struct BacktracePrinter {
508    message: String,
509    verbosity: Verbosity,
510    lib_verbosity: Verbosity,
511    strip_function_hash: bool,
512    is_panic_handler: bool,
513    colors: ColorScheme,
514    filters: Vec<Arc<FilterCallback>>,
515    should_print_addresses: bool,
516}
517
518impl Default for BacktracePrinter {
519    fn default() -> Self {
520        Self {
521            verbosity: Verbosity::from_env(),
522            lib_verbosity: Verbosity::lib_from_env(),
523            message: "The application panicked (crashed).".to_owned(),
524            strip_function_hash: false,
525            colors: ColorScheme::classic(),
526            is_panic_handler: false,
527            filters: vec![Arc::new(default_frame_filter)],
528            should_print_addresses: false,
529        }
530    }
531}
532
533impl std::fmt::Debug for BacktracePrinter {
534    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
535        fmt.debug_struct("Settings")
536            .field("message", &self.message)
537            .field("verbosity", &self.verbosity)
538            .field("lib_verbosity", &self.lib_verbosity)
539            .field("strip_function_hash", &self.strip_function_hash)
540            .field("is_panic_handler", &self.is_panic_handler)
541            .field("print_addresses", &self.should_print_addresses)
542            .field("colors", &self.colors)
543            .finish()
544    }
545}
546
547/// Builder functions.
548impl BacktracePrinter {
549    /// Alias for `BacktracePrinter::default`.
550    pub fn new() -> Self {
551        Self::default()
552    }
553
554    /// Alter the color scheme.
555    ///
556    /// Defaults to `ColorScheme::classic()`.
557    pub fn color_scheme(mut self, colors: ColorScheme) -> Self {
558        self.colors = colors;
559        self
560    }
561
562    /// Controls the "greeting" message of the panic.
563    ///
564    /// Defaults to `"The application panicked (crashed)"`.
565    pub fn message(mut self, message: impl Into<String>) -> Self {
566        self.message = message.into();
567        self
568    }
569
570    /// Controls the verbosity level used when installed as panic handler.
571    ///
572    /// Defaults to `Verbosity::from_env()`.
573    pub fn verbosity(mut self, v: Verbosity) -> Self {
574        self.verbosity = v;
575        self
576    }
577
578    /// Controls the lib verbosity level used when formatting user provided traces.
579    ///
580    /// Defaults to `Verbosity::lib_from_env()`.
581    pub fn lib_verbosity(mut self, v: Verbosity) -> Self {
582        self.lib_verbosity = v;
583        self
584    }
585
586    /// Controls whether the hash part of functions is stripped.
587    ///
588    /// Defaults to `false`.
589    pub fn strip_function_hash(mut self, strip: bool) -> Self {
590        self.strip_function_hash = strip;
591        self
592    }
593
594    /// Controls whether addresses (or module offsets if available) should be printed.
595    ///
596    /// Defaults to `false`.
597    pub fn print_addresses(mut self, val: bool) -> Self {
598        self.should_print_addresses = val;
599        self
600    }
601
602    /// Add a custom filter to the set of frame filters
603    ///
604    /// Filters are run in the order they are added.
605    ///
606    /// # Example
607    ///
608    /// ```rust
609    /// use color_backtrace::{default_output_stream, BacktracePrinter};
610    ///
611    /// BacktracePrinter::new()
612    ///     .add_frame_filter(Box::new(|frames| {
613    ///         frames.retain(|x| matches!(&x.name, Some(n) if !n.starts_with("blabla")))
614    ///     }))
615    ///     .install(default_output_stream());
616    /// ```
617    pub fn add_frame_filter(mut self, filter: Box<FilterCallback>) -> Self {
618        self.filters.push(filter.into());
619        self
620    }
621
622    /// Clears all filters associated with this printer, including the default filter
623    pub fn clear_frame_filters(mut self) -> Self {
624        self.filters.clear();
625        self
626    }
627}
628
629/// Routines for putting the panic printer to use.
630impl BacktracePrinter {
631    /// Install the `color_backtrace` handler with default settings.
632    ///
633    /// Output streams can be created via `default_output_stream()` or
634    /// using any other stream that implements
635    /// [`termcolor::WriteColor`](termcolor::WriteColor).
636    pub fn install(self, out: impl WriteColor + Sync + Send + 'static) {
637        std::panic::set_hook(self.into_panic_handler(out))
638    }
639
640    /// Create a `color_backtrace` panic handler from this panic printer.
641    ///
642    /// This can be used if you want to combine the handler with other handlers.
643    pub fn into_panic_handler(
644        mut self,
645        out: impl WriteColor + Sync + Send + 'static,
646    ) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
647        self.is_panic_handler = true;
648        let out_stream_mutex = Mutex::new(out);
649        Box::new(move |pi| {
650            let mut lock = out_stream_mutex.lock().unwrap();
651            if let Err(e) = self.print_panic_info(pi, &mut *lock) {
652                // Panicking while handling a panic would send us into a deadlock,
653                // so we just print the error to stderr instead.
654                eprintln!("Error while printing panic: {:?}", e);
655            }
656        })
657    }
658
659    /// Pretty-prints a [`backtrace::Backtrace`](backtrace::Backtrace) to an output stream.
660    pub fn print_trace(&self, trace: &backtrace::Backtrace, out: &mut impl WriteColor) -> IOResult {
661        writeln!(out, "{:━^80}", " BACKTRACE ")?;
662
663        // Collect frame info.
664        let frames: Vec<_> = trace
665            .frames()
666            .iter()
667            .flat_map(|frame| frame.symbols().iter().map(move |sym| (frame.ip(), sym)))
668            .zip(1usize..)
669            .map(|((ip, sym), n)| Frame {
670                name: sym.name().map(|x| x.to_string()),
671                lineno: sym.lineno(),
672                filename: sym.filename().map(|x| x.into()),
673                n,
674                ip: ip as usize,
675                _private_ctor: (),
676            })
677            .collect();
678
679        let mut filtered_frames = frames.iter().collect();
680        match env::var("COLORBT_SHOW_HIDDEN").ok().as_deref() {
681            Some("1") | Some("on") | Some("y") => (),
682            _ => {
683                for filter in &self.filters {
684                    filter(&mut filtered_frames);
685                }
686            }
687        }
688
689        if filtered_frames.is_empty() {
690            // TODO: Would probably look better centered.
691            return writeln!(out, "<empty backtrace>");
692        }
693
694        // Don't let filters mess with the order.
695        filtered_frames.sort_by_key(|x| x.n);
696
697        macro_rules! print_hidden {
698            ($n:expr) => {
699                out.set_color(&self.colors.frames_omitted_msg)?;
700                let n = $n;
701                let text = format!(
702                    "{decorator} {n} frame{plural} hidden {decorator}",
703                    n = n,
704                    plural = if n == 1 { "" } else { "s" },
705                    decorator = "⋮",
706                );
707                writeln!(out, "{:^80}", text)?;
708                out.reset()?;
709            };
710        }
711
712        let mut last_n = 0;
713        for frame in &filtered_frames {
714            let frame_delta = frame.n - last_n - 1;
715            if frame_delta != 0 {
716                print_hidden!(frame_delta);
717            }
718            if !frame.is_dependency_code() {
719                frame.print(frame.n, out, self)?;
720            }
721            last_n = frame.n;
722        }
723
724        let last_filtered_n = filtered_frames.last().unwrap().n;
725        let last_unfiltered_n = frames.last().unwrap().n;
726        if last_filtered_n < last_unfiltered_n {
727            print_hidden!(last_unfiltered_n - last_filtered_n);
728        }
729
730        Ok(())
731    }
732
733    /// Pretty-print a backtrace to a `String`, using VT100 color codes.
734    pub fn format_trace_to_string(&self, trace: &backtrace::Backtrace) -> IOResult<String> {
735        // TODO: should we implicitly enable VT100 support on Windows here?
736        let mut ansi = Ansi::new(vec![]);
737        self.print_trace(trace, &mut ansi)?;
738        Ok(String::from_utf8(ansi.into_inner()).unwrap())
739    }
740
741    /// Pretty-prints a [`PanicInfo`](PanicInfo) struct to an output stream.
742    pub fn print_panic_info(&self, pi: &PanicInfo, out: &mut impl WriteColor) -> IOResult {
743        out.set_color(&self.colors.header)?;
744        writeln!(out, "{}", self.message)?;
745        out.reset()?;
746
747        // Print panic message.
748        let payload = pi
749            .payload()
750            .downcast_ref::<String>()
751            .map(String::as_str)
752            .or_else(|| pi.payload().downcast_ref::<&str>().cloned())
753            .unwrap_or("<non string panic payload>");
754
755        write!(out, "Message:  ")?;
756        out.set_color(&self.colors.msg_loc_prefix)?;
757        writeln!(out, "{}", payload)?;
758        out.reset()?;
759
760        // If known, print panic location.
761        write!(out, "Location: ")?;
762        if let Some(loc) = pi.location() {
763            out.set_color(&self.colors.src_loc)?;
764            write!(out, "{}", loc.file())?;
765            out.set_color(&self.colors.src_loc_separator)?;
766            write!(out, ":")?;
767            out.set_color(&self.colors.src_loc)?;
768            writeln!(out, "{}", loc.line())?;
769            out.reset()?;
770        } else {
771            writeln!(out, "<unknown>")?;
772        }
773
774        // Print some info on how to increase verbosity.
775        if self.current_verbosity() == Verbosity::Minimal {
776            write!(out, "\nBacktrace omitted.\n\nRun with ")?;
777            out.set_color(&self.colors.env_var)?;
778            write!(out, "RUST_BACKTRACE=1")?;
779            out.reset()?;
780            writeln!(out, " environment variable to display it.")?;
781        } else {
782            // This text only makes sense if frames are displayed.
783            write!(out, "\nRun with ")?;
784            out.set_color(&self.colors.env_var)?;
785            write!(out, "COLORBT_SHOW_HIDDEN=1")?;
786            out.reset()?;
787            writeln!(out, " environment variable to disable frame filtering.")?;
788        }
789        if self.current_verbosity() <= Verbosity::Medium {
790            write!(out, "Run with ")?;
791            out.set_color(&self.colors.env_var)?;
792            write!(out, "RUST_BACKTRACE=full")?;
793            out.reset()?;
794            writeln!(out, " to include source snippets.")?;
795        }
796
797        if self.current_verbosity() >= Verbosity::Medium {
798            self.print_trace(&backtrace::Backtrace::new(), out)?;
799        }
800
801        Ok(())
802    }
803
804    fn current_verbosity(&self) -> Verbosity {
805        if self.is_panic_handler {
806            self.verbosity
807        } else {
808            self.lib_verbosity
809        }
810    }
811
812    fn should_print_addresses(&self) -> bool {
813        self.should_print_addresses
814    }
815}
816
817// ============================================================================================== //
818// [Deprecated routines for backward compat]                                                      //
819// ============================================================================================== //
820
821#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::print_trace` instead`")]
822pub fn print_backtrace(trace: &backtrace::Backtrace, s: &mut BacktracePrinter) -> IOResult {
823    s.print_trace(trace, &mut default_output_stream())
824}
825
826#[deprecated(
827    since = "0.4.0",
828    note = "Use `BacktracePrinter::print_panic_info` instead`"
829)]
830pub fn print_panic_info(pi: &PanicInfo, s: &mut BacktracePrinter) -> IOResult {
831    s.print_panic_info(pi, &mut default_output_stream())
832}
833
834// ============================================================================================== //