diff --git a/Cargo.lock b/Cargo.lock index 188850e8e5..93f24f8ead 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4635,6 +4635,7 @@ dependencies = [ "log 0.4.14", "qrcode", "rand 0.8.4", + "regex", "rpassword", "rustyline", "strum", diff --git a/applications/tari_console_wallet/Cargo.toml b/applications/tari_console_wallet/Cargo.toml index 53939552f3..5f43a75189 100644 --- a/applications/tari_console_wallet/Cargo.toml +++ b/applications/tari_console_wallet/Cargo.toml @@ -26,6 +26,7 @@ unicode-width = "0.1" unicode-segmentation = "1.6.0" log = { version = "0.4.8", features = ["std"] } qrcode = { version = "0.12" } +regex = "1.5.4" rpassword = "5.0" rustyline = "6.0" strum = "^0.19" diff --git a/applications/tari_console_wallet/src/ui/app.rs b/applications/tari_console_wallet/src/ui/app.rs index b9df53d852..028a37de81 100644 --- a/applications/tari_console_wallet/src/ui/app.rs +++ b/applications/tari_console_wallet/src/ui/app.rs @@ -25,6 +25,7 @@ use crate::{ ui::{ components::{ base_node::BaseNode, + log_tab::LogTab, menu::Menu, network_tab::NetworkTab, receive_tab::ReceiveTab, @@ -85,7 +86,8 @@ impl App { .add("Transactions".into(), Box::new(TransactionsTab::new())) .add("Send".into(), Box::new(SendTab::new())) .add("Receive".into(), Box::new(ReceiveTab::new())) - .add("Network".into(), Box::new(NetworkTab::new(base_node_selected))); + .add("Network".into(), Box::new(NetworkTab::new(base_node_selected))) + .add("Log".into(), Box::new(LogTab::new())); let base_node_status = BaseNode::new(); let menu = Menu::new(); diff --git a/applications/tari_console_wallet/src/ui/components/log_tab.rs b/applications/tari_console_wallet/src/ui/components/log_tab.rs new file mode 100644 index 0000000000..9efaf0faf8 --- /dev/null +++ b/applications/tari_console_wallet/src/ui/components/log_tab.rs @@ -0,0 +1,107 @@ +use crate::ui::{components::Component, state::AppState}; +use regex::Regex; +use std::fs; +use tui::{ + backend::Backend, + layout::{Constraint, Layout, Rect}, + style::{Color, Modifier, Style}, + text::{Span, Spans}, + widgets::{Block, Borders, Paragraph, Wrap}, + Frame, +}; + +pub struct LogTab { + scroll: u16, + re: Regex, +} + +impl LogTab { + pub fn new() -> Self { + Self { scroll: 1, + re : Regex::new( + r"(?P\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d*) \[(?P[^\]]*)\] (?PINFO|WARN|DEBUG|ERROR|TRACE)\s*(?P .*)", + ) + .unwrap() + } + } + + // Format the log line nicely. If it cannot be parsed then return raw line + fn format_line(&self, line: String) -> Spans { + match self.re.captures(line.as_str()) { + Some(caps) => Spans::from(vec![ + Span::styled(caps["timestamp"].to_string(), Style::default().fg(Color::LightGreen)), + Span::raw(" ["), + Span::styled(caps["target"].to_string(), Style::default().fg(Color::LightMagenta)), + Span::raw("] "), + Span::styled( + caps["level"].to_string(), + Style::default().fg(match &caps["level"] { + "ERROR" => Color::LightRed, + "WARN" => Color::LightYellow, + _ => Color::LightMagenta, + }), + ), + Span::raw(caps["message"].to_string()), + ]), + // In case the line is not well formatted, just print as it is + None => Spans::from(vec![Span::raw(line)]), + } + } + + fn draw_logs(&mut self, f: &mut Frame, area: Rect, _app_state: &AppState) + where B: Backend { + // First render the border and calculate the inner area + let block = Block::default().borders(Borders::ALL).title(Span::styled( + "StdOut log", + Style::default().fg(Color::White).add_modifier(Modifier::BOLD), + )); + f.render_widget(block, area); + let log_area = Layout::default() + .constraints([Constraint::Min(42)].as_ref()) + .margin(1) + .split(area); + // Read the log file + let content = match fs::read_to_string("log/wallet/stdout.log") { + Ok(content) => content, + Err(err) => format!("Error reading log : {}", err), + }; + // Convert the content into Spans + let mut text: Vec = content + .split('\n') + .map(|line| self.format_line(line.to_string())) + .collect(); + // We want newest at the top + text.reverse(); + // Render the Paragraph + let paragraph = Paragraph::new(text.clone()) + .wrap(Wrap { trim: true }) + .scroll((self.scroll, 0)); + f.render_widget(paragraph, log_area[0]); + } +} + +impl Component for LogTab { + fn draw(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) { + let areas = Layout::default() + .constraints([Constraint::Min(42)].as_ref()) + .split(area); + + self.draw_logs(f, areas[0], app_state); + } + + fn on_key(&mut self, _app_state: &mut AppState, _c: char) {} + + fn on_up(&mut self, _app_state: &mut AppState) { + if self.scroll > 1 { + self.scroll -= 1; + } + } + + fn on_down(&mut self, _app_state: &mut AppState) { + self.scroll += 1; + } + + fn on_esc(&mut self, _: &mut AppState) {} + + fn on_backspace(&mut self, _app_state: &mut AppState) {} +} diff --git a/applications/tari_console_wallet/src/ui/components/mod.rs b/applications/tari_console_wallet/src/ui/components/mod.rs index 562c3d5763..acfd103c32 100644 --- a/applications/tari_console_wallet/src/ui/components/mod.rs +++ b/applications/tari_console_wallet/src/ui/components/mod.rs @@ -23,6 +23,7 @@ pub mod balance; pub mod base_node; mod component; +pub mod log_tab; pub(crate) mod menu; pub mod network_tab; pub mod receive_tab; diff --git a/common/logging/log4rs_sample_wallet.yml b/common/logging/log4rs_sample_wallet.yml index ae89009eca..631d479043 100644 --- a/common/logging/log4rs_sample_wallet.yml +++ b/common/logging/log4rs_sample_wallet.yml @@ -11,6 +11,17 @@ # timestamp [target] LEVEL message refresh_rate: 30 seconds appenders: +# An appender named "stdout" that writes to file. + stdout: + kind: file + path: "log/wallet/stdout.log" + append: false + encoder: + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{t}] {h({l}):5} {m}{n}" + filters: + - kind: threshold + level: warn + # An appender named "network" that writes to a file with a custom pattern encoder network: kind: rolling_file @@ -45,7 +56,7 @@ appenders: encoder: pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{t}] [Thread:{I}] {l:5} {m}{n}" - # An appender named "base_layer" that writes to a file with a custom pattern encoder + # An appender named "base_layer" that writes to a file with a custom pattern encoder other: kind: rolling_file path: "log/wallet/other.log" @@ -67,6 +78,7 @@ root: level: info appenders: - base_layer + - stdout loggers: # base_layer @@ -106,7 +118,7 @@ loggers: level: info appenders: - network - additive: false + # Route log events sent to the "mio" logger to the "other" appender mio: level: error appenders: