Skip to content

Commit

Permalink
feat: TUI logs view (#3064)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
JosephGoulden authored and quentinlesceller committed Nov 13, 2019
1 parent 38e6497 commit 8ce2bfd
Show file tree
Hide file tree
Showing 15 changed files with 313 additions and 152 deletions.
3 changes: 1 addition & 2 deletions config/src/comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,7 @@ fn comments() -> HashMap<String, String> {
retval.insert(
"run_tui".to_string(),
"
#whether to run the ncurses TUI. Ncurses must be installed and this
#will also disable logging to stdout
#whether to run the ncurses TUI (Ncurses must be installed)
"
.to_string(),
);
Expand Down
2 changes: 1 addition & 1 deletion config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::core::global;
use crate::p2p;
use crate::servers::ServerConfig;
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 node config file location
Expand Down
2 changes: 1 addition & 1 deletion config/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::io;
use std::path::PathBuf;

use crate::servers::ServerConfig;
use crate::util::LoggingConfig;
use crate::util::logger::LoggingConfig;

/// Error type wrapping config errors.
#[derive(Debug)]
Expand Down
2 changes: 1 addition & 1 deletion servers/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
//! Modules common to all Grin server types
pub mod adapters;
pub mod hooks;
pub mod stats;
pub mod types;
pub mod hooks;
16 changes: 11 additions & 5 deletions servers/src/grin/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use std::fs;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::sync::Arc;
use std::sync::{mpsc, Arc};
use std::{
thread::{self, JoinHandle},
time,
Expand Down Expand Up @@ -52,6 +52,7 @@ use crate::p2p::types::PeerAddr;
use crate::pool;
use crate::util::file::get_first_line;
use crate::util::{RwLock, StopState};
use grin_util::logger::LogEntry;

/// Grin server holding internal structures.
pub struct Server {
Expand Down Expand Up @@ -83,9 +84,13 @@ impl Server {
/// Instantiates and starts a new server. Optionally takes a callback
/// for the server to send an ARC copy of itself, to allow another process
/// 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
F: FnMut(Server),
F: FnMut(Server, Option<mpsc::Receiver<LogEntry>>),
{
let mining_config = config.stratum_mining_config.clone();
let enable_test_miner = config.run_test_miner;
Expand All @@ -111,7 +116,7 @@ impl Server {
}
}

info_callback(serv);
info_callback(serv, logs_rx);
Ok(())
}

Expand Down Expand Up @@ -555,9 +560,10 @@ impl Server {
}
}
// 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();
let _ = self.lock_file.unlock();
warn!("Shutdown complete");
}

/// Pause the p2p server.
Expand Down
62 changes: 35 additions & 27 deletions src/bin/cmd/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,46 +27,53 @@ use crate::core::global;
use crate::p2p::{PeerAddr, Seeding};
use crate::servers;
use crate::tui::ui;
use grin_util::logger::LogEntry;
use std::sync::mpsc;

/// wrap below to allow UI to clean up on stop
pub fn start_server(config: servers::ServerConfig) {
start_server_tui(config);
pub fn start_server(config: servers::ServerConfig, logs_rx: Option<mpsc::Receiver<LogEntry>>) {
start_server_tui(config, logs_rx);
// Just kill process for now, otherwise the process
// hangs around until sigint because the API server
// currently has no shutdown facility
warn!("Shutting down...");
thread::sleep(Duration::from_millis(1000));
warn!("Shutdown complete.");
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
// everything it might need
if config.run_tui.unwrap_or(false) {
warn!("Starting GRIN in UI mode...");
servers::Server::start(config, |serv: servers::Server| {
let mut controller = ui::Controller::new().unwrap_or_else(|e| {
panic!("Error loading UI controller: {}", e);
});
controller.run(serv);
})
servers::Server::start(
config,
logs_rx,
|serv: servers::Server, logs_rx: Option<mpsc::Receiver<LogEntry>>| {
let mut controller = ui::Controller::new(logs_rx.unwrap()).unwrap_or_else(|e| {
panic!("Error loading UI controller: {}", e);
});
controller.run(serv);
},
)
.unwrap();
} else {
warn!("Starting GRIN w/o UI...");
servers::Server::start(config, |serv: servers::Server| {
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
})
.expect("Error setting handler for both SIGINT (Ctrl+C) and SIGTERM (kill)");
while running.load(Ordering::SeqCst) {
thread::sleep(Duration::from_secs(1));
}
warn!("Received SIGINT (Ctrl+C) or SIGTERM (kill).");
serv.stop();
})
servers::Server::start(
config,
logs_rx,
|serv: servers::Server, _: Option<mpsc::Receiver<LogEntry>>| {
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
})
.expect("Error setting handler for both SIGINT (Ctrl+C) and SIGTERM (kill)");
while running.load(Ordering::SeqCst) {
thread::sleep(Duration::from_secs(1));
}
warn!("Received SIGINT (Ctrl+C) or SIGTERM (kill).");
serv.stop();
},
)
.unwrap();
}
}
Expand All @@ -78,6 +85,7 @@ fn start_server_tui(config: servers::ServerConfig) {
pub fn server_command(
server_args: Option<&ArgMatches<'_>>,
mut global_config: GlobalConfig,
logs_rx: Option<mpsc::Receiver<LogEntry>>,
) -> i32 {
global::set_mining_mode(
global_config
Expand Down Expand Up @@ -123,7 +131,7 @@ pub fn server_command(
if let Some(a) = server_args {
match a.subcommand() {
("run", _) => {
start_server(server_config);
start_server(server_config, logs_rx);
}
("", _) => {
println!("Subcommand required, use 'grin help server' for details");
Expand All @@ -137,7 +145,7 @@ pub fn server_command(
}
}
} else {
start_server(server_config);
start_server(server_config, logs_rx);
}
0
}
46 changes: 25 additions & 21 deletions src/bin/grin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ use grin_core as core;
use grin_p2p as p2p;
use grin_servers as servers;
use grin_util as util;
use grin_util::logger::LogEntry;
use std::sync::mpsc;

mod cmd;
pub mod tui;
Expand Down Expand Up @@ -136,34 +138,36 @@ fn real_main() -> i32 {
}
}

if let Some(mut config) = node_config.clone() {
let mut l = config.members.as_mut().unwrap().logging.clone().unwrap();
let run_tui = 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));
let mut config = node_config.clone().unwrap();
let mut logging_config = config.members.as_mut().unwrap().logging.clone().unwrap();
logging_config.tui_running = config.members.as_mut().unwrap().server.run_tui;

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 {
info!(
"Using configuration file at {}",
file_path.to_str().unwrap()
);
} else {
info!("Node configuration file not found, using default");
}
}
global::set_mining_mode(config.members.unwrap().server.clone().chain_type);

if let Some(file_path) = &config.config_file_path {
info!(
"Using configuration file at {}",
file_path.to_str().unwrap()
);
} else {
info!("Node configuration file not found, using default");
};

log_build_info();

// Execute subcommand
match args.subcommand() {
// server commands and options
("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
Expand All @@ -177,11 +181,11 @@ fn real_main() -> i32 {
Ok(_) => 0,
Err(_) => 1,
}
},
}

// If nothing is specified, try to just use the config file instead
// this could possibly become the way to configure most things
// with most command line options being phased out
_ => cmd::server_command(None, node_config.unwrap()),
_ => cmd::server_command(None, node_config.unwrap(), logs_rx),
}
}
3 changes: 3 additions & 0 deletions src/bin/tui/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_DIFF_STATUS: &str = "mining_diff_status_table";

// Logs View
pub const VIEW_LOGS: &str = "logs_view";

// Mining View
pub const VIEW_VERSION: &str = "version_view";

Expand Down
104 changes: 104 additions & 0 deletions src/bin/tui/logs.rs
Original file line number Diff line number Diff line change
@@ -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;
}
}
});
}
}
}
Loading

0 comments on commit 8ce2bfd

Please sign in to comment.