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
269 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 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 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 _ => 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 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 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 #[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 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 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 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 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 if s.current_verbosity() >= Verbosity::Full {
482 self.print_source_if_avail(out, s)?;
483 }
484
485 Ok(())
486 }
487}
488
489pub 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) .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
509pub 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 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 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#[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 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 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#[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
653impl BacktracePrinter {
655 pub fn new() -> Self {
657 Self::default()
658 }
659
660 pub fn color_scheme(mut self, colors: ColorScheme) -> Self {
664 self.colors = colors;
665 self
666 }
667
668 pub fn message(mut self, message: impl Into<String>) -> Self {
672 self.message = message.into();
673 self
674 }
675
676 pub fn verbosity(mut self, v: Verbosity) -> Self {
680 self.verbosity = v;
681 self
682 }
683
684 pub fn lib_verbosity(mut self, v: Verbosity) -> Self {
688 self.lib_verbosity = v;
689 self
690 }
691
692 pub fn strip_function_hash(mut self, strip: bool) -> Self {
696 self.strip_function_hash = strip;
697 self
698 }
699
700 pub fn print_addresses(mut self, val: bool) -> Self {
704 self.should_print_addresses = val;
705 self
706 }
707
708 pub fn add_frame_filter(mut self, filter: Box<FilterCallback>) -> Self {
724 self.filters.push(filter.into());
725 self
726 }
727
728 pub fn clear_frame_filters(mut self) -> Self {
730 self.filters.clear();
731 self
732 }
733
734 pub fn dependency_predicate(mut self, is_dependency: Box<IsDependencyCallback>) -> Self {
736 self.is_dependency = is_dependency.into();
737 self
738 }
739}
740
741impl BacktracePrinter {
743 pub fn install(self, out: impl WriteColor + Sync + Send + 'static) {
748 std::panic::set_hook(self.into_panic_handler(out))
749 }
750
751 #[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 eprintln!("Error while printing panic: {:?}", e);
767 }
768 })
769 }
770
771 pub fn print_trace(&self, trace: &dyn Backtrace, out: &mut impl WriteColor) -> IOResult {
773 writeln!(out, "{:━^80}", " BACKTRACE ")?;
774
775 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 return writeln!(out, "<empty backtrace>");
791 }
792
793 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 pub fn format_trace_to_string(&self, trace: &dyn Backtrace) -> IOResult<String> {
832 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 #[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 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 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 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 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#[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