lb_rs/service/
debug.rs

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};
12
13#[derive(Serialize)]
14pub struct DebugInfo {
15    pub time: String,
16    pub name: String,
17    pub last_synced: String,
18    pub lb_version: String,
19    pub rust_triple: String,
20    pub os_info: String,
21    pub lb_dir: String,
22    pub server_url: String,
23    pub integrity: String,
24    pub is_syncing: bool,
25    pub status: String,
26    pub panics: Vec<String>,
27}
28
29impl Lb {
30    async fn human_last_synced(&self) -> String {
31        let tx = self.ro_tx().await;
32        let db = tx.db();
33
34        let last_synced = *db.last_synced.get().unwrap_or(&0);
35
36        if last_synced != 0 {
37            Duration::milliseconds(clock::get_time().0 - last_synced)
38                .format_human()
39                .to_string()
40        } else {
41            "never".to_string()
42        }
43    }
44
45    fn now(&self) -> String {
46        let now = chrono::Local::now();
47        now.format("%Y-%m-%d %H:%M:%S %Z").to_string()
48    }
49
50    async fn collect_panics(&self) -> LbResult<Vec<String>> {
51        let mut panics = vec![];
52
53        let dir_path = &self.config.writeable_path;
54        let path = Path::new(dir_path);
55
56        let prefix = "panic---";
57        let suffix = ".log";
58        let timestamp_format = "%Y-%m-%d---%H-%M-%S";
59
60        let mut entries = fs::read_dir(path).await?;
61        while let Some(entry) = entries.next_entry().await? {
62            let file_name = entry.file_name().into_string().unwrap_or_default();
63
64            // Check if the filename matches the expected format
65            if file_name.starts_with(prefix) && file_name.ends_with(suffix) {
66                // Extract the timestamp portion from the filename
67                let timestamp_str = &file_name[prefix.len()..file_name.len() - suffix.len()];
68
69                // Parse the timestamp
70                if let Ok(timestamp) =
71                    NaiveDateTime::parse_from_str(timestamp_str, timestamp_format)
72                {
73                    let file_path = path.join(file_name);
74                    let contents = fs::read_to_string(file_path).await?;
75                    let contents = format!("time: {timestamp}: contents: {contents}");
76                    panics.push(contents);
77                }
78            }
79        }
80        Ok(panics)
81    }
82
83    #[instrument(level = "debug", skip(self), err(Debug))]
84    pub async fn debug_info(&self, os_info: String) -> LbResult<String> {
85        let account = self.get_account()?;
86
87        let arch = env::consts::ARCH;
88        let os = env::consts::OS;
89        let family = env::consts::FAMILY;
90
91        let (integrity, last_synced, panics) = tokio::join!(
92            self.test_repo_integrity(),
93            self.human_last_synced(),
94            self.collect_panics()
95        );
96
97        let mut status = self.status().await;
98        status.space_used = None;
99        let status = format!("{status:?}");
100        let is_syncing = self.syncing.load(Ordering::Relaxed);
101
102        Ok(serde_json::to_string_pretty(&DebugInfo {
103            time: self.now(),
104            name: account.username.clone(),
105            lb_version: get_code_version().into(),
106            rust_triple: format!("{arch}.{family}.{os}"),
107            server_url: account.api_url.clone(),
108            integrity: format!("{integrity:?}"),
109            lb_dir: self.config.writeable_path.clone(),
110            last_synced,
111            os_info,
112            status,
113            is_syncing,
114            panics: panics?,
115        })?)
116    }
117}