Skip to main content

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