diff --git a/src/env_var_descriptions.rs b/src/env_var_descriptions.rs index c8b33b8..036aa0b 100644 --- a/src/env_var_descriptions.rs +++ b/src/env_var_descriptions.rs @@ -11,6 +11,7 @@ pub static ENV_VAR_DESCRIPTIONS: Map<&str, &str> = phf_map! { "LD_LIBRARY_PATH" => "Colon-separated list of directories where the dynamic linker will search for shared object libraries.", "XRT_DEBUG_GUI" => "Set to 1 to enable the Monado debug UI", + "XRT_JSON_LOG" => "Set to 1 to enable JSON logging for Monado. This enables better log visualization and log level filtering.", }; pub fn env_var_descriptions_as_paragraph() -> String { diff --git a/src/log_level.rs b/src/log_level.rs new file mode 100644 index 0000000..b2fc926 --- /dev/null +++ b/src/log_level.rs @@ -0,0 +1,102 @@ +use serde::{de::Visitor, Deserialize, Serialize}; +use std::{fmt::Display, slice::Iter}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)] +pub enum LogLevel { + Trace, + Debug, + Info, + Warning, + Error, +} + +struct LogLevelStringVisitor; +impl<'de> Visitor<'de> for LogLevelStringVisitor { + type Value = LogLevel; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str( + "A case-insensitive string among trace, debug, info, warning, warn, error, err", + ) + } + + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: serde::de::Error, { + Ok(LogLevel::from_string(v.to_string())) + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, { + Ok(LogLevel::from_string(v)) + } +} + +impl<'de> Deserialize<'de> for LogLevel { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de> { + deserializer.deserialize_string(LogLevelStringVisitor) + } +} + +impl LogLevel { + pub fn from_string(s: String) -> Self { + match s.to_lowercase().as_str() { + "trace" => Self::Trace, + "debug" => Self::Debug, + "info" => Self::Info, + "warning" => Self::Warning, + "warn" => Self::Warning, + "error" => Self::Error, + "err" => Self::Error, + _ => Self::Debug, + } + } + + pub fn iter() -> Iter<'static, LogLevel> { + [ + Self::Trace, + Self::Debug, + Self::Info, + Self::Warning, + Self::Error, + ] + .iter() + } + + pub fn as_number(&self) -> u32 { + match self { + Self::Trace => 0, + Self::Debug => 1, + Self::Info => 2, + Self::Warning => 3, + Self::Error => 99, + } + } +} + +impl PartialOrd for LogLevel { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.as_number().cmp(&other.as_number())) + } +} + +impl Ord for LogLevel { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + +impl Display for LogLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + LogLevel::Trace => "Trace", + LogLevel::Debug => "Debug", + LogLevel::Info => "Info", + LogLevel::Warning => "Warning", + LogLevel::Error => "Error", + }) + } +} diff --git a/src/log_parser.rs b/src/log_parser.rs new file mode 100644 index 0000000..ca1ce32 --- /dev/null +++ b/src/log_parser.rs @@ -0,0 +1,16 @@ +use crate::log_level::LogLevel; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct MonadoLog { + pub level: LogLevel, + pub file: String, + pub func: String, + pub message: String, +} + +impl MonadoLog { + pub fn from_str(s: &str) -> Option { + serde_json::from_str::(s).ok() + } +} diff --git a/src/main.rs b/src/main.rs index 29482e6..ee2fb37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,8 @@ pub mod ui; pub mod adb; pub mod downloader; pub mod env_var_descriptions; +pub mod log_parser; +pub mod log_level; fn main() -> Result<()> { // Prepare i18n diff --git a/src/profiles/system_valve_index.rs b/src/profiles/system_valve_index.rs index d7f0380..17b22fe 100644 --- a/src/profiles/system_valve_index.rs +++ b/src/profiles/system_valve_index.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; pub fn system_valve_index_profile() -> Profile { let mut environment: HashMap = HashMap::new(); + environment.insert("XRT_JSON_LOG".into(), "1".into()); environment.insert("XRT_COMPOSITOR_SCALE_PERCENTAGE".into(), "140".into()); environment.insert("XRT_COMPOSITOR_COMPUTE".into(), "1".into()); environment.insert("SURVIVE_GLOBALSCENESOLVER".into(), "0".into()); diff --git a/src/profiles/valve_index.rs b/src/profiles/valve_index.rs index 3b5cbde..4f5f748 100644 --- a/src/profiles/valve_index.rs +++ b/src/profiles/valve_index.rs @@ -10,6 +10,7 @@ pub fn valve_index_profile() -> Profile { let data_dir = get_data_dir(); let prefix = format!("{data}/prefixes/valve_index_default", data = data_dir); let mut environment: HashMap = HashMap::new(); + environment.insert("XRT_JSON_LOG".into(), "1".into()); environment.insert("XRT_COMPOSITOR_SCALE_PERCENTAGE".into(), "140".into()); environment.insert("XRT_COMPOSITOR_COMPUTE".into(), "1".into()); environment.insert("SURVIVE_GLOBALSCENESOLVER".into(), "0".into()); diff --git a/src/ui/debug_view.rs b/src/ui/debug_view.rs index fe62dc4..2fccdac 100644 --- a/src/ui/debug_view.rs +++ b/src/ui/debug_view.rs @@ -1,55 +1,10 @@ +use crate::log_level::LogLevel; +use crate::log_parser::MonadoLog; use expect_dialog::ExpectDialog; use gtk::prelude::*; use relm4::prelude::*; use relm4::{ComponentSender, SimpleComponent}; use sourceview5::prelude::*; -use std::fmt::Display; -use std::slice::Iter; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum LogLevel { - Trace, - Debug, - Info, - Warning, - Error, -} - -impl LogLevel { - pub fn from_string(s: String) -> Self { - match s.to_lowercase().as_str() { - "trace" => Self::Trace, - "debug" => Self::Debug, - "info" => Self::Info, - "warning" => Self::Warning, - "error" => Self::Error, - _ => Self::Debug, - } - } - - pub fn iter() -> Iter<'static, LogLevel> { - [ - Self::Trace, - Self::Debug, - Self::Info, - Self::Warning, - Self::Error, - ] - .iter() - } -} - -impl Display for LogLevel { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - LogLevel::Trace => "Trace", - LogLevel::Debug => "Debug", - LogLevel::Info => "Info", - LogLevel::Warning => "Warning", - LogLevel::Error => "Error", - }) - } -} #[derive(Debug)] pub enum SearchDirection { @@ -63,6 +18,7 @@ pub enum DebugViewMsg { ClearLog, EnableDebugViewChanged(bool), FilterLog(SearchDirection), + LogLevelChanged(LogLevel), } #[tracker::track] @@ -87,6 +43,8 @@ pub struct DebugView { search_settings: sourceview5::SearchSettings, #[tracker::do_not_track] search_mark: Option, + #[tracker::do_not_track] + log_level: LogLevel, enable_debug_view: bool, } @@ -189,16 +147,17 @@ impl SimpleComponent for DebugView { self.reset(); match message { + Self::Input::LogLevelChanged(lvl) => { + self.log_level = lvl; + let log = self.log.clone(); + self.log = vec![]; + self.textbuf.set_text(""); + sender.input(Self::Input::LogUpdated(log)); + } Self::Input::FilterLog(direction) => { let searchbar = self.searchbar.as_ref().unwrap().clone(); let search_entry = self.search_entry.as_ref().unwrap().clone(); let search_text = search_entry.text().to_string(); - // TODO: add log level filtering - let log_level = LogLevel::iter() - .as_slice() - .get(self.dropdown.as_ref().unwrap().selected() as usize) - .unwrap(); - println!("log level: {}", log_level.to_string()); if searchbar.is_search_mode() && !search_text.is_empty() { self.search_settings .set_search_text(Some(search_text.as_str())); @@ -240,8 +199,25 @@ impl SimpleComponent for DebugView { (adj.upper() - adj.page_size() - adj.value()) <= 15.0 }; self.log.extend(n_log.clone()); - self.textbuf - .insert(&mut self.textbuf.end_iter(), n_log.concat().as_str()); + for row in n_log { + let txt = match MonadoLog::from_str(row.as_str()) { + Some(o) => match o.level >= self.log_level { + false => None, + true => Some(format!( + "{lvl}\t[{file}:{func}]\n\t{msg}\n", + lvl = o.level.to_string(), + file = o.file, + func = o.func, + msg = o.message + )), + }, + None => Some(row), + }; + if txt.is_some() { + self.textbuf + .insert(&mut self.textbuf.end_iter(), txt.unwrap().as_str()); + } + } let textbuf = self.textbuf.clone(); let textview = self.textview.as_ref().unwrap().clone(); if is_at_bottom && !self.searchbar.as_ref().unwrap().is_search_mode() { @@ -293,6 +269,7 @@ impl SimpleComponent for DebugView { search_settings, search_ctx, search_mark: None, + log_level: LogLevel::Trace, }; let widgets = view_output!(); @@ -306,8 +283,14 @@ impl SimpleComponent for DebugView { let dd_sender = sender.clone(); widgets .log_level_dropdown - .connect_selected_notify(move |_| { - dd_sender.input(Self::Input::FilterLog(SearchDirection::Forward)); + .connect_selected_notify(move |dd| { + dd_sender.input(Self::Input::LogLevelChanged( + LogLevel::iter() + .as_slice() + .get(dd.selected() as usize) + .unwrap() + .clone(), + )); }); }