From 6c2854b588079442ef1138f211cf3959151ce10d Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:12:00 -0700 Subject: [PATCH] Improve log viewer --- unshell-gui/src/log_viewer/mod.rs | 185 ++++++++++++++++++++++++++++-- unshell-lib/Cargo.lock | 152 +++++++++++++++++++++++- unshell-lib/Cargo.toml | 2 +- unshell-lib/src/logger/mod.rs | 4 +- unshell-server/Cargo.lock | 2 + unshell-server/Cargo.toml | 2 +- unshell-server/src/lib.rs | 1 + unshell-server/src/logger.rs | 9 +- unshell-server/src/main.rs | 4 +- unshell-server/src/modules/mod.rs | 0 10 files changed, 333 insertions(+), 28 deletions(-) create mode 100644 unshell-server/src/modules/mod.rs diff --git a/unshell-gui/src/log_viewer/mod.rs b/unshell-gui/src/log_viewer/mod.rs index 8b264dc..6ada1d2 100644 --- a/unshell-gui/src/log_viewer/mod.rs +++ b/unshell-gui/src/log_viewer/mod.rs @@ -1,42 +1,205 @@ +use chrono::DateTime; +use chrono::Utc; +use egui::Color32; +use egui::TextStyle; +use egui_extras::Column; +use egui_extras::TableBuilder; + use crate::auth::Auth; use std::sync::Arc; use std::sync::Mutex; +use std::time::SystemTime; #[derive(serde::Deserialize, serde::Serialize)] pub struct LogViewer { + enable_location: bool, #[serde(skip)] state: Arc>, } #[derive(Default)] struct LogState { - logs: Vec, + logs: Vec, // trees: Option>, // tree_keys: Option>, - // is_requesting: bool, + is_requesting: bool, + requested_data: bool, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub enum LogLevel { + Debug, + Info, + Warn, + Error, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct Record { + log_level: LogLevel, + location: Option, + time: SystemTime, + message: String, +} + +impl Record { + pub fn display_level(&self, ui: &mut egui::Ui) { + match self.log_level { + LogLevel::Debug => ui.colored_label(Color32::LIGHT_BLUE, "DBUG"), + LogLevel::Info => ui.colored_label(Color32::DARK_GREEN, "INFO"), + LogLevel::Warn => ui.colored_label(Color32::YELLOW, "WARN"), + LogLevel::Error => ui.colored_label(Color32::RED, "ERR!"), + }; + } + pub fn display_location(&self, ui: &mut egui::Ui) { + if let Some(ref location) = self.location { + ui.label(location); + } + } + pub fn display_time(&self, ui: &mut egui::Ui) { + let date: DateTime = self.time.into(); + let date = date.to_rfc2822().to_string(); + ui.label(date); + } + pub fn display_message(&self, ui: &mut egui::Ui) { + ui.strong(&self.message); + } } impl LogViewer { pub fn update(&mut self, auth: &mut Auth, ui: &mut egui::Ui) { ui.heading("Log Viewer"); - for log in &self.state.lock().unwrap().logs { - ui.label(log); - } - if ui.button("Poll").clicked() { - let state_clone = self.state.clone(); - auth.get(&format!("/api/log/{}", 0), move |e: Vec| { - (*state_clone.lock().unwrap()).logs = e; - // crate::log(&format!("{e:?}")); + + ui.horizontal(|ui| { + if ui.button("Refresh").clicked() { + self.refresh_logs(auth); + } + + ui.checkbox(&mut self.enable_location, "Enable Location"); + }); + + // let logs = ; + + let body_text_size = TextStyle::Body.resolve(ui.style()).size; + use egui_extras::{Size, StripBuilder}; + StripBuilder::new(ui) + .size(Size::remainder().at_least(100.0)) // for the table + .size(Size::exact(body_text_size)) + .vertical(|mut strip| { + strip.cell(|ui| { + egui::ScrollArea::both() + .stick_to_bottom(true) + .show(ui, |ui| { + let table = TableBuilder::new(ui) + .striped(true) + .resizable(true) + .stick_to_bottom(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::auto()) + .column(Column::auto()) + .column(Column::auto()) + .min_scrolled_height(0.0) + .sense(egui::Sense::click()); + + let table = if self.enable_location { + table.column(Column::auto()) + } else { + table + }; + + table + .header(20., |mut header| { + header.col(|ui| { + ui.strong("Time"); + }); + header.col(|ui| { + ui.strong("Level"); + }); + header.col(|ui| { + ui.strong("Message"); + }); + if self.enable_location { + header.col(|ui| { + ui.strong("Location"); + }); + } + }) + .body(|mut body| { + let state_lock = self.state.lock().unwrap(); + // let logs = state_lock.logs.as_ref(); + + for log in state_lock.logs.iter() { + // // let runtime = self.current_runtimes + + body.row(18., |mut row| { + row.col(|ui| log.display_time(ui)); + row.col(|ui| log.display_level(ui)); + row.col(|ui| log.display_message(ui)); + if self.enable_location { + row.col(|ui| log.display_location(ui)); + } + }); + } + }); + }); + }); }); + + { + let state_lock = self.state.lock().unwrap(); + + match ( + state_lock.is_requesting, + state_lock.logs.len() == 0, + state_lock.requested_data, + ) { + (true, _, _) => { + drop(state_lock); + ui.spinner(); + } + (false, true, true) => { + drop(state_lock); + ui.label("There are no logs"); + } + (false, true, false) => { + drop(state_lock); + self.refresh_logs(auth); + } + _ => { + drop(state_lock); + } + } } } + + fn refresh_logs(&self, auth: &mut Auth) { + let state_clone = self.state.clone(); + { + let mut state_lock = self.state.lock().unwrap(); + state_lock.logs.clear(); + state_lock.is_requesting = true; + } + auth.get(&format!("/api/log/{}", 0), move |e: Vec| { + let mut state_lock = state_clone.lock().unwrap(); + state_lock.logs.append( + &mut e + .iter() + .map(|log| serde_json::from_str(log).unwrap()) + .collect(), + ); + state_lock.is_requesting = false; + state_lock.requested_data = true; + + // crate::log(&format!("{e:?}")); + }); + } } impl Default for LogViewer { fn default() -> Self { Self { - // logs: Vec::new(), + enable_location: false, state: Arc::new(Mutex::new(LogState::default())), } } diff --git a/unshell-lib/Cargo.lock b/unshell-lib/Cargo.lock index 7b06f4a..00c8031 100644 --- a/unshell-lib/Cargo.lock +++ b/unshell-lib/Cargo.lock @@ -57,6 +57,18 @@ dependencies = [ "virtue", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + [[package]] name = "block-buffer" version = "0.10.4" @@ -115,6 +127,18 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.42" @@ -303,6 +327,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.28" @@ -330,6 +363,29 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -392,6 +448,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "regex" version = "1.12.2" @@ -433,6 +498,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.228" @@ -460,7 +531,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.109", ] [[package]] @@ -493,6 +564,51 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "static_init" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bae1df58c5fea7502e8e352ec26b5579f6178e1fdb311e088580c980dee25ed" +dependencies = [ + "bitflags 1.3.2", + "cfg_aliases 0.2.1", + "libc", + "parking_lot", + "parking_lot_core", + "static_init_macro", + "winapi", +] + +[[package]] +name = "static_init_macro" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1389c88ddd739ec6d3f8f83343764a0e944cd23cfbf126a9796a714b0b6edd6f" +dependencies = [ + "cfg_aliases 0.1.1", + "memchr", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.109" @@ -551,7 +667,9 @@ version = "0.0.0" dependencies = [ "proc-macro2", "quote", - "syn", + "rand", + "static_init", + "syn 2.0.109", "unshell-crypt", ] @@ -614,7 +732,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.109", "wasm-bindgen-shared", ] @@ -627,6 +745,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -648,7 +788,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.109", ] [[package]] @@ -659,7 +799,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.109", ] [[package]] @@ -709,5 +849,5 @@ checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.109", ] diff --git a/unshell-lib/Cargo.toml b/unshell-lib/Cargo.toml index ddf9680..699521f 100644 --- a/unshell-lib/Cargo.toml +++ b/unshell-lib/Cargo.toml @@ -3,7 +3,7 @@ name = "unshell-lib" edition = "2024" [features] -default = ["client", "server", "log_debug"] +default = ["client", "server", "log"] # Components client = [] diff --git a/unshell-lib/src/logger/mod.rs b/unshell-lib/src/logger/mod.rs index e59f097..1daced4 100644 --- a/unshell-lib/src/logger/mod.rs +++ b/unshell-lib/src/logger/mod.rs @@ -13,7 +13,7 @@ pub use pretty_logger::PrettyLogger; static mut LOGGER: &dyn Logger = &DefaultLogger; -#[derive(Debug)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] pub enum LogLevel { Debug, Info, @@ -21,7 +21,7 @@ pub enum LogLevel { Error, } -#[derive(Debug)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Record { log_level: LogLevel, location: Option, diff --git a/unshell-server/Cargo.lock b/unshell-server/Cargo.lock index d128a93..5540962 100644 --- a/unshell-server/Cargo.lock +++ b/unshell-server/Cargo.lock @@ -1549,6 +1549,8 @@ version = "0.0.0" dependencies = [ "proc-macro2", "quote", + "rand", + "static_init", "syn 2.0.111", "unshell-crypt", ] diff --git a/unshell-server/Cargo.toml b/unshell-server/Cargo.toml index 745db50..a3a975a 100644 --- a/unshell-server/Cargo.toml +++ b/unshell-server/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [features] -log_debug = [] +log_debug = ["unshell-lib/log_debug"] diff --git a/unshell-server/src/lib.rs b/unshell-server/src/lib.rs index 0c40c0a..3789df1 100644 --- a/unshell-server/src/lib.rs +++ b/unshell-server/src/lib.rs @@ -2,6 +2,7 @@ mod api; pub mod logger; +mod modules; mod server; pub use api::app::start_api; diff --git a/unshell-server/src/logger.rs b/unshell-server/src/logger.rs index e2d063a..6bb8b6c 100644 --- a/unshell-server/src/logger.rs +++ b/unshell-server/src/logger.rs @@ -9,7 +9,7 @@ use std::path::PathBuf; const LOG_DIR: &str = "logs"; /// The maximum number of logs to return in one call to `poll_logs`. -const LOG_COUNT: usize = 10; +const LOG_COUNT: usize = 100; /// The full path to the log file. /// Initialized once based on the startup time. @@ -47,8 +47,7 @@ impl Logger { /// * `message` - The string content of the log entry. pub fn log(message: String) { // 1. Format the complete log line with timestamp - let now = Local::now(); - let log_line = format!("[{}] {}\n", now.format("%Y-%m-%d %H:%M:%S%.3f"), message); + let log_line = format!("{}\n", message); // 2. Open the file in append mode, creating it if it doesn't exist. // The file path is guaranteed to be valid due to the `lazy_static` initialization. @@ -117,9 +116,7 @@ impl Logger { // Get the slice of the lines let slice = &lines[start_index..end_index]; - // The logs are currently in historical order (oldest to newest within the slice). - // We must reverse them to return the "most recent" logs in descending order. - slice.iter().rev().cloned().collect() + slice.iter().cloned().collect() } Err(e) => { // Return an empty vector and print an error message if the file cannot be read diff --git a/unshell-server/src/main.rs b/unshell-server/src/main.rs index 97c8133..3560361 100644 --- a/unshell-server/src/main.rs +++ b/unshell-server/src/main.rs @@ -26,7 +26,9 @@ async fn main() { let args = Args::parse(); unshell_lib::logger::PrettyLogger::init_output(|message| { - unshell_server::logger::Logger::log(format!("{message:?}")); + if let Ok(json) = serde_json::to_string(message) { + unshell_server::logger::Logger::log(json); + } }); let database = Server::new(args.database_name); diff --git a/unshell-server/src/modules/mod.rs b/unshell-server/src/modules/mod.rs new file mode 100644 index 0000000..e69de29