mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-20 19:11:08 +03:00
feat: TUI logs view (#3064)
* fix: add logs page to TUI * chore: print panic traces to TUI logs * chore: stop and start tui nicely and a bit of refactoring * chore: rustfmt * chore: typo * chore: use sync_channel for logs * chore: don't try to unwrap err on try_send log message * chore: fix compiler/lint warnings * fix: Only create logs channel if TUI is enabled and resovle other small review comments * fix: wrap logs in TUI to fix window size * fix: debug and trace logs appear white in the TUI logs
This commit is contained in:
parent
38e6497919
commit
8ce2bfda58
15 changed files with 313 additions and 152 deletions
|
@ -114,8 +114,7 @@ fn comments() -> HashMap<String, String> {
|
||||||
retval.insert(
|
retval.insert(
|
||||||
"run_tui".to_string(),
|
"run_tui".to_string(),
|
||||||
"
|
"
|
||||||
#whether to run the ncurses TUI. Ncurses must be installed and this
|
#whether to run the ncurses TUI (Ncurses must be installed)
|
||||||
#will also disable logging to stdout
|
|
||||||
"
|
"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -30,7 +30,7 @@ use crate::core::global;
|
||||||
use crate::p2p;
|
use crate::p2p;
|
||||||
use crate::servers::ServerConfig;
|
use crate::servers::ServerConfig;
|
||||||
use crate::types::{ConfigError, ConfigMembers, GlobalConfig};
|
use crate::types::{ConfigError, ConfigMembers, GlobalConfig};
|
||||||
use crate::util::LoggingConfig;
|
use crate::util::logger::LoggingConfig;
|
||||||
|
|
||||||
/// The default file name to use when trying to derive
|
/// The default file name to use when trying to derive
|
||||||
/// the node config file location
|
/// the node config file location
|
||||||
|
|
|
@ -19,7 +19,7 @@ use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::servers::ServerConfig;
|
use crate::servers::ServerConfig;
|
||||||
use crate::util::LoggingConfig;
|
use crate::util::logger::LoggingConfig;
|
||||||
|
|
||||||
/// Error type wrapping config errors.
|
/// Error type wrapping config errors.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -15,6 +15,6 @@
|
||||||
//! Modules common to all Grin server types
|
//! Modules common to all Grin server types
|
||||||
|
|
||||||
pub mod adapters;
|
pub mod adapters;
|
||||||
|
pub mod hooks;
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod hooks;
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::{mpsc, Arc};
|
||||||
use std::{
|
use std::{
|
||||||
thread::{self, JoinHandle},
|
thread::{self, JoinHandle},
|
||||||
time,
|
time,
|
||||||
|
@ -52,6 +52,7 @@ use crate::p2p::types::PeerAddr;
|
||||||
use crate::pool;
|
use crate::pool;
|
||||||
use crate::util::file::get_first_line;
|
use crate::util::file::get_first_line;
|
||||||
use crate::util::{RwLock, StopState};
|
use crate::util::{RwLock, StopState};
|
||||||
|
use grin_util::logger::LogEntry;
|
||||||
|
|
||||||
/// Grin server holding internal structures.
|
/// Grin server holding internal structures.
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
|
@ -83,9 +84,13 @@ impl Server {
|
||||||
/// Instantiates and starts a new server. Optionally takes a callback
|
/// Instantiates and starts a new server. Optionally takes a callback
|
||||||
/// for the server to send an ARC copy of itself, to allow another process
|
/// for the server to send an ARC copy of itself, to allow another process
|
||||||
/// to poll info about the server status
|
/// to poll info about the server status
|
||||||
pub fn start<F>(config: ServerConfig, mut info_callback: F) -> Result<(), Error>
|
pub fn start<F>(
|
||||||
|
config: ServerConfig,
|
||||||
|
logs_rx: Option<mpsc::Receiver<LogEntry>>,
|
||||||
|
mut info_callback: F,
|
||||||
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
F: FnMut(Server),
|
F: FnMut(Server, Option<mpsc::Receiver<LogEntry>>),
|
||||||
{
|
{
|
||||||
let mining_config = config.stratum_mining_config.clone();
|
let mining_config = config.stratum_mining_config.clone();
|
||||||
let enable_test_miner = config.run_test_miner;
|
let enable_test_miner = config.run_test_miner;
|
||||||
|
@ -111,7 +116,7 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info_callback(serv);
|
info_callback(serv, logs_rx);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -555,9 +560,10 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// this call is blocking and makes sure all peers stop, however
|
// this call is blocking and makes sure all peers stop, however
|
||||||
// we can't be sure that we stoped a listener blocked on accept, so we don't join the p2p thread
|
// we can't be sure that we stopped a listener blocked on accept, so we don't join the p2p thread
|
||||||
self.p2p.stop();
|
self.p2p.stop();
|
||||||
let _ = self.lock_file.unlock();
|
let _ = self.lock_file.unlock();
|
||||||
|
warn!("Shutdown complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pause the p2p server.
|
/// Pause the p2p server.
|
||||||
|
|
|
@ -27,46 +27,53 @@ use crate::core::global;
|
||||||
use crate::p2p::{PeerAddr, Seeding};
|
use crate::p2p::{PeerAddr, Seeding};
|
||||||
use crate::servers;
|
use crate::servers;
|
||||||
use crate::tui::ui;
|
use crate::tui::ui;
|
||||||
|
use grin_util::logger::LogEntry;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
|
||||||
/// wrap below to allow UI to clean up on stop
|
/// wrap below to allow UI to clean up on stop
|
||||||
pub fn start_server(config: servers::ServerConfig) {
|
pub fn start_server(config: servers::ServerConfig, logs_rx: Option<mpsc::Receiver<LogEntry>>) {
|
||||||
start_server_tui(config);
|
start_server_tui(config, logs_rx);
|
||||||
// Just kill process for now, otherwise the process
|
// Just kill process for now, otherwise the process
|
||||||
// hangs around until sigint because the API server
|
// hangs around until sigint because the API server
|
||||||
// currently has no shutdown facility
|
// currently has no shutdown facility
|
||||||
warn!("Shutting down...");
|
|
||||||
thread::sleep(Duration::from_millis(1000));
|
|
||||||
warn!("Shutdown complete.");
|
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_server_tui(config: servers::ServerConfig) {
|
fn start_server_tui(config: servers::ServerConfig, logs_rx: Option<mpsc::Receiver<LogEntry>>) {
|
||||||
// Run the UI controller.. here for now for simplicity to access
|
// Run the UI controller.. here for now for simplicity to access
|
||||||
// everything it might need
|
// everything it might need
|
||||||
if config.run_tui.unwrap_or(false) {
|
if config.run_tui.unwrap_or(false) {
|
||||||
warn!("Starting GRIN in UI mode...");
|
warn!("Starting GRIN in UI mode...");
|
||||||
servers::Server::start(config, |serv: servers::Server| {
|
servers::Server::start(
|
||||||
let mut controller = ui::Controller::new().unwrap_or_else(|e| {
|
config,
|
||||||
panic!("Error loading UI controller: {}", e);
|
logs_rx,
|
||||||
});
|
|serv: servers::Server, logs_rx: Option<mpsc::Receiver<LogEntry>>| {
|
||||||
controller.run(serv);
|
let mut controller = ui::Controller::new(logs_rx.unwrap()).unwrap_or_else(|e| {
|
||||||
})
|
panic!("Error loading UI controller: {}", e);
|
||||||
|
});
|
||||||
|
controller.run(serv);
|
||||||
|
},
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
warn!("Starting GRIN w/o UI...");
|
warn!("Starting GRIN w/o UI...");
|
||||||
servers::Server::start(config, |serv: servers::Server| {
|
servers::Server::start(
|
||||||
let running = Arc::new(AtomicBool::new(true));
|
config,
|
||||||
let r = running.clone();
|
logs_rx,
|
||||||
ctrlc::set_handler(move || {
|
|serv: servers::Server, _: Option<mpsc::Receiver<LogEntry>>| {
|
||||||
r.store(false, Ordering::SeqCst);
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
})
|
let r = running.clone();
|
||||||
.expect("Error setting handler for both SIGINT (Ctrl+C) and SIGTERM (kill)");
|
ctrlc::set_handler(move || {
|
||||||
while running.load(Ordering::SeqCst) {
|
r.store(false, Ordering::SeqCst);
|
||||||
thread::sleep(Duration::from_secs(1));
|
})
|
||||||
}
|
.expect("Error setting handler for both SIGINT (Ctrl+C) and SIGTERM (kill)");
|
||||||
warn!("Received SIGINT (Ctrl+C) or SIGTERM (kill).");
|
while running.load(Ordering::SeqCst) {
|
||||||
serv.stop();
|
thread::sleep(Duration::from_secs(1));
|
||||||
})
|
}
|
||||||
|
warn!("Received SIGINT (Ctrl+C) or SIGTERM (kill).");
|
||||||
|
serv.stop();
|
||||||
|
},
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +85,7 @@ fn start_server_tui(config: servers::ServerConfig) {
|
||||||
pub fn server_command(
|
pub fn server_command(
|
||||||
server_args: Option<&ArgMatches<'_>>,
|
server_args: Option<&ArgMatches<'_>>,
|
||||||
mut global_config: GlobalConfig,
|
mut global_config: GlobalConfig,
|
||||||
|
logs_rx: Option<mpsc::Receiver<LogEntry>>,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
global::set_mining_mode(
|
global::set_mining_mode(
|
||||||
global_config
|
global_config
|
||||||
|
@ -123,7 +131,7 @@ pub fn server_command(
|
||||||
if let Some(a) = server_args {
|
if let Some(a) = server_args {
|
||||||
match a.subcommand() {
|
match a.subcommand() {
|
||||||
("run", _) => {
|
("run", _) => {
|
||||||
start_server(server_config);
|
start_server(server_config, logs_rx);
|
||||||
}
|
}
|
||||||
("", _) => {
|
("", _) => {
|
||||||
println!("Subcommand required, use 'grin help server' for details");
|
println!("Subcommand required, use 'grin help server' for details");
|
||||||
|
@ -137,7 +145,7 @@ pub fn server_command(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
start_server(server_config);
|
start_server(server_config, logs_rx);
|
||||||
}
|
}
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,8 @@ use grin_core as core;
|
||||||
use grin_p2p as p2p;
|
use grin_p2p as p2p;
|
||||||
use grin_servers as servers;
|
use grin_servers as servers;
|
||||||
use grin_util as util;
|
use grin_util as util;
|
||||||
|
use grin_util::logger::LogEntry;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
|
||||||
mod cmd;
|
mod cmd;
|
||||||
pub mod tui;
|
pub mod tui;
|
||||||
|
@ -136,26 +138,28 @@ fn real_main() -> i32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut config) = node_config.clone() {
|
let mut config = node_config.clone().unwrap();
|
||||||
let mut l = config.members.as_mut().unwrap().logging.clone().unwrap();
|
let mut logging_config = config.members.as_mut().unwrap().logging.clone().unwrap();
|
||||||
let run_tui = config.members.as_mut().unwrap().server.run_tui;
|
logging_config.tui_running = config.members.as_mut().unwrap().server.run_tui;
|
||||||
if let Some(true) = run_tui {
|
|
||||||
l.log_to_stdout = false;
|
|
||||||
l.tui_running = Some(true);
|
|
||||||
}
|
|
||||||
init_logger(Some(l));
|
|
||||||
|
|
||||||
global::set_mining_mode(config.members.unwrap().server.clone().chain_type);
|
let (logs_tx, logs_rx) = if logging_config.tui_running.unwrap() {
|
||||||
|
let (logs_tx, logs_rx) = mpsc::sync_channel::<LogEntry>(200);
|
||||||
|
(Some(logs_tx), Some(logs_rx))
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
init_logger(Some(logging_config), logs_tx);
|
||||||
|
|
||||||
if let Some(file_path) = &config.config_file_path {
|
global::set_mining_mode(config.members.unwrap().server.clone().chain_type);
|
||||||
info!(
|
|
||||||
"Using configuration file at {}",
|
if let Some(file_path) = &config.config_file_path {
|
||||||
file_path.to_str().unwrap()
|
info!(
|
||||||
);
|
"Using configuration file at {}",
|
||||||
} else {
|
file_path.to_str().unwrap()
|
||||||
info!("Node configuration file not found, using default");
|
);
|
||||||
}
|
} else {
|
||||||
}
|
info!("Node configuration file not found, using default");
|
||||||
|
};
|
||||||
|
|
||||||
log_build_info();
|
log_build_info();
|
||||||
|
|
||||||
|
@ -163,7 +167,7 @@ fn real_main() -> i32 {
|
||||||
match args.subcommand() {
|
match args.subcommand() {
|
||||||
// server commands and options
|
// server commands and options
|
||||||
("server", Some(server_args)) => {
|
("server", Some(server_args)) => {
|
||||||
cmd::server_command(Some(server_args), node_config.unwrap())
|
cmd::server_command(Some(server_args), node_config.unwrap(), logs_rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// client commands and options
|
// client commands and options
|
||||||
|
@ -177,11 +181,11 @@ fn real_main() -> i32 {
|
||||||
Ok(_) => 0,
|
Ok(_) => 0,
|
||||||
Err(_) => 1,
|
Err(_) => 1,
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
// If nothing is specified, try to just use the config file instead
|
// If nothing is specified, try to just use the config file instead
|
||||||
// this could possibly become the way to configure most things
|
// this could possibly become the way to configure most things
|
||||||
// with most command line options being phased out
|
// with most command line options being phased out
|
||||||
_ => cmd::server_command(None, node_config.unwrap()),
|
_ => cmd::server_command(None, node_config.unwrap(), logs_rx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,9 @@ pub const SUBMENU_MINING_BUTTON: &str = "mining_submenu_button";
|
||||||
pub const TABLE_MINING_STATUS: &str = "mining_status_table";
|
pub const TABLE_MINING_STATUS: &str = "mining_status_table";
|
||||||
pub const TABLE_MINING_DIFF_STATUS: &str = "mining_diff_status_table";
|
pub const TABLE_MINING_DIFF_STATUS: &str = "mining_diff_status_table";
|
||||||
|
|
||||||
|
// Logs View
|
||||||
|
pub const VIEW_LOGS: &str = "logs_view";
|
||||||
|
|
||||||
// Mining View
|
// Mining View
|
||||||
pub const VIEW_VERSION: &str = "version_view";
|
pub const VIEW_VERSION: &str = "version_view";
|
||||||
|
|
||||||
|
|
104
src/bin/tui/logs.rs
Normal file
104
src/bin/tui/logs.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright 2019 The Grin Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use cursive::theme::{BaseColor, Color, ColorStyle};
|
||||||
|
use cursive::traits::Identifiable;
|
||||||
|
use cursive::view::View;
|
||||||
|
use cursive::views::BoxView;
|
||||||
|
use cursive::{Cursive, Printer};
|
||||||
|
|
||||||
|
use crate::tui::constants::VIEW_LOGS;
|
||||||
|
use cursive::utils::lines::spans::{LinesIterator, Row};
|
||||||
|
use cursive::utils::markup::StyledString;
|
||||||
|
use grin_util::logger::LogEntry;
|
||||||
|
use log::Level;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
pub struct TUILogsView;
|
||||||
|
|
||||||
|
impl TUILogsView {
|
||||||
|
pub fn create() -> Box<dyn View> {
|
||||||
|
let logs_view = BoxView::with_full_screen(LogBufferView::new(200).with_id("logs"));
|
||||||
|
Box::new(logs_view.with_id(VIEW_LOGS))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(c: &mut Cursive, entry: LogEntry) {
|
||||||
|
c.call_on_id("logs", |t: &mut LogBufferView| {
|
||||||
|
t.update(entry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LogBufferView {
|
||||||
|
buffer: VecDeque<LogEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LogBufferView {
|
||||||
|
fn new(size: usize) -> Self {
|
||||||
|
let mut buffer = VecDeque::new();
|
||||||
|
buffer.resize(
|
||||||
|
size,
|
||||||
|
LogEntry {
|
||||||
|
log: String::new(),
|
||||||
|
level: Level::Info,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
LogBufferView { buffer }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, entry: LogEntry) {
|
||||||
|
self.buffer.push_front(entry);
|
||||||
|
self.buffer.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color(level: Level) -> ColorStyle {
|
||||||
|
match level {
|
||||||
|
Level::Info => ColorStyle::new(
|
||||||
|
Color::Light(BaseColor::Green),
|
||||||
|
Color::Dark(BaseColor::Black),
|
||||||
|
),
|
||||||
|
Level::Warn => ColorStyle::new(
|
||||||
|
Color::Light(BaseColor::Yellow),
|
||||||
|
Color::Dark(BaseColor::Black),
|
||||||
|
),
|
||||||
|
Level::Error => {
|
||||||
|
ColorStyle::new(Color::Light(BaseColor::Red), Color::Dark(BaseColor::Black))
|
||||||
|
}
|
||||||
|
_ => ColorStyle::new(
|
||||||
|
Color::Light(BaseColor::White),
|
||||||
|
Color::Dark(BaseColor::Black),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View for LogBufferView {
|
||||||
|
fn draw(&self, printer: &Printer) {
|
||||||
|
let mut i = 0;
|
||||||
|
for entry in self.buffer.iter().take(printer.size.y) {
|
||||||
|
printer.with_color(LogBufferView::color(entry.level), |p| {
|
||||||
|
let log_message = StyledString::plain(&entry.log);
|
||||||
|
let mut rows: Vec<Row> = LinesIterator::new(&log_message, printer.size.x).collect();
|
||||||
|
rows.reverse(); // So stack traces are in the right order.
|
||||||
|
for row in rows {
|
||||||
|
for span in row.resolve(&log_message) {
|
||||||
|
p.print((0, p.size.y - 1 - i), span.content);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,8 +25,8 @@ use cursive::views::{
|
||||||
use cursive::Cursive;
|
use cursive::Cursive;
|
||||||
|
|
||||||
use crate::tui::constants::{
|
use crate::tui::constants::{
|
||||||
MAIN_MENU, ROOT_STACK, SUBMENU_MINING_BUTTON, VIEW_BASIC_STATUS, VIEW_MINING, VIEW_PEER_SYNC,
|
MAIN_MENU, ROOT_STACK, SUBMENU_MINING_BUTTON, VIEW_BASIC_STATUS, VIEW_LOGS, VIEW_MINING,
|
||||||
VIEW_VERSION,
|
VIEW_PEER_SYNC, VIEW_VERSION,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn create() -> Box<dyn View> {
|
pub fn create() -> Box<dyn View> {
|
||||||
|
@ -38,6 +38,7 @@ pub fn create() -> Box<dyn View> {
|
||||||
.get_mut()
|
.get_mut()
|
||||||
.add_item("Peers and Sync", VIEW_PEER_SYNC);
|
.add_item("Peers and Sync", VIEW_PEER_SYNC);
|
||||||
main_menu.get_mut().add_item("Mining", VIEW_MINING);
|
main_menu.get_mut().add_item("Mining", VIEW_MINING);
|
||||||
|
main_menu.get_mut().add_item("Logs", VIEW_LOGS);
|
||||||
main_menu.get_mut().add_item("Version Info", VIEW_VERSION);
|
main_menu.get_mut().add_item("Version Info", VIEW_VERSION);
|
||||||
let change_view = |s: &mut Cursive, v: &&str| {
|
let change_view = |s: &mut Cursive, v: &&str| {
|
||||||
if *v == "" {
|
if *v == "" {
|
||||||
|
|
|
@ -17,6 +17,7 @@ use chrono;
|
||||||
use humansize;
|
use humansize;
|
||||||
//
|
//
|
||||||
mod constants;
|
mod constants;
|
||||||
|
mod logs;
|
||||||
mod menu;
|
mod menu;
|
||||||
mod mining;
|
mod mining;
|
||||||
mod peers;
|
mod peers;
|
||||||
|
|
|
@ -34,13 +34,15 @@ use crate::built_info;
|
||||||
use crate::servers::Server;
|
use crate::servers::Server;
|
||||||
use crate::tui::constants::ROOT_STACK;
|
use crate::tui::constants::ROOT_STACK;
|
||||||
use crate::tui::types::{TUIStatusListener, UIMessage};
|
use crate::tui::types::{TUIStatusListener, UIMessage};
|
||||||
use crate::tui::{menu, mining, peers, status, version};
|
use crate::tui::{logs, menu, mining, peers, status, version};
|
||||||
|
use grin_util::logger::LogEntry;
|
||||||
|
|
||||||
pub struct UI {
|
pub struct UI {
|
||||||
cursive: Cursive,
|
cursive: Cursive,
|
||||||
ui_rx: mpsc::Receiver<UIMessage>,
|
ui_rx: mpsc::Receiver<UIMessage>,
|
||||||
ui_tx: mpsc::Sender<UIMessage>,
|
ui_tx: mpsc::Sender<UIMessage>,
|
||||||
controller_tx: mpsc::Sender<ControllerMessage>,
|
controller_tx: mpsc::Sender<ControllerMessage>,
|
||||||
|
logs_rx: mpsc::Receiver<LogEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn modify_theme(theme: &mut Theme) {
|
fn modify_theme(theme: &mut Theme) {
|
||||||
|
@ -57,19 +59,25 @@ fn modify_theme(theme: &mut Theme) {
|
||||||
|
|
||||||
impl UI {
|
impl UI {
|
||||||
/// Create a new UI
|
/// Create a new UI
|
||||||
pub fn new(controller_tx: mpsc::Sender<ControllerMessage>) -> UI {
|
pub fn new(
|
||||||
|
controller_tx: mpsc::Sender<ControllerMessage>,
|
||||||
|
logs_rx: mpsc::Receiver<LogEntry>,
|
||||||
|
) -> UI {
|
||||||
let (ui_tx, ui_rx) = mpsc::channel::<UIMessage>();
|
let (ui_tx, ui_rx) = mpsc::channel::<UIMessage>();
|
||||||
|
|
||||||
let mut grin_ui = UI {
|
let mut grin_ui = UI {
|
||||||
cursive: Cursive::default(),
|
cursive: Cursive::default(),
|
||||||
ui_tx: ui_tx,
|
ui_tx,
|
||||||
ui_rx: ui_rx,
|
ui_rx,
|
||||||
controller_tx: controller_tx,
|
controller_tx,
|
||||||
|
logs_rx,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create UI objects, etc
|
// Create UI objects, etc
|
||||||
let status_view = status::TUIStatusView::create();
|
let status_view = status::TUIStatusView::create();
|
||||||
let mining_view = mining::TUIMiningView::create();
|
let mining_view = mining::TUIMiningView::create();
|
||||||
let peer_view = peers::TUIPeerView::create();
|
let peer_view = peers::TUIPeerView::create();
|
||||||
|
let logs_view = logs::TUILogsView::create();
|
||||||
let version_view = version::TUIVersionView::create();
|
let version_view = version::TUIVersionView::create();
|
||||||
|
|
||||||
let main_menu = menu::create();
|
let main_menu = menu::create();
|
||||||
|
@ -78,6 +86,7 @@ impl UI {
|
||||||
.layer(version_view)
|
.layer(version_view)
|
||||||
.layer(mining_view)
|
.layer(mining_view)
|
||||||
.layer(peer_view)
|
.layer(peer_view)
|
||||||
|
.layer(logs_view)
|
||||||
.layer(status_view)
|
.layer(status_view)
|
||||||
.with_id(ROOT_STACK)
|
.with_id(ROOT_STACK)
|
||||||
.full_height();
|
.full_height();
|
||||||
|
@ -128,6 +137,10 @@ impl UI {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while let Some(message) = self.logs_rx.try_iter().next() {
|
||||||
|
logs::TUILogsView::update(&mut self.cursive, message);
|
||||||
|
}
|
||||||
|
|
||||||
// Process any pending UI messages
|
// Process any pending UI messages
|
||||||
while let Some(message) = self.ui_rx.try_iter().next() {
|
while let Some(message) = self.ui_rx.try_iter().next() {
|
||||||
match message {
|
match message {
|
||||||
|
@ -162,13 +175,14 @@ pub enum ControllerMessage {
|
||||||
|
|
||||||
impl Controller {
|
impl Controller {
|
||||||
/// Create a new controller
|
/// Create a new controller
|
||||||
pub fn new() -> Result<Controller, String> {
|
pub fn new(logs_rx: mpsc::Receiver<LogEntry>) -> Result<Controller, String> {
|
||||||
let (tx, rx) = mpsc::channel::<ControllerMessage>();
|
let (tx, rx) = mpsc::channel::<ControllerMessage>();
|
||||||
Ok(Controller {
|
Ok(Controller {
|
||||||
rx: rx,
|
rx,
|
||||||
ui: UI::new(tx),
|
ui: UI::new(tx, logs_rx),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the controller
|
/// Run the controller
|
||||||
pub fn run(&mut self, server: Server) {
|
pub fn run(&mut self, server: Server) {
|
||||||
let stat_update_interval = 1;
|
let stat_update_interval = 1;
|
||||||
|
@ -177,6 +191,7 @@ impl Controller {
|
||||||
while let Some(message) = self.rx.try_iter().next() {
|
while let Some(message) = self.rx.try_iter().next() {
|
||||||
match message {
|
match message {
|
||||||
ControllerMessage::Shutdown => {
|
ControllerMessage::Shutdown => {
|
||||||
|
warn!("Shutdown in progress, please wait");
|
||||||
self.ui.stop();
|
self.ui.stop();
|
||||||
server.stop();
|
server.stop();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -43,7 +43,7 @@ pub mod secp_static;
|
||||||
pub use crate::secp_static::static_secp_instance;
|
pub use crate::secp_static::static_secp_instance;
|
||||||
|
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub use crate::types::{LogLevel, LoggingConfig, ZeroingString};
|
pub use crate::types::ZeroingString;
|
||||||
|
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,7 @@ use std::ops::Deref;
|
||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
use std::{panic, thread};
|
use std::{panic, thread};
|
||||||
|
|
||||||
use crate::types::{self, LogLevel, LoggingConfig};
|
use log::{Level, Record};
|
||||||
|
|
||||||
use log::{LevelFilter, Record};
|
|
||||||
use log4rs;
|
use log4rs;
|
||||||
use log4rs::append::console::ConsoleAppender;
|
use log4rs::append::console::ConsoleAppender;
|
||||||
use log4rs::append::file::FileAppender;
|
use log4rs::append::file::FileAppender;
|
||||||
|
@ -32,17 +30,12 @@ use log4rs::append::rolling_file::{
|
||||||
use log4rs::append::Append;
|
use log4rs::append::Append;
|
||||||
use log4rs::config::{Appender, Config, Root};
|
use log4rs::config::{Appender, Config, Root};
|
||||||
use log4rs::encode::pattern::PatternEncoder;
|
use log4rs::encode::pattern::PatternEncoder;
|
||||||
|
use log4rs::encode::writer::simple::SimpleWriter;
|
||||||
|
use log4rs::encode::Encode;
|
||||||
use log4rs::filter::{threshold::ThresholdFilter, Filter, Response};
|
use log4rs::filter::{threshold::ThresholdFilter, Filter, Response};
|
||||||
|
use std::error::Error;
|
||||||
fn convert_log_level(in_level: &LogLevel) -> LevelFilter {
|
use std::sync::mpsc;
|
||||||
match *in_level {
|
use std::sync::mpsc::SyncSender;
|
||||||
LogLevel::Info => LevelFilter::Info,
|
|
||||||
LogLevel::Warning => LevelFilter::Warn,
|
|
||||||
LogLevel::Debug => LevelFilter::Debug,
|
|
||||||
LogLevel::Trace => LevelFilter::Trace,
|
|
||||||
LogLevel::Error => LevelFilter::Error,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Flag to observe whether logging was explicitly initialised (don't output otherwise)
|
/// Flag to observe whether logging was explicitly initialised (don't output otherwise)
|
||||||
|
@ -56,6 +49,57 @@ lazy_static! {
|
||||||
|
|
||||||
const LOGGING_PATTERN: &str = "{d(%Y%m%d %H:%M:%S%.3f)} {h({l})} {M} - {m}{n}";
|
const LOGGING_PATTERN: &str = "{d(%Y%m%d %H:%M:%S%.3f)} {h({l})} {M} - {m}{n}";
|
||||||
|
|
||||||
|
/// 32 log files to rotate over by default
|
||||||
|
const DEFAULT_ROTATE_LOG_FILES: u32 = 32 as u32;
|
||||||
|
|
||||||
|
/// Log Entry
|
||||||
|
#[derive(Clone, Serialize, Debug)]
|
||||||
|
pub struct LogEntry {
|
||||||
|
/// The log message
|
||||||
|
pub log: String,
|
||||||
|
/// The log levelO
|
||||||
|
pub level: Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logging config
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct LoggingConfig {
|
||||||
|
/// whether to log to stdout
|
||||||
|
pub log_to_stdout: bool,
|
||||||
|
/// logging level for stdout
|
||||||
|
pub stdout_log_level: Level,
|
||||||
|
/// whether to log to file
|
||||||
|
pub log_to_file: bool,
|
||||||
|
/// log file level
|
||||||
|
pub file_log_level: Level,
|
||||||
|
/// Log file path
|
||||||
|
pub log_file_path: String,
|
||||||
|
/// Whether to append to log or replace
|
||||||
|
pub log_file_append: bool,
|
||||||
|
/// Size of the log in bytes to rotate over (optional)
|
||||||
|
pub log_max_size: Option<u64>,
|
||||||
|
/// Number of the log files to rotate over (optional)
|
||||||
|
pub log_max_files: Option<u32>,
|
||||||
|
/// Whether the tui is running (optional)
|
||||||
|
pub tui_running: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LoggingConfig {
|
||||||
|
fn default() -> LoggingConfig {
|
||||||
|
LoggingConfig {
|
||||||
|
log_to_stdout: true,
|
||||||
|
stdout_log_level: Level::Warn,
|
||||||
|
log_to_file: true,
|
||||||
|
file_log_level: Level::Info,
|
||||||
|
log_file_path: String::from("grin.log"),
|
||||||
|
log_file_append: true,
|
||||||
|
log_max_size: Some(1024 * 1024 * 16), // 16 megabytes default
|
||||||
|
log_max_files: Some(DEFAULT_ROTATE_LOG_FILES),
|
||||||
|
tui_running: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This filter is rejecting messages that doesn't start with "grin"
|
/// This filter is rejecting messages that doesn't start with "grin"
|
||||||
/// in order to save log space for only Grin-related records
|
/// in order to save log space for only Grin-related records
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -73,8 +117,32 @@ impl Filter for GrinFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ChannelAppender {
|
||||||
|
output: Mutex<SyncSender<LogEntry>>,
|
||||||
|
encoder: Box<dyn Encode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Append for ChannelAppender {
|
||||||
|
fn append(&self, record: &Record) -> Result<(), Box<dyn Error + Sync + Send>> {
|
||||||
|
let mut writer = SimpleWriter(Vec::new());
|
||||||
|
self.encoder.encode(&mut writer, record)?;
|
||||||
|
|
||||||
|
let log = String::from_utf8_lossy(writer.0.as_slice()).to_string();
|
||||||
|
|
||||||
|
let _ = self.output.lock().try_send(LogEntry {
|
||||||
|
log,
|
||||||
|
level: record.level(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize the logger with the given configuration
|
/// Initialize the logger with the given configuration
|
||||||
pub fn init_logger(config: Option<LoggingConfig>) {
|
pub fn init_logger(config: Option<LoggingConfig>, logs_tx: Option<mpsc::SyncSender<LogEntry>>) {
|
||||||
if let Some(c) = config {
|
if let Some(c) = config {
|
||||||
let tui_running = c.tui_running.unwrap_or(false);
|
let tui_running = c.tui_running.unwrap_or(false);
|
||||||
if tui_running {
|
if tui_running {
|
||||||
|
@ -86,8 +154,8 @@ pub fn init_logger(config: Option<LoggingConfig>) {
|
||||||
let mut config_ref = LOGGING_CONFIG.lock();
|
let mut config_ref = LOGGING_CONFIG.lock();
|
||||||
*config_ref = c.clone();
|
*config_ref = c.clone();
|
||||||
|
|
||||||
let level_stdout = convert_log_level(&c.stdout_log_level);
|
let level_stdout = c.stdout_log_level.to_level_filter();
|
||||||
let level_file = convert_log_level(&c.file_log_level);
|
let level_file = c.file_log_level.to_level_filter();
|
||||||
|
|
||||||
// Determine minimum logging level for Root logger
|
// Determine minimum logging level for Root logger
|
||||||
let level_minimum = if level_stdout > level_file {
|
let level_minimum = if level_stdout > level_file {
|
||||||
|
@ -105,15 +173,26 @@ pub fn init_logger(config: Option<LoggingConfig>) {
|
||||||
|
|
||||||
let mut appenders = vec![];
|
let mut appenders = vec![];
|
||||||
|
|
||||||
if c.log_to_stdout && !tui_running {
|
if tui_running {
|
||||||
let filter = Box::new(ThresholdFilter::new(level_stdout));
|
let channel_appender = ChannelAppender {
|
||||||
|
encoder: Box::new(PatternEncoder::new(&LOGGING_PATTERN)),
|
||||||
|
output: Mutex::new(logs_tx.unwrap()),
|
||||||
|
};
|
||||||
|
|
||||||
appenders.push(
|
appenders.push(
|
||||||
Appender::builder()
|
Appender::builder()
|
||||||
.filter(filter)
|
.filter(Box::new(ThresholdFilter::new(level_stdout)))
|
||||||
|
.filter(Box::new(GrinFilter))
|
||||||
|
.build("tui", Box::new(channel_appender)),
|
||||||
|
);
|
||||||
|
root = root.appender("tui");
|
||||||
|
} else if c.log_to_stdout {
|
||||||
|
appenders.push(
|
||||||
|
Appender::builder()
|
||||||
|
.filter(Box::new(ThresholdFilter::new(level_stdout)))
|
||||||
.filter(Box::new(GrinFilter))
|
.filter(Box::new(GrinFilter))
|
||||||
.build("stdout", Box::new(stdout)),
|
.build("stdout", Box::new(stdout)),
|
||||||
);
|
);
|
||||||
|
|
||||||
root = root.appender("stdout");
|
root = root.appender("stdout");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,9 +202,7 @@ pub fn init_logger(config: Option<LoggingConfig>) {
|
||||||
let filter = Box::new(ThresholdFilter::new(level_file));
|
let filter = Box::new(ThresholdFilter::new(level_file));
|
||||||
let file: Box<dyn Append> = {
|
let file: Box<dyn Append> = {
|
||||||
if let Some(size) = c.log_max_size {
|
if let Some(size) = c.log_max_size {
|
||||||
let count = c
|
let count = c.log_max_files.unwrap_or_else(|| DEFAULT_ROTATE_LOG_FILES);
|
||||||
.log_max_files
|
|
||||||
.unwrap_or_else(|| types::DEFAULT_ROTATE_LOG_FILES);
|
|
||||||
let roller = FixedWindowRoller::builder()
|
let roller = FixedWindowRoller::builder()
|
||||||
.build(&format!("{}.{{}}.gz", c.log_file_path), count)
|
.build(&format!("{}.{{}}.gz", c.log_file_path), count)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -188,13 +265,13 @@ pub fn init_test_logger() {
|
||||||
}
|
}
|
||||||
let mut logger = LoggingConfig::default();
|
let mut logger = LoggingConfig::default();
|
||||||
logger.log_to_file = false;
|
logger.log_to_file = false;
|
||||||
logger.stdout_log_level = LogLevel::Debug;
|
logger.stdout_log_level = Level::Debug;
|
||||||
|
|
||||||
// Save current logging configuration
|
// Save current logging configuration
|
||||||
let mut config_ref = LOGGING_CONFIG.lock();
|
let mut config_ref = LOGGING_CONFIG.lock();
|
||||||
*config_ref = logger;
|
*config_ref = logger;
|
||||||
|
|
||||||
let level_stdout = convert_log_level(&config_ref.stdout_log_level);
|
let level_stdout = config_ref.stdout_log_level.to_level_filter();
|
||||||
let level_minimum = level_stdout; // minimum logging level for Root logger
|
let level_minimum = level_stdout; // minimum logging level for Root logger
|
||||||
|
|
||||||
// Start logger
|
// Start logger
|
||||||
|
|
|
@ -12,64 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//! Logging configuration types
|
//! Zeroing String
|
||||||
|
|
||||||
/// Log level types
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub enum LogLevel {
|
|
||||||
/// Error
|
|
||||||
Error,
|
|
||||||
/// Warning
|
|
||||||
Warning,
|
|
||||||
/// Info
|
|
||||||
Info,
|
|
||||||
/// Debug
|
|
||||||
Debug,
|
|
||||||
/// Trace
|
|
||||||
Trace,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 32 log files to rotate over by default
|
|
||||||
pub const DEFAULT_ROTATE_LOG_FILES: u32 = 32 as u32;
|
|
||||||
|
|
||||||
/// Logging config
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub struct LoggingConfig {
|
|
||||||
/// whether to log to stdout
|
|
||||||
pub log_to_stdout: bool,
|
|
||||||
/// logging level for stdout
|
|
||||||
pub stdout_log_level: LogLevel,
|
|
||||||
/// whether to log to file
|
|
||||||
pub log_to_file: bool,
|
|
||||||
/// log file level
|
|
||||||
pub file_log_level: LogLevel,
|
|
||||||
/// Log file path
|
|
||||||
pub log_file_path: String,
|
|
||||||
/// Whether to append to log or replace
|
|
||||||
pub log_file_append: bool,
|
|
||||||
/// Size of the log in bytes to rotate over (optional)
|
|
||||||
pub log_max_size: Option<u64>,
|
|
||||||
/// Number of the log files to rotate over (optional)
|
|
||||||
pub log_max_files: Option<u32>,
|
|
||||||
/// Whether the tui is running (optional)
|
|
||||||
pub tui_running: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for LoggingConfig {
|
|
||||||
fn default() -> LoggingConfig {
|
|
||||||
LoggingConfig {
|
|
||||||
log_to_stdout: true,
|
|
||||||
stdout_log_level: LogLevel::Warning,
|
|
||||||
log_to_file: true,
|
|
||||||
file_log_level: LogLevel::Info,
|
|
||||||
log_file_path: String::from("grin.log"),
|
|
||||||
log_file_append: true,
|
|
||||||
log_max_size: Some(1024 * 1024 * 16), // 16 megabytes default
|
|
||||||
log_max_files: Some(DEFAULT_ROTATE_LOG_FILES),
|
|
||||||
tui_running: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
Loading…
Reference in a new issue