sos_debug_snapshot/
lib.rs1use futures::{pin_mut, StreamExt};
2use sos_archive::ZipWriter;
3use sos_client_storage::{
4 ClientBaseStorage, ClientFolderStorage, ClientStorage,
5};
6use sos_logs::LOG_FILE_NAME;
7use sos_sync::SyncStorage;
8use sos_vfs as vfs;
9use std::path::Path;
10
11mod error;
12pub use error::Error;
13
14#[derive(Debug)]
16pub struct DebugSnapshotOptions {
17 pub include_log_files: bool,
19 pub include_audit_trail: bool,
22}
23
24impl Default for DebugSnapshotOptions {
25 fn default() -> Self {
26 Self {
27 include_log_files: true,
28 include_audit_trail: false,
29 }
30 }
31}
32
33pub async fn export_debug_snapshot(
41 source: &ClientStorage,
42 file: impl AsRef<Path>,
43 options: DebugSnapshotOptions,
44) -> Result<(), Error> {
45 let zip_file = vfs::File::create(file.as_ref()).await?;
46 let mut zip_writer = ZipWriter::new(zip_file);
47
48 let account_id = *source.account_id();
49 let debug_tree = source.debug_account_tree(account_id).await?;
50
51 let buffer = serde_json::to_vec_pretty(&debug_tree)?;
52 zip_writer.add_file("account.json", &buffer).await?;
53
54 let login = source.read_login_vault().await?;
55 let buffer = serde_json::to_vec_pretty(login.summary())?;
56 zip_writer.add_file("login.json", &buffer).await?;
57
58 if let Some(device) = source.read_device_vault().await? {
59 let buffer = serde_json::to_vec_pretty(device.summary())?;
60 zip_writer.add_file("device.json", &buffer).await?;
61 }
62
63 let target = source.backend_target();
64 let paths = target.paths();
65
66 if options.include_log_files {
67 let logs = paths.logs_dir();
68 let mut dir = vfs::read_dir(logs).await?;
69 while let Some(entry) = dir.next_entry().await? {
70 let path = entry.path();
71 if let Some(name) = path.file_name() {
72 if name.to_string_lossy().starts_with(LOG_FILE_NAME) {
73 let buffer = vfs::read(&path).await?;
74 zip_writer
75 .add_file(
76 &format!("logs/{}.jsonl", name.to_string_lossy()),
77 &buffer,
78 )
79 .await?;
80 }
81 }
82 }
83 }
84
85 #[cfg(feature = "audit")]
86 if options.include_audit_trail {
87 if let Some(providers) = sos_backend::audit::providers() {
88 for (index, provider) in providers.iter().enumerate() {
89 let stream = provider.audit_stream(false).await?;
90 pin_mut!(stream);
91
92 let events = stream
93 .filter_map(|e| async move { e.ok() })
94 .filter_map(|e| async move {
95 if e.account_id() == &account_id {
96 Some(e)
97 } else {
98 None
99 }
100 })
101 .collect::<Vec<_>>()
102 .await;
103
104 let buffer = serde_json::to_vec_pretty(&events)?;
105 zip_writer
106 .add_file(&format!("audit/{}.json", index), &buffer)
107 .await?;
108 }
109 }
110 }
111
112 zip_writer.finish().await?;
113
114 Ok(())
115}