1use std::{
5 backtrace::{Backtrace, BacktraceStatus},
6 panic::PanicInfo,
7};
8
9pub fn panic_hook(panic_info: &PanicInfo) {
61 let payload = panic_info.payload();
62
63 #[allow(clippy::manual_map)]
64 let payload = if let Some(s) = payload.downcast_ref::<&str>() {
65 Some(&**s)
66 } else if let Some(s) = payload.downcast_ref::<String>() {
67 Some(s.as_str())
68 } else {
69 None
70 };
71
72 let location = panic_info.location().map(|l| l.to_string());
73 let (backtrace, note) = if cfg!(feature = "capture-backtrace") {
74 let backtrace = Backtrace::capture();
75 let note = (backtrace.status() == BacktraceStatus::Disabled)
76 .then_some("run with RUST_BACKTRACE=1 environment variable to display a backtrace");
77 (Some(backtrace), note)
78 } else {
79 (None, None)
80 };
81
82 tracing::error!(
83 panic.payload = payload,
84 panic.location = location,
85 panic.backtrace = backtrace.map(tracing::field::display),
86 panic.note = note,
87 "A panic occurred",
88 );
89}
90
91#[cfg(test)]
92mod tests {
93 use tracing::subscriber::DefaultGuard;
94
95 use super::panic_hook;
96 use std::io;
97 use std::sync::{Arc, Mutex, MutexGuard, TryLockError};
98
99 #[test]
100 fn test_static_panic_message() {
101 let buffer = Arc::new(Mutex::new(vec![]));
102 let _guard = init_subscriber(buffer.clone());
103 let _ = std::panic::catch_unwind(|| {
104 std::panic::set_hook(Box::new(panic_hook));
105 panic!("This is a static panic message");
106 });
107
108 let logs = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
109 assert!(logs.contains("This is a static panic message"));
110 }
111
112 #[cfg(feature = "capture-backtrace")]
113 #[test]
114 fn panic_has_backtrace() {
115 let buffer = Arc::new(Mutex::new(vec![]));
116 let _guard = init_subscriber(buffer.clone());
117 let _ = std::panic::catch_unwind(|| {
118 std::panic::set_hook(Box::new(panic_hook));
119 panic!("This is a static panic message");
120 });
121
122 let logs = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
123 assert!(logs.contains("backtrace"));
124 }
125
126 #[cfg(not(feature = "capture-backtrace"))]
127 #[test]
128 fn panic_has_no_backtrace() {
129 let buffer = Arc::new(Mutex::new(vec![]));
130 let _guard = init_subscriber(buffer.clone());
131 let _ = std::panic::catch_unwind(|| {
132 std::panic::set_hook(Box::new(panic_hook));
133 panic!("This is a static panic message");
134 });
135
136 let logs = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
137 assert!(!logs.contains("backtrace"));
138 }
139
140 #[test]
141 fn test_interpolated_panic_message() {
142 let buffer = Arc::new(Mutex::new(vec![]));
143 let _guard = init_subscriber(buffer.clone());
144
145 let _ = std::panic::catch_unwind(|| {
146 std::panic::set_hook(Box::new(panic_hook));
147 panic!("This is an {} panic message", "interpolated");
148 });
149
150 let logs = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
151 assert!(logs.contains("This is an interpolated panic message"));
152 }
153
154 fn init_subscriber(buffer: Arc<Mutex<Vec<u8>>>) -> DefaultGuard {
155 let subscriber = tracing_subscriber::fmt()
156 .with_writer(move || MockWriter::new(buffer.clone()))
157 .finish();
158 tracing::subscriber::set_default(subscriber)
159 }
160
161 pub struct MockWriter {
164 buf: Arc<Mutex<Vec<u8>>>,
165 }
166
167 impl MockWriter {
168 pub fn new(buf: Arc<Mutex<Vec<u8>>>) -> Self {
169 Self { buf }
170 }
171
172 pub fn map_error<Guard>(err: TryLockError<Guard>) -> io::Error {
173 match err {
174 TryLockError::WouldBlock => io::Error::from(io::ErrorKind::WouldBlock),
175 TryLockError::Poisoned(_) => io::Error::from(io::ErrorKind::Other),
176 }
177 }
178
179 pub fn buf(&self) -> io::Result<MutexGuard<'_, Vec<u8>>> {
180 self.buf.try_lock().map_err(Self::map_error)
181 }
182 }
183
184 impl io::Write for MockWriter {
185 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
186 self.buf()?.write(buf)
187 }
188
189 fn flush(&mut self) -> io::Result<()> {
190 self.buf()?.flush()
191 }
192 }
193}