reovim_kernel/panic/
report.rs1use std::{fmt::Write, panic::PanicHookInfo, path::PathBuf, time::SystemTime};
8
9fn format_timestamp(timestamp: SystemTime) -> (String, String) {
15 let duration = timestamp
16 .duration_since(std::time::UNIX_EPOCH)
17 .unwrap_or_default();
18
19 let secs = duration.as_secs();
20 let nanos = duration.subsec_nanos();
21
22 let days_since_epoch = secs / 86400;
25 let time_of_day = secs % 86400;
26
27 let hours = time_of_day / 3600;
28 let minutes = (time_of_day % 3600) / 60;
29 let seconds = time_of_day % 60;
30
31 let (year, month, day) = days_to_ymd(days_since_epoch);
33
34 let filename =
35 format!("{year:04}-{month:02}-{day:02}_{hours:02}-{minutes:02}-{seconds:02}-{nanos}");
36
37 let display = format!(
38 "{year:04}-{month:02}-{day:02} {hours:02}:{minutes:02}:{seconds:02} UTC +{nanos}ns"
39 );
40
41 (filename, display)
42}
43
44#[allow(
46 clippy::missing_const_for_fn,
47 clippy::cast_possible_wrap,
48 clippy::cast_sign_loss
49)]
50#[cfg_attr(coverage_nightly, coverage(off))]
51fn days_to_ymd(days: u64) -> (u64, u64, u64) {
52 let days = days as i64 + 719_468; let era = if days >= 0 { days } else { days - 146_096 } / 146_097;
56 let doe = (days - era * 146_097) as u64; let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; let y = yoe as i64 + era * 400;
59 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let m = if mp < 10 { mp + 3 } else { mp - 9 }; let y = if m <= 2 { y + 1 } else { y };
64
65 (y as u64, m, d)
66}
67
68#[derive(Debug)]
70pub struct CrashReport {
71 pub timestamp: std::time::SystemTime,
73 pub panic_message: String,
75 pub panic_location: Option<String>,
77 pub backtrace: String,
79 pub rust_version: &'static str,
81 pub reovim_version: &'static str,
83 pub thread_name: Option<String>,
85 pub thread_id: Option<u64>,
87 pub server_logs: Option<String>,
90 pub client_dump_paths: Vec<PathBuf>,
93}
94
95impl CrashReport {
96 #[must_use]
98 pub fn summary(&self) -> String {
99 format!(
100 "{} at {}",
101 self.panic_message,
102 self.panic_location.as_deref().unwrap_or("unknown location")
103 )
104 }
105
106 pub fn write_to_file(&self) -> std::io::Result<PathBuf> {
116 let dir = super::recovery::recovery_dir();
117 std::fs::create_dir_all(&dir)?;
118
119 let (filename_ts, display_ts) = format_timestamp(self.timestamp);
121 let filename = format!("crash-{filename_ts}.txt");
122 let path = dir.join(&filename);
123
124 let mut content = format!(
125 "Reovim Crash Report\n\
126==================\n\n\
127Timestamp: {}\n\
128Reovim Version: {}\n\
129Rust Version: {}\n\
130Thread: {} (id: {})\n\n\
131Panic Message:\n{}\n\n\
132Location:\n{}\n\n\
133Backtrace:\n{}\n",
134 display_ts,
135 self.reovim_version,
136 self.rust_version,
137 self.thread_name.as_deref().unwrap_or("unnamed"),
138 self.thread_id
139 .map_or_else(|| "unknown".to_string(), |id| id.to_string()),
140 self.panic_message,
141 self.panic_location.as_deref().unwrap_or("unknown"),
142 self.backtrace
143 );
144
145 if let Some(ref logs) = self.server_logs {
147 content.push_str("\nServer Debug Logs:\n");
148 content.push_str("------------------\n");
149 content.push_str(logs);
150 content.push('\n');
151 }
152
153 if !self.client_dump_paths.is_empty() {
155 content.push_str("\nClient Debug Dumps:\n");
156 content.push_str("-------------------\n");
157 for path in &self.client_dump_paths {
158 let _ = writeln!(content, " - {}", path.display());
159 }
160 }
161
162 std::fs::write(&path, content)?;
163 Ok(path)
164 }
165}
166
167#[must_use]
177pub fn generate_crash_report(info: &PanicHookInfo<'_>) -> CrashReport {
178 let panic_message = info
179 .payload()
180 .downcast_ref::<&str>()
181 .copied()
182 .map(ToString::to_string)
183 .or_else(|| info.payload().downcast_ref::<String>().cloned())
184 .unwrap_or_else(|| "Unknown panic".to_string());
185
186 let panic_location = info
187 .location()
188 .map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()));
189
190 let backtrace = std::backtrace::Backtrace::force_capture().to_string();
191
192 let current_thread = std::thread::current();
194 let thread_name = current_thread.name().map(ToString::to_string);
195 let thread_id_str = format!("{:?}", current_thread.id());
197 let thread_id = thread_id_str
199 .strip_prefix("ThreadId(")
200 .and_then(|s| s.strip_suffix(')'))
201 .and_then(|s| s.parse::<u64>().ok());
202
203 CrashReport {
204 timestamp: std::time::SystemTime::now(),
205 panic_message,
206 panic_location,
207 backtrace,
208 rust_version: option_env!("RUSTC_VERSION").unwrap_or("unknown"),
209 reovim_version: env!("CARGO_PKG_VERSION"),
210 thread_name,
211 thread_id,
212 server_logs: None,
214 client_dump_paths: Vec::new(),
215 }
216}