From 08069704c1373d91bc28fe79ff23e86a7d632f6b Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Wed, 10 Jul 2019 10:27:44 +1000 Subject: [PATCH] Add cli flag for logging to JSON file --- account_manager/src/main.rs | 24 +++++++-------- beacon_node/client/Cargo.toml | 3 +- beacon_node/client/src/config.rs | 51 +++++++++++++++++++++++++++++-- beacon_node/src/main.rs | 11 +++++-- validator_client/Cargo.toml | 3 +- validator_client/src/config.rs | 52 +++++++++++++++++++++++++++++--- validator_client/src/main.rs | 11 +++++-- 7 files changed, 129 insertions(+), 26 deletions(-) diff --git a/account_manager/src/main.rs b/account_manager/src/main.rs index 68c304ec5..3c55c39e2 100644 --- a/account_manager/src/main.rs +++ b/account_manager/src/main.rs @@ -14,13 +14,20 @@ fn main() { let decorator = slog_term::TermDecorator::new().build(); let drain = slog_term::CompactFormat::new(decorator).build().fuse(); let drain = slog_async::Async::new(drain).build().fuse(); - let log = slog::Logger::root(drain, o!()); + let mut log = slog::Logger::root(drain, o!()); // CLI let matches = App::new("Lighthouse Accounts Manager") .version("0.0.1") .author("Sigma Prime ") .about("Eth 2.0 Accounts Manager") + .arg( + Arg::with_name("logfile") + .long("logfile") + .value_name("logfile") + .help("File path where output will be written.") + .takes_value(true), + ) .arg( Arg::with_name("datadir") .long("datadir") @@ -91,21 +98,12 @@ fn main() { let mut client_config = ValidatorClientConfig::default(); - if let Err(e) = client_config.apply_cli_args(&matches) { - crit!(log, "Failed to apply CLI args"; "error" => format!("{:?}", e)); - return; - }; - // Ensure the `data_dir` in the config matches that supplied to the CLI. client_config.data_dir = data_dir.clone(); - // Update the client config with any CLI args. - match client_config.apply_cli_args(&matches) { - Ok(()) => (), - Err(s) => { - crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => s); - return; - } + if let Err(e) = client_config.apply_cli_args(&matches, &mut log) { + crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => format!("{:?}", e)); + return; }; // Log configuration diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 94a529ea7..d3b1e6294 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -21,8 +21,9 @@ serde_derive = "1.0" error-chain = "0.12.0" eth2_ssz = { path = "../../eth2/utils/ssz" } slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_debug"] } -slog-term = "^2.4.0" slog-async = "^2.3.0" +slog-json = "^2.3" +slog-term = "^2.4.0" tokio = "0.1.15" clap = "2.32.0" dirs = "1.0.3" diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 415ef0ec9..864577559 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -2,8 +2,10 @@ use clap::ArgMatches; use http_server::HttpServerConfig; use network::NetworkConfig; use serde_derive::{Deserialize, Serialize}; -use std::fs; +use slog::{info, o, Drain}; +use std::fs::{self, OpenOptions}; use std::path::PathBuf; +use std::sync::Mutex; /// The core configuration of a Lighthouse beacon node. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -11,6 +13,7 @@ pub struct Config { pub data_dir: PathBuf, pub db_type: String, db_name: String, + pub log_file: PathBuf, pub network: network::NetworkConfig, pub rpc: rpc::RPCConfig, pub http: HttpServerConfig, @@ -20,6 +23,7 @@ impl Default for Config { fn default() -> Self { Self { data_dir: PathBuf::from(".lighthouse"), + log_file: PathBuf::from(""), db_type: "disk".to_string(), db_name: "chain_db".to_string(), // Note: there are no default bootnodes specified. @@ -45,23 +49,64 @@ impl Config { Some(path) } + // Update the logger to output in JSON to specified file + fn update_logger(&mut self, log: &mut slog::Logger) -> Result<(), &'static str> { + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&self.log_file); + + if file.is_err() { + return Err("Cannot open log file"); + } + let file = file.unwrap(); + + if let Some(file) = self.log_file.to_str() { + info!( + *log, + "Log file specified, output will now be written to {} in json.", file + ); + } else { + info!( + *log, + "Log file specified output will now be written in json" + ); + } + + let drain = Mutex::new(slog_json::Json::default(file)).fuse(); + let drain = slog_async::Async::new(drain).build().fuse(); + *log = slog::Logger::root(drain, o!()); + + Ok(()) + } + /// Apply the following arguments to `self`, replacing values if they are specified in `args`. /// /// Returns an error if arguments are obviously invalid. May succeed even if some values are /// invalid. - pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), String> { + pub fn apply_cli_args( + &mut self, + args: &ArgMatches, + log: &mut slog::Logger, + ) -> Result<(), String> { if let Some(dir) = args.value_of("datadir") { self.data_dir = PathBuf::from(dir); }; if let Some(dir) = args.value_of("db") { self.db_type = dir.to_string(); - } + }; self.network.apply_cli_args(args)?; self.rpc.apply_cli_args(args)?; self.http.apply_cli_args(args)?; + if let Some(log_file) = args.value_of("logfile") { + self.log_file = PathBuf::from(log_file); + self.update_logger(log)?; + }; + Ok(()) } } diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 791feae54..6beb0fd64 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -29,6 +29,13 @@ fn main() { .help("Data directory for keys and databases.") .takes_value(true) ) + .arg( + Arg::with_name("logfile") + .long("logfile") + .value_name("logfile") + .help("File path where output will be written.") + .takes_value(true), + ) // network related arguments .arg( Arg::with_name("listen-address") @@ -159,7 +166,7 @@ fn main() { _ => drain.filter_level(Level::Info), }; - let log = slog::Logger::root(drain.fuse(), o!()); + let mut log = slog::Logger::root(drain.fuse(), o!()); let data_dir = match matches .value_of("datadir") @@ -214,7 +221,7 @@ fn main() { client_config.data_dir = data_dir.clone(); // Update the client config with any CLI args. - match client_config.apply_cli_args(&matches) { + match client_config.apply_cli_args(&matches, &mut log) { Ok(()) => (), Err(s) => { crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => s); diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 1972f870c..d2824bc2f 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -26,8 +26,9 @@ types = { path = "../eth2/types" } serde = "1.0" serde_derive = "1.0" slog = "^2.2.3" -slog-term = "^2.4.0" slog-async = "^2.3.0" +slog-json = "^2.3" +slog-term = "^2.4.0" tokio = "0.1.18" tokio-timer = "0.2.10" toml = "^0.5" diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index d7664c161..7bc504b23 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -2,11 +2,11 @@ use bincode; use bls::Keypair; use clap::ArgMatches; use serde_derive::{Deserialize, Serialize}; -use slog::{debug, error, info}; -use std::fs; -use std::fs::File; +use slog::{debug, error, info, o, Drain}; +use std::fs::{self, File, OpenOptions}; use std::io::{Error, ErrorKind}; use std::path::PathBuf; +use std::sync::Mutex; use types::{EthSpec, MainnetEthSpec}; /// Stores the core configuration for this validator instance. @@ -14,6 +14,8 @@ use types::{EthSpec, MainnetEthSpec}; pub struct Config { /// The data directory, which stores all validator databases pub data_dir: PathBuf, + /// The path where the logs will be outputted + pub log_file: PathBuf, /// The server at which the Beacon Node can be contacted pub server: String, /// The number of slots per epoch. @@ -27,6 +29,7 @@ impl Default for Config { fn default() -> Self { Self { data_dir: PathBuf::from(".lighthouse-validator"), + log_file: PathBuf::from(""), server: "localhost:5051".to_string(), slots_per_epoch: MainnetEthSpec::slots_per_epoch(), } @@ -38,11 +41,20 @@ impl Config { /// /// Returns an error if arguments are obviously invalid. May succeed even if some values are /// invalid. - pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> { + pub fn apply_cli_args( + &mut self, + args: &ArgMatches, + log: &mut slog::Logger, + ) -> Result<(), &'static str> { if let Some(datadir) = args.value_of("datadir") { self.data_dir = PathBuf::from(datadir); }; + if let Some(log_file) = args.value_of("logfile") { + self.log_file = PathBuf::from(log_file); + self.update_logger(log)?; + }; + if let Some(srv) = args.value_of("server") { self.server = srv.to_string(); }; @@ -50,6 +62,38 @@ impl Config { Ok(()) } + // Update the logger to output in JSON to specified file + fn update_logger(&mut self, log: &mut slog::Logger) -> Result<(), &'static str> { + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&self.log_file); + + if file.is_err() { + return Err("Cannot open log file"); + } + let file = file.unwrap(); + + if let Some(file) = self.log_file.to_str() { + info!( + *log, + "Log file specified, output will now be written to {} in json.", file + ); + } else { + info!( + *log, + "Log file specified output will now be written in json" + ); + } + + let drain = Mutex::new(slog_json::Json::default(file)).fuse(); + let drain = slog_async::Async::new(drain).build().fuse(); + *log = slog::Logger::root(drain, o!()); + + Ok(()) + } + /// Try to load keys from validator_dir, returning None if none are found or an error. #[allow(dead_code)] pub fn fetch_keys(&self, log: &slog::Logger) -> Option> { diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 5beea4c38..c12cae6a2 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -26,7 +26,7 @@ fn main() { let decorator = slog_term::TermDecorator::new().build(); let drain = slog_term::CompactFormat::new(decorator).build().fuse(); let drain = slog_async::Async::new(drain).build().fuse(); - let log = slog::Logger::root(drain, o!()); + let mut log = slog::Logger::root(drain, o!()); // CLI let matches = App::new("Lighthouse Validator Client") @@ -41,6 +41,13 @@ fn main() { .help("Data directory for keys and databases.") .takes_value(true), ) + .arg( + Arg::with_name("logfile") + .long("logfile") + .value_name("logfile") + .help("File path where output will be written.") + .takes_value(true), + ) .arg( Arg::with_name("eth2-spec") .long("eth2-spec") @@ -123,7 +130,7 @@ fn main() { client_config.data_dir = data_dir.clone(); // Update the client config with any CLI args. - match client_config.apply_cli_args(&matches) { + match client_config.apply_cli_args(&matches, &mut log) { Ok(()) => (), Err(s) => { crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => s);