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