aster/session/
diagnostics.rs1use 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}