1use 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
51pub use termcolor;
53
54#[cfg(feature = "use-btparse-crate")]
56pub use btparse;
57
58type IOResult<T = ()> = Result<T, std::io::Error>;
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
70pub enum Verbosity {
71 Minimal,
73 Medium,
75 Full,
77}
78
79impl Verbosity {
80 pub fn from_env() -> Self {
82 Self::convert_env(env::var("RUST_BACKTRACE").ok())
83 }
84
85 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
104pub fn install() {
117 BacktracePrinter::default().install(default_output_stream());
118}
119
120pub fn default_output_stream() -> Box<StandardStream> {
124 let color_choice = default_color_choice();
125 Box::new(StandardStream::stderr(color_choice))
126}
127
128pub 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 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
180pub 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
224fn 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
250pub 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 pub fn is_dependency_code(&self) -> bool {
275 default_is_dependency_frame(self)
276 }
277
278 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 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 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 _ => 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 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 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 #[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 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 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 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 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 if s.current_verbosity() >= Verbosity::Full {
491 self.print_source_if_avail(out, s)?;
492 }
493
494 Ok(())
495 }
496}
497
498pub 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) .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
517pub 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 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 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#[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 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 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#[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
660impl BacktracePrinter {
662 pub fn new() -> Self {
664 Self::default()
665 }
666
667 pub fn color_scheme(mut self, colors: ColorScheme) -> Self {
671 self.colors = colors;
672 self
673 }
674
675 pub fn message(mut self, message: impl Into<String>) -> Self {
679 self.message = message.into();
680 self
681 }
682
683 pub fn verbosity(mut self, v: Verbosity) -> Self {
687 self.verbosity = v;
688 self
689 }
690
691 pub fn lib_verbosity(mut self, v: Verbosity) -> Self {
695 self.lib_verbosity = v;
696 self
697 }
698
699 pub fn strip_function_hash(mut self, strip: bool) -> Self {
703 self.strip_function_hash = strip;
704 self
705 }
706
707 pub fn print_addresses(mut self, val: bool) -> Self {
711 self.should_print_addresses = val;
712 self
713 }
714
715 pub fn add_frame_filter(mut self, filter: Box<FilterCallback>) -> Self {
731 self.filters.push(filter.into());
732 self
733 }
734
735 pub fn clear_frame_filters(mut self) -> Self {
737 self.filters.clear();
738 self
739 }
740
741 pub fn dependency_predicate(mut self, is_dependency: Box<IsDependencyCallback>) -> Self {
743 self.is_dependency = is_dependency.into();
744 self
745 }
746}
747
748impl BacktracePrinter {
750 pub fn install(self, out: impl WriteColor + Sync + Send + 'static) {
755 std::panic::set_hook(self.into_panic_handler(out))
756 }
757
758 #[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 eprintln!("Error while printing panic: {:?}", e);
774 }
775 })
776 }
777
778 pub fn print_trace(&self, trace: &dyn Backtrace, out: &mut impl WriteColor) -> IOResult {
780 writeln!(out, "{:━^80}", " BACKTRACE ")?;
781
782 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 return writeln!(out, "<empty backtrace>");
798 }
799
800 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 pub fn format_trace_to_string(&self, trace: &dyn Backtrace) -> IOResult<String> {
839 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 #[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 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 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 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 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#[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