1use crate::model::clock;
2use crate::model::errors::LbResult;
3use crate::Lb;
4use crate::{get_code_version, service::logging::LOG_FILE};
5use basic_human_duration::ChronoHumanDuration;
6use chrono::NaiveDateTime;
7use serde::Serialize;
8use std::env;
9use std::io::SeekFrom;
10use std::path::{Path, PathBuf};
11use time::Duration;
12use tokio::fs::{self, File};
13use tokio::io::{AsyncReadExt, AsyncSeekExt};
14
15#[derive(Serialize)]
16pub struct DebugInfo {
17 pub time: String,
18 pub name: String,
19 pub last_synced: String,
20 pub lb_version: String,
21 pub rust_triple: String,
22 pub os_info: String,
23 pub lb_dir: String,
24 pub server_url: String,
25 pub integrity: String,
26 pub log_tail: String,
27 pub last_panic: String,
28}
29
30impl Lb {
31 async fn tail_log(&self) -> LbResult<String> {
32 let mut path = PathBuf::from(&self.config.writeable_path);
33 if path.exists() {
34 path.push(LOG_FILE);
35 let mut file = File::open(path).await?;
36 let size = file.metadata().await?.len();
37 let read_amount = 5 * 1024;
38 let pos = if read_amount > size { 0 } else { size - read_amount };
39
40 let mut buffer = Vec::with_capacity(read_amount as usize);
41 file.seek(SeekFrom::Start(pos)).await?;
42 file.read_to_end(&mut buffer).await?;
43 if self.config.colored_logs {
44 buffer = strip_ansi_escapes::strip(buffer);
46 }
47 Ok(String::from_utf8_lossy(&buffer).to_string())
48 } else {
49 Ok("NO LOGS FOUND".to_string())
50 }
51 }
52
53 async fn human_last_synced(&self) -> String {
54 let tx = self.ro_tx().await;
55 let db = tx.db();
56
57 let last_synced = *db.last_synced.get().unwrap_or(&0);
58
59 if last_synced != 0 {
60 Duration::milliseconds(clock::get_time().0 - last_synced)
61 .format_human()
62 .to_string()
63 } else {
64 "never".to_string()
65 }
66 }
67
68 fn now(&self) -> String {
69 let now = chrono::Local::now();
70 now.format("%Y-%m-%d %H:%M:%S %Z").to_string()
71 }
72
73 async fn find_most_recent_panic_log(&self) -> LbResult<String> {
74 let dir_path = &self.config.writeable_path;
75 let path = Path::new(dir_path);
76
77 let prefix = "panic---";
78 let suffix = ".log";
79 let timestamp_format = "%Y-%m-%d---%H-%M-%S";
80
81 let mut most_recent_file: Option<String> = None;
82 let mut most_recent_time: Option<NaiveDateTime> = None;
83
84 let mut entries = fs::read_dir(path).await?;
85 while let Some(entry) = entries.next_entry().await? {
86 let file_name = entry.file_name().into_string().unwrap_or_default();
87
88 if file_name.starts_with(prefix) && file_name.ends_with(suffix) {
90 let timestamp_str = &file_name[prefix.len()..file_name.len() - suffix.len()];
92
93 if let Ok(timestamp) =
95 NaiveDateTime::parse_from_str(timestamp_str, timestamp_format)
96 {
97 match most_recent_time {
99 Some(ref current_most_recent) => {
100 if timestamp > *current_most_recent {
101 most_recent_time = Some(timestamp);
102 most_recent_file = Some(file_name.clone());
103 }
104 }
105 None => {
106 most_recent_time = Some(timestamp);
107 most_recent_file = Some(file_name.clone());
108 }
109 }
110 }
111 }
112 }
113
114 if let Some(file_name) = most_recent_file {
116 let file_path = path.join(file_name);
117 let contents = fs::read_to_string(file_path).await?;
118 Ok(contents)
119 } else {
120 Ok(String::default())
121 }
122 }
123
124 #[instrument(level = "debug", skip(self), err(Debug))]
125 pub async fn debug_info(&self, os_info: String) -> LbResult<String> {
126 let account = self.get_account()?;
127
128 let arch = env::consts::ARCH;
129 let os = env::consts::OS;
130 let family = env::consts::FAMILY;
131
132 let (integrity, log_tail, last_synced, last_panic) = tokio::join!(
133 self.test_repo_integrity(),
134 self.tail_log(),
135 self.human_last_synced(),
136 self.find_most_recent_panic_log()
137 );
138
139 Ok(serde_json::to_string_pretty(&DebugInfo {
140 time: self.now(),
141 name: account.username.clone(),
142 lb_version: get_code_version().into(),
143 rust_triple: format!("{arch}.{family}.{os}"),
144 server_url: account.api_url.clone(),
145 integrity: format!("{:?}", integrity),
146 log_tail: log_tail?,
147 lb_dir: self.config.writeable_path.clone(),
148 last_synced,
149 os_info,
150 last_panic: last_panic?,
151 })?)
152 }
153}