diff --git a/miner/src/transaction_queue.rs b/miner/src/transaction_queue.rs index ce09b2078..e9f2570e3 100644 --- a/miner/src/transaction_queue.rs +++ b/miner/src/transaction_queue.rs @@ -712,10 +712,10 @@ impl TransactionQueue { } fn check_too_cheap(is_in: bool) -> Result<(), TransactionError> { - if !is_in { - Err(TransactionError::TooCheapToReplace) - } else { + if is_in { Ok(()) + } else { + Err(TransactionError::TooCheapToReplace) } } diff --git a/parity/main.rs b/parity/main.rs index 8eb67a46f..dff7ceaeb 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -281,30 +281,40 @@ struct Args { flag_networkid: Option, } -fn setup_log(init: &Option) { +fn setup_log(init: &Option) -> Arc { use rlog::*; + let mut levels = String::new(); let mut builder = LogBuilder::new(); builder.filter(None, LogLevelFilter::Info); if env::var("RUST_LOG").is_ok() { - builder.parse(&env::var("RUST_LOG").unwrap()); + let lvl = &env::var("RUST_LOG").unwrap(); + levels.push_str(&lvl); + levels.push_str(","); + builder.parse(lvl); } if let Some(ref s) = *init { + levels.push_str(s); builder.parse(s); } - let format = |record: &LogRecord| { + let logs = Arc::new(RotatingLogger::new(levels)); + let log2 = logs.clone(); + let format = move |record: &LogRecord| { let timestamp = time::strftime("%Y-%m-%d %H:%M:%S %Z", &time::now()).unwrap(); - if max_log_level() <= LogLevelFilter::Info { + let format = if max_log_level() <= LogLevelFilter::Info { format!("{}{}", timestamp, record.args()) } else { format!("{}{}:{}: {}", timestamp, record.level(), record.target(), record.args()) - } + }; + log2.append(format.clone()); + format }; builder.format(format); builder.init().unwrap(); + logs } #[cfg(feature = "rpc")] @@ -316,6 +326,7 @@ fn setup_rpc_server( url: &SocketAddr, cors_domain: Option, apis: Vec<&str>, + logger: Arc, ) -> RpcServer { use rpc::v1::*; @@ -329,7 +340,7 @@ fn setup_rpc_server( server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate()); }, "personal" => server.add_delegate(PersonalClient::new(&secret_store).to_delegate()), - "ethcore" => server.add_delegate(EthcoreClient::new(&miner).to_delegate()), + "ethcore" => server.add_delegate(EthcoreClient::new(&miner, logger.clone()).to_delegate()), _ => { die!("{}: Invalid API name to be enabled.", api); }, @@ -351,6 +362,7 @@ fn setup_webapp_server( miner: Arc, url: &SocketAddr, auth: Option<(String, String)>, + logger: Arc, ) -> WebappServer { use rpc::v1::*; @@ -360,7 +372,7 @@ fn setup_webapp_server( server.add_delegate(EthClient::new(&client, &sync, &secret_store, &miner).to_delegate()); server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate()); server.add_delegate(PersonalClient::new(&secret_store).to_delegate()); - server.add_delegate(EthcoreClient::new(&miner).to_delegate()); + server.add_delegate(EthcoreClient::new(&miner, logger).to_delegate()); let start_result = match auth { None => { server.start_unsecure_http(url) @@ -389,6 +401,7 @@ fn setup_rpc_server( _url: &SocketAddr, _cors_domain: Option, _apis: Vec<&str>, + _logger: Arc, ) -> ! { die!("Your Parity version has been compiled without JSON-RPC support.") } @@ -404,6 +417,7 @@ fn setup_webapp_server( _miner: Arc, _url: &SocketAddr, _auth: Option<(String, String)>, + _logger: Arc, ) -> ! { die!("Your Parity version has been compiled without WebApps support.") } @@ -690,7 +704,7 @@ impl Configuration { let panic_handler = PanicHandler::new_in_arc(); // Setup logging - setup_log(&self.args.flag_logging); + let logger = setup_log(&self.args.flag_logging); // Raise fdlimit unsafe { ::fdlimit::raise_fd_limit(); } @@ -742,7 +756,8 @@ impl Configuration { miner.clone(), &addr, cors_domain, - apis.split(',').collect() + apis.split(',').collect(), + logger.clone(), )) } else { None @@ -776,6 +791,7 @@ impl Configuration { miner.clone(), &addr, auth, + logger.clone(), )) } else { None diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index 2d1267a02..533aad80e 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -15,8 +15,9 @@ // along with Parity. If not, see . //! Ethcore-specific rpc implementation. -use util::{U256, Address}; +use util::{U256, Address, RotatingLogger}; use std::sync::{Arc, Weak}; +use std::ops::Deref; use jsonrpc_core::*; use ethminer::{MinerService}; use v1::traits::Ethcore; @@ -26,13 +27,15 @@ use v1::types::Bytes; pub struct EthcoreClient where M: MinerService { miner: Weak, + logger: Arc, } impl EthcoreClient where M: MinerService { /// Creates new `EthcoreClient`. - pub fn new(miner: &Arc) -> Self { + pub fn new(miner: &Arc, logger: Arc) -> Self { EthcoreClient { - miner: Arc::downgrade(miner) + miner: Arc::downgrade(miner), + logger: logger, } } } @@ -89,4 +92,14 @@ impl Ethcore for EthcoreClient where M: MinerService + 'static { fn gas_floor_target(&self, _: Params) -> Result { to_value(&take_weak!(self.miner).gas_floor_target()) } + + fn dev_logs(&self, _params: Params) -> Result { + let logs = self.logger.logs(); + to_value(&logs.deref().as_slice()) + } + + fn dev_logs_levels(&self, _params: Params) -> Result { + to_value(&self.logger.levels()) + } + } diff --git a/rpc/src/v1/tests/ethcore.rs b/rpc/src/v1/tests/ethcore.rs index bef3180cf..43a131bab 100644 --- a/rpc/src/v1/tests/ethcore.rs +++ b/rpc/src/v1/tests/ethcore.rs @@ -22,16 +22,20 @@ use ethminer::MinerService; use v1::tests::helpers::TestMinerService; use util::numbers::*; use rustc_serialize::hex::FromHex; +use util::log::RotatingLogger; fn miner_service() -> Arc { Arc::new(TestMinerService::default()) } +fn logger() -> Arc { + Arc::new(RotatingLogger::new("rpc=trace".to_owned())) +} #[test] fn rpc_ethcore_extra_data() { let miner = miner_service(); - let ethcore = EthcoreClient::new(&miner).to_delegate(); + let ethcore = EthcoreClient::new(&miner, logger()).to_delegate(); let io = IoHandler::new(); io.add_delegate(ethcore); @@ -45,7 +49,7 @@ fn rpc_ethcore_extra_data() { #[test] fn rpc_ethcore_gas_floor_target() { let miner = miner_service(); - let ethcore = EthcoreClient::new(&miner).to_delegate(); + let ethcore = EthcoreClient::new(&miner, logger()).to_delegate(); let io = IoHandler::new(); io.add_delegate(ethcore); @@ -58,7 +62,7 @@ fn rpc_ethcore_gas_floor_target() { #[test] fn rpc_ethcore_min_gas_price() { let miner = miner_service(); - let ethcore = EthcoreClient::new(&miner).to_delegate(); + let ethcore = EthcoreClient::new(&miner, logger()).to_delegate(); let io = IoHandler::new(); io.add_delegate(ethcore); @@ -71,7 +75,7 @@ fn rpc_ethcore_min_gas_price() { #[test] fn rpc_ethcore_set_min_gas_price() { let miner = miner_service(); - let ethcore = EthcoreClient::new(&miner).to_delegate(); + let ethcore = EthcoreClient::new(&miner, logger()).to_delegate(); let io = IoHandler::new(); io.add_delegate(ethcore); @@ -85,7 +89,7 @@ fn rpc_ethcore_set_min_gas_price() { #[test] fn rpc_ethcore_set_gas_floor_target() { let miner = miner_service(); - let ethcore = EthcoreClient::new(&miner).to_delegate(); + let ethcore = EthcoreClient::new(&miner, logger()).to_delegate(); let io = IoHandler::new(); io.add_delegate(ethcore); @@ -99,7 +103,7 @@ fn rpc_ethcore_set_gas_floor_target() { #[test] fn rpc_ethcore_set_extra_data() { let miner = miner_service(); - let ethcore = EthcoreClient::new(&miner).to_delegate(); + let ethcore = EthcoreClient::new(&miner, logger()).to_delegate(); let io = IoHandler::new(); io.add_delegate(ethcore); @@ -113,7 +117,7 @@ fn rpc_ethcore_set_extra_data() { #[test] fn rpc_ethcore_set_author() { let miner = miner_service(); - let ethcore = EthcoreClient::new(&miner).to_delegate(); + let ethcore = EthcoreClient::new(&miner, logger()).to_delegate(); let io = IoHandler::new(); io.add_delegate(ethcore); @@ -125,9 +129,36 @@ fn rpc_ethcore_set_author() { } #[test] +fn rpc_ethcore_dev_logs() { + let miner = miner_service(); + let logger = logger(); + logger.append("a".to_owned()); + logger.append("b".to_owned()); + let ethcore = EthcoreClient::new(&miner, logger.clone()).to_delegate(); + let io = IoHandler::new(); + io.add_delegate(ethcore); + + let request = r#"{"jsonrpc": "2.0", "method": "ethcore_devLogs", "params":[], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":["b","a"],"id":1}"#; + + assert_eq!(io.handle_request(request), Some(response.to_owned())); +} + +#[test] +fn rpc_ethcore_dev_logs_levels() { + let miner = miner_service(); + let ethcore = EthcoreClient::new(&miner, logger()).to_delegate(); + let io = IoHandler::new(); + io.add_delegate(ethcore); + + let request = r#"{"jsonrpc": "2.0", "method": "ethcore_devLogsLevels", "params":[], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":"rpc=trace","id":1}"#; + assert_eq!(io.handle_request(request), Some(response.to_owned())); +} + fn rpc_ethcore_set_transactions_limit() { let miner = miner_service(); - let ethcore = EthcoreClient::new(&miner).to_delegate(); + let ethcore = EthcoreClient::new(&miner, logger()).to_delegate(); let io = IoHandler::new(); io.add_delegate(ethcore); @@ -142,7 +173,7 @@ fn rpc_ethcore_set_transactions_limit() { #[test] fn rpc_ethcore_transactions_limit() { let miner = miner_service(); - let ethcore = EthcoreClient::new(&miner).to_delegate(); + let ethcore = EthcoreClient::new(&miner, logger()).to_delegate(); let io = IoHandler::new(); io.add_delegate(ethcore); diff --git a/rpc/src/v1/traits/ethcore.rs b/rpc/src/v1/traits/ethcore.rs index 90bb112e0..f00d7bf9a 100644 --- a/rpc/src/v1/traits/ethcore.rs +++ b/rpc/src/v1/traits/ethcore.rs @@ -48,6 +48,12 @@ pub trait Ethcore: Sized + Send + Sync + 'static { /// Returns minimal gas price for transaction to be included in queue. fn min_gas_price(&self, _: Params) -> Result { rpc_unimplemented!() } + /// Returns latest logs + fn dev_logs(&self, _: Params) -> Result { rpc_unimplemented!() } + + /// Returns logs levels + fn dev_logs_levels(&self, _: Params) -> Result { rpc_unimplemented!() } + /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { let mut delegate = IoDelegate::new(Arc::new(self)); @@ -61,6 +67,9 @@ pub trait Ethcore: Sized + Send + Sync + 'static { delegate.add_method("ethcore_gasFloorTarget", Ethcore::gas_floor_target); delegate.add_method("ethcore_minGasPrice", Ethcore::min_gas_price); delegate.add_method("ethcore_transactionsLimit", Ethcore::transactions_limit); + delegate.add_method("ethcore_devLogs", Ethcore::dev_logs); + delegate.add_method("ethcore_devLogsLevels", Ethcore::dev_logs_levels); + delegate } } diff --git a/util/src/log.rs b/util/src/log.rs index 72b97caf5..172957c13 100644 --- a/util/src/log.rs +++ b/util/src/log.rs @@ -19,6 +19,8 @@ use std::env; use rlog::{LogLevelFilter}; use env_logger::LogBuilder; +use std::sync::{RwLock, RwLockReadGuard}; +use arrayvec::ArrayVec; lazy_static! { static ref LOG_DUMMY: bool = { @@ -40,3 +42,79 @@ lazy_static! { pub fn init_log() { let _ = *LOG_DUMMY; } + +const LOG_SIZE : usize = 128; + +/// Logger implementation that keeps up to `LOG_SIZE` log elements. +pub struct RotatingLogger { + /// Defined logger levels + levels: String, + /// Logs array. Latest log is always at index 0 + logs: RwLock>, +} + +impl RotatingLogger { + + /// Creates new `RotatingLogger` with given levels. + /// It does not enforce levels - it's just read only. + pub fn new(levels: String) -> Self { + RotatingLogger { + levels: levels, + logs: RwLock::new(ArrayVec::<[_; LOG_SIZE]>::new()), + } + } + + /// Append new log entry + pub fn append(&self, log: String) { + self.logs.write().unwrap().insert(0, log); + } + + /// Return levels + pub fn levels(&self) -> &str { + &self.levels + } + + /// Return logs + pub fn logs(&self) -> RwLockReadGuard> { + self.logs.read().unwrap() + } + +} + +#[cfg(test)] +mod test { + use super::RotatingLogger; + + fn logger() -> RotatingLogger { + RotatingLogger::new("test".to_owned()) + } + + #[test] + fn should_return_log_levels() { + // given + let logger = logger(); + + // when + let levels = logger.levels(); + + // then + assert_eq!(levels, "test"); + } + + #[test] + fn should_return_latest_logs() { + // given + let logger = logger(); + + // when + logger.append("a".to_owned()); + logger.append("b".to_owned()); + + // then + let logs = logger.logs(); + assert_eq!(logs[0], "b".to_owned()); + assert_eq!(logs[1], "a".to_owned()); + assert_eq!(logs.len(), 2); + } +} +