Skip to main content

aster/session/
diagnostics.rs

1use crate::config::paths::Paths;
2use crate::providers::utils::LOGS_TO_KEEP;
3use crate::session::SessionManager;
4use std::fs::{self};
5use std::io::Cursor;
6use std::io::Write;
7use zip::write::FileOptions;
8use zip::ZipWriter;
9
10pub async fn generate_diagnostics(session_id: &str) -> anyhow::Result<Vec<u8>> {
11    let logs_dir = Paths::in_state_dir("logs");
12    let config_dir = Paths::config_dir();
13    let config_path = config_dir.join("config.yaml");
14    let data_dir = Paths::data_dir();
15
16    let system_info = format!(
17        "App Version: {}\n\
18     OS: {}\n\
19     OS Version: {}\n\
20     Architecture: {}\n\
21     Timestamp: {}\n",
22        env!("CARGO_PKG_VERSION"),
23        std::env::consts::OS,
24        sys_info::os_release().unwrap_or_else(|_| "unknown".to_string()),
25        std::env::consts::ARCH,
26        chrono::Utc::now().to_rfc3339()
27    );
28
29    let mut buffer = Vec::new();
30    {
31        let mut zip = ZipWriter::new(Cursor::new(&mut buffer));
32        let options = FileOptions::default().compression_method(zip::CompressionMethod::Deflated);
33
34        let mut log_files: Vec<_> = fs::read_dir(&logs_dir)?
35            .filter_map(|e| e.ok())
36            .filter(|e| e.path().extension().is_some_and(|ext| ext == "jsonl"))
37            .collect();
38
39        log_files.sort_by_key(|e| e.metadata().ok().and_then(|m| m.modified().ok()));
40
41        for entry in log_files.iter().rev().take(LOGS_TO_KEEP) {
42            let path = entry.path();
43            let name = path.file_name().unwrap().to_str().unwrap();
44            zip.start_file(format!("logs/{}", name), options)?;
45            zip.write_all(&fs::read(&path)?)?;
46        }
47
48        let session_data = SessionManager::export_session(session_id).await?;
49        zip.start_file("session.json", options)?;
50        zip.write_all(session_data.as_bytes())?;
51
52        if config_path.exists() {
53            zip.start_file("config.yaml", options)?;
54            zip.write_all(&fs::read(&config_path)?)?;
55        }
56
57        zip.start_file("system.txt", options)?;
58        zip.write_all(system_info.as_bytes())?;
59
60        let schedule_json = data_dir.join("schedule.json");
61        if schedule_json.exists() {
62            zip.start_file("schedule.json", options)?;
63            zip.write_all(&fs::read(&schedule_json)?)?;
64        }
65
66        let schedules_json = data_dir.join("schedules.json");
67        if schedules_json.exists() {
68            zip.start_file("schedules.json", options)?;
69            zip.write_all(&fs::read(&schedules_json)?)?;
70        }
71
72        let scheduled_recipes_dir = data_dir.join("scheduled_recipes");
73        if scheduled_recipes_dir.exists() && scheduled_recipes_dir.is_dir() {
74            for entry in fs::read_dir(&scheduled_recipes_dir)? {
75                let entry = entry?;
76                let path = entry.path();
77                if path.is_file() {
78                    let name = path.file_name().unwrap().to_str().unwrap();
79                    zip.start_file(format!("scheduled_recipes/{}", name), options)?;
80                    zip.write_all(&fs::read(&path)?)?;
81                }
82            }
83        }
84
85        zip.finish()?;
86    }
87
88    Ok(buffer)
89}