1use crate::model::clock;
2use crate::model::errors::LbResult;
3use crate::{Lb, get_code_version};
4use basic_human_duration::ChronoHumanDuration;
5use chrono::NaiveDateTime;
6use serde::Serialize;
7use std::env;
8use std::path::Path;
9use std::sync::atomic::Ordering;
10use time::Duration;
11use tokio::fs::{self, OpenOptions};
12use tokio::io::AsyncWriteExt;
13
14#[derive(Serialize)]
15pub struct DebugInfo {
16 pub time: String,
17 pub name: String,
18 pub last_synced: String,
19 pub lb_version: String,
20 pub rust_triple: String,
21 pub os_info: String,
22 pub lb_dir: String,
23 pub server_url: String,
24 pub integrity: String,
25 pub is_syncing: bool,
26 pub status: String,
27 pub panics: Vec<String>,
28}
29
30impl Lb {
31 async fn human_last_synced(&self) -> String {
32 let tx = self.ro_tx().await;
33 let db = tx.db();
34
35 let last_synced = *db.last_synced.get().unwrap_or(&0);
36
37 if last_synced != 0 {
38 Duration::milliseconds(clock::get_time().0 - last_synced)
39 .format_human()
40 .to_string()
41 } else {
42 "never".to_string()
43 }
44 }
45
46 fn now(&self) -> String {
47 let now = chrono::Local::now();
48 now.format("%Y-%m-%d %H:%M:%S %Z").to_string()
49 }
50
51 async fn collect_panics(&self) -> LbResult<Vec<String>> {
52 let mut panics = vec![];
53
54 let dir_path = &self.config.writeable_path;
55 let path = Path::new(dir_path);
56
57 let prefix = "panic---";
58 let suffix = ".log";
59 let timestamp_format = "%Y-%m-%d---%H-%M-%S";
60
61 let mut entries = fs::read_dir(path).await?;
62 while let Some(entry) = entries.next_entry().await? {
63 let file_name = entry.file_name().into_string().unwrap_or_default();
64
65 if file_name.starts_with(prefix) && file_name.ends_with(suffix) {
67 let timestamp_str = &file_name[prefix.len()..file_name.len() - suffix.len()];
69
70 if let Ok(timestamp) =
72 NaiveDateTime::parse_from_str(timestamp_str, timestamp_format)
73 {
74 let file_path = path.join(file_name);
75 let contents = fs::read_to_string(file_path).await?;
76 let contents = format!("time: {timestamp}: contents: {contents}");
77 panics.push(contents);
78 }
79 }
80 }
81 panics.reverse();
82
83 Ok(panics)
84 }
85
86 #[instrument(level = "debug", skip(self), err(Debug))]
87 pub async fn write_panic_to_file(&self, error_header: String, bt: String) -> LbResult<String> {
88 let file_name = generate_panic_filename(&self.config.writeable_path);
89 let content = generate_panic_content(&error_header, &bt);
90
91 let mut file = OpenOptions::new()
92 .create(true)
93 .append(true)
94 .open(&file_name)
95 .await?;
96
97 file.write_all(content.as_bytes()).await?;
98
99 Ok(file_name)
100 }
101
102 #[instrument(level = "debug", skip(self), err(Debug))]
103 pub async fn debug_info(&self, os_info: String) -> LbResult<String> {
104 let account = self.get_account()?;
105
106 let arch = env::consts::ARCH;
107 let os = env::consts::OS;
108 let family = env::consts::FAMILY;
109
110 let (integrity, last_synced, panics) = tokio::join!(
111 self.test_repo_integrity(),
112 self.human_last_synced(),
113 self.collect_panics()
114 );
115
116 let mut status = self.status().await;
117 status.space_used = None;
118 let status = format!("{status:?}");
119 let is_syncing = self.syncing.load(Ordering::Relaxed);
120
121 Ok(serde_json::to_string_pretty(&DebugInfo {
122 time: self.now(),
123 name: account.username.clone(),
124 lb_version: get_code_version().into(),
125 rust_triple: format!("{arch}.{family}.{os}"),
126 server_url: account.api_url.clone(),
127 integrity: format!("{integrity:?}"),
128 lb_dir: self.config.writeable_path.clone(),
129 last_synced,
130 os_info,
131 status,
132 is_syncing,
133 panics: panics?,
134 })?)
135 }
136}
137
138pub fn generate_panic_filename(path: &str) -> String {
139 let timestamp = chrono::Local::now().format("%Y-%m-%d---%H-%M-%S");
140 format!("{path}/panic---{timestamp}.log")
141}
142
143pub fn generate_panic_content(panic_info: &str, bt: &str) -> String {
144 format!("INFO: {panic_info}\nBT: {bt}")
145}