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, 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            // Check if the filename matches the expected format
66            if file_name.starts_with(prefix) && file_name.ends_with(suffix) {
67                // Extract the timestamp portion from the filename
68                let timestamp_str = &file_name[prefix.len()..file_name.len() - suffix.len()];
69
70                // Parse the timestamp
71                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}