diff --git a/Cargo.toml b/Cargo.toml index abf3a6e5b03..b06b72696c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ categories = ["cryptography::cryptocurrencies"] [workspace.dependencies] iroha = { path = "cli" } -iroha_dsl = { version = "=2.0.0-pre-rc.20", path = "dsl" } iroha_torii = { version = "=2.0.0-pre-rc.20", path = "torii" } iroha_torii_derive = { version = "=2.0.0-pre-rc.20", path = "torii/derive" } iroha_torii_const = { version = "=2.0.0-pre-rc.20", path = "torii/const" } @@ -51,14 +50,10 @@ iroha_wasm_builder = { version = "=2.0.0-pre-rc.20", path = "wasm_builder" } iroha_smart_contract = { version = "=2.0.0-pre-rc.20", path = "smart_contract" } iroha_smart_contract_derive = { version = "=2.0.0-pre-rc.20", path = "smart_contract/derive" } iroha_smart_contract_utils = { version = "=2.0.0-pre-rc.20", path = "smart_contract/utils" } -iroha_executor = { version = "=2.0.0-pre-rc.20", path = "smart_contract/executor" } iroha_executor_derive = { version = "=2.0.0-pre-rc.20", path = "smart_contract/executor/derive" } -iroha_trigger = { version = "=2.0.0-pre-rc.20", path = "smart_contract/trigger" } iroha_trigger_derive = { version = "=2.0.0-pre-rc.20", path = "smart_contract/trigger/derive" } test_network = { version = "=2.0.0-pre-rc.20", path = "core/test_network" } - -proc-macro-error = "1.0.4" proc-macro2 = "1.0.69" syn = { version = "2.0.38", default-features = false } quote = "1.0.33" @@ -71,8 +66,6 @@ tokio = "1.33.0" tokio-stream = "0.1.14" tokio-tungstenite = "0.20.1" tungstenite = "0.20.1" - -crossbeam = "0.8.2" crossbeam-queue = "0.3.8" parking_lot = { version = "0.12.1" } @@ -98,10 +91,8 @@ owo-colors = "3.5.0" supports-color = "2.1.0" inquire = "0.6.2" spinoff = "0.8.0" -duct = "0.13.6" criterion = "0.5.1" -proptest = "1.3.1" expect-test = "1.4.1" eyre = "0.6.8" @@ -204,53 +195,53 @@ clippy.wildcard_dependencies = "deny" [workspace] resolver = "2" members = [ - "cli", - "client", - "client_cli", - "config", - "config/base", - "core", - "core/test_network", - "crypto", - "data_model", - "genesis", - "primitives", - "primitives/derive", - "primitives/numeric", - "ffi", - "ffi/derive", - "futures", - "futures/derive", - "logger", - "macro", - "macro/derive", - "macro/utils", - "p2p", - "schema", - "schema/derive", - "schema/gen", - "smart_contract", - "smart_contract/derive", - "smart_contract/trigger", - "smart_contract/trigger/derive", - "smart_contract/utils", - "smart_contract/executor", - "smart_contract/executor/derive", - "telemetry", - "tools/kagami", - "tools/kura_inspector", - "tools/parity_scale_decoder", - "tools/swarm", - "tools/wasm_builder_cli", - "tools/wasm_test_runner", - "torii", - "torii/derive", - "torii/const", - "version", - "version/derive", - "wasm_codec", - "wasm_codec/derive", - "wasm_builder", + "cli", + "client", + "client_cli", + "config", + "config/base", + "core", + "core/test_network", + "crypto", + "data_model", + "genesis", + "primitives", + "primitives/derive", + "primitives/numeric", + "ffi", + "ffi/derive", + "futures", + "futures/derive", + "logger", + "macro", + "macro/derive", + "macro/utils", + "p2p", + "schema", + "schema/derive", + "schema/gen", + "smart_contract", + "smart_contract/derive", + "smart_contract/trigger", + "smart_contract/trigger/derive", + "smart_contract/utils", + "smart_contract/executor", + "smart_contract/executor/derive", + "telemetry", + "tools/kagami", + "tools/kura_inspector", + "tools/parity_scale_decoder", + "tools/swarm", + "tools/wasm_builder_cli", + "tools/wasm_test_runner", + "torii", + "torii/derive", + "torii/const", + "version", + "version/derive", + "wasm_codec", + "wasm_codec/derive", + "wasm_builder", ] [profile.deploy] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 4ab631e22e2..bb21b2c8a12 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -24,7 +24,9 @@ default = ["telemetry", "schema-endpoint"] telemetry = ["iroha_telemetry", "iroha_core/telemetry", "iroha_torii/telemetry"] # Support developer-specific telemetry. # Should not be enabled on production builds. -dev-telemetry = ["iroha_core/dev-telemetry", "iroha_telemetry"] +# Tokio Console is configured via ENV: +# https://docs.rs/console-subscriber/0.2.0/console_subscriber/struct.Builder.html#method.with_default_env +dev-telemetry = ["telemetry", "iroha_telemetry/dev-telemetry", "iroha_logger/tokio-console"] # Support schema generation from the `schema` endpoint in the local binary. # Useful for debugging issues with decoding in SDKs. schema-endpoint = ["iroha_torii/schema"] @@ -79,8 +81,8 @@ vergen = { workspace = true, features = ["cargo"] } [package.metadata.cargo-all-features] denylist = [ - "schema-endpoint", - "telemetry", - "test-network", + "schema-endpoint", + "telemetry", + "test-network", ] skip_optional_dependencies = true diff --git a/cli/src/lib.rs b/cli/src/lib.rs index eb631467a21..1c3b107bcc6 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -6,8 +6,9 @@ //! should be constructed externally: (see `main.rs`). #[cfg(debug_assertions)] use core::sync::atomic::{AtomicBool, Ordering}; -use std::{path::Path, sync::Arc}; +use std::{path::PathBuf, sync::Arc}; +use clap::Parser; use color_eyre::eyre::{eyre, Result, WrapErr}; use iroha_config::parameters::{actual::Root as Config, user::CliContext}; use iroha_core::{ @@ -28,7 +29,7 @@ use iroha_core::{ }; use iroha_data_model::prelude::*; use iroha_genesis::{GenesisNetwork, RawGenesisBlock}; -use iroha_logger::actor::LoggerHandle; +use iroha_logger::{actor::LoggerHandle, InitConfig as LoggerInitConfig}; use iroha_torii::Torii; use tokio::{ signal, @@ -229,16 +230,16 @@ impl Iroha { Ok(state) => { iroha_logger::info!( at_height = state.view().height(), - "Successfully loaded state from a snapshot" + "Successfully loaded the state from a snapshot" ); Some(state) } Err(TryReadSnapshotError::NotFound) => { - iroha_logger::info!("Didn't find a snapshot of state, creating an empty one"); + iroha_logger::info!("Didn't find a state snapshot; creating an empty state"); None } Err(error) => { - iroha_logger::warn!(%error, "Failed to load state from a snapshot, creating an empty one"); + iroha_logger::warn!(%error, "Failed to load the state from a snapshot; creating an empty state"); None } }.unwrap_or_else(|| { @@ -252,10 +253,9 @@ impl Iroha { let state = Arc::new(state); let queue = Arc::new(Queue::from_config(config.queue)); - match Self::start_telemetry(&logger, &config).await? { - TelemetryStartStatus::Started => iroha_logger::info!("Telemetry started"), - TelemetryStartStatus::NotStarted => iroha_logger::warn!("Telemetry not started"), - }; + + #[cfg(feature = "telemetry")] + Self::start_telemetry(&logger, &config).await?; let kura_thread_handler = Kura::start(Arc::clone(&kura)); @@ -322,6 +322,7 @@ impl Iroha { Arc::clone(&queue), events_sender, Arc::clone(¬ify_shutdown), + #[cfg(feature = "telemetry")] sumeragi.clone(), live_query_store_handle, Arc::clone(&kura), @@ -382,18 +383,15 @@ impl Iroha { } #[cfg(feature = "telemetry")] - async fn start_telemetry( - logger: &LoggerHandle, - config: &Config, - ) -> Result { + async fn start_telemetry(logger: &LoggerHandle, config: &Config) -> Result<()> { #[cfg(feature = "dev-telemetry")] { - if let Some(config) = &config.dev_telemetry { + if let Some(out_file) = &config.dev_telemetry.out_file { let receiver = logger .subscribe_on_telemetry(iroha_logger::telemetry::Channel::Future) .await .wrap_err("Failed to subscribe on telemetry")?; - let _handle = iroha_telemetry::dev::start(config.clone(), receiver) + let _handle = iroha_telemetry::dev::start_file_output(out_file.clone(), receiver) .await .wrap_err("Failed to setup telemetry for futures")?; } @@ -407,21 +405,14 @@ impl Iroha { let _handle = iroha_telemetry::ws::start(config.clone(), receiver) .await .wrap_err("Failed to setup telemetry for websocket communication")?; - - Ok(TelemetryStartStatus::Started) + iroha_logger::info!("Telemetry started"); + Ok(()) } else { - Ok(TelemetryStartStatus::NotStarted) + iroha_logger::info!("Telemetry not started due to absent configuration"); + Ok(()) } } - #[cfg(not(feature = "telemetry"))] - async fn start_telemetry( - _logger: &LoggerHandle, - _config: &Config, - ) -> Result { - Ok(TelemetryStartStatus::NotStarted) - } - fn start_listening_signal(notify_shutdown: Arc) -> Result> { let (mut sigint, mut sigterm) = signal::unix::signal(signal::unix::SignalKind::interrupt()) .and_then(|sigint| { @@ -481,11 +472,6 @@ impl Iroha { } } -enum TelemetryStartStatus { - Started, - NotStarted, -} - fn genesis_account(public_key: PublicKey) -> Account { Account::new(iroha_genesis::GENESIS_ACCOUNT_ID.clone(), public_key) .build(&iroha_genesis::GENESIS_ACCOUNT_ID) @@ -503,20 +489,24 @@ fn genesis_domain(public_key: PublicKey) -> Domain { domain } -/// Read configuration and then a genesis block if specified. +/// Read the configuration and then a genesis block if specified. /// /// # Errors /// - If failed to read the config /// - If failed to load the genesis block /// - If failed to build a genesis network -pub fn read_config_and_genesis>( - path: Option

, - submit_genesis: bool, -) -> Result<(Config, Option)> { +pub fn read_config_and_genesis( + args: &Args, +) -> Result<(Config, LoggerInitConfig, Option)> { use iroha_config::parameters::actual::Genesis; - let config = Config::load(path, CliContext { submit_genesis }) - .wrap_err("failed to load configuration")?; + let config = Config::load( + args.config.as_ref(), + CliContext { + submit_genesis: args.submit_genesis, + }, + ) + .wrap_err("failed to load configuration")?; let genesis = if let Genesis::Full { key_pair, file } = &config.genesis { let raw_block = RawGenesisBlock::from_path(file)?; @@ -530,7 +520,73 @@ pub fn read_config_and_genesis>( None }; - Ok((config, genesis)) + let logger_config = LoggerInitConfig::new(config.logger, args.terminal_colors); + + #[cfg(not(feature = "telemetry"))] + if config.telemetry.is_some() { + // TODO: use a centralized configuration logging + // https://github.com/hyperledger/iroha/issues/4300 + eprintln!("`telemetry` config is specified, but ignored, because Iroha is compiled without `telemetry` feature enabled"); + } + + #[cfg(not(feature = "dev-telemetry"))] + if config.dev_telemetry.out_file.is_some() { + // TODO: use a centralized configuration logging + // https://github.com/hyperledger/iroha/issues/4300 + eprintln!("`dev_telemetry.out_file` config is specified, but ignored, because Iroha is compiled without `dev-telemetry` feature enabled"); + } + + Ok((config, logger_config, genesis)) +} + +#[allow(missing_docs)] +pub fn is_colouring_supported() -> bool { + supports_color::on(supports_color::Stream::Stdout).is_some() +} + +fn default_terminal_colors_str() -> clap::builder::OsStr { + is_colouring_supported().to_string().into() +} + +/// Iroha peer Command-Line Interface. +#[derive(Parser, Debug)] +#[command( + name = "iroha", + version = concat!("version=", env!("CARGO_PKG_VERSION"), " git_commit_sha=", env!("VERGEN_GIT_SHA"), " cargo_features=", env!("VERGEN_CARGO_FEATURES")), + author +)] +pub struct Args { + /// Path to the configuration file + #[arg(long, short, value_name("PATH"), value_hint(clap::ValueHint::FilePath))] + pub config: Option, + /// Whether to enable ANSI colored output or not + /// + /// By default, Iroha determines whether the terminal supports colors or not. + /// + /// In order to disable this flag explicitly, pass `--terminal-colors=false`. + #[arg( + long, + env, + default_missing_value("true"), + default_value(default_terminal_colors_str()), + action(clap::ArgAction::Set), + require_equals(true), + num_args(0..=1), + )] + pub terminal_colors: bool, + /// Whether the current peer should submit the genesis block or not + /// + /// Only one peer in the network should submit the genesis block. + /// + /// This argument must be set alongside with `genesis.file` and `genesis.private_key` + /// configuration options. If not, Iroha will exit with an error. + /// + /// In case when the network consists only of this one peer, i.e. the amount of trusted + /// peers in the configuration (`sumeragi.trusted_peers`) is less than 2, this peer must + /// submit the genesis, since there are no other peers who can provide it. In this case, Iroha + /// will exit with an error if `--submit-genesis` is not set. + #[arg(long)] + pub submit_genesis: bool, } #[cfg(test)] @@ -606,7 +662,7 @@ mod tests { cfg.genesis.file.set("./genesis/gen.json".into()); cfg.kura.store_dir.set("../storage".into()); cfg.snapshot.store_dir.set("../snapshots".into()); - cfg.telemetry.dev.out_file.set("../logs/telemetry".into()); + cfg.dev_telemetry.out_file.set("../logs/telemetry".into()); toml::Value::try_from(cfg)? }; @@ -624,7 +680,11 @@ mod tests { // When - let (config, genesis) = read_config_and_genesis(Some(config_path), true)?; + let (config, _logger, genesis) = read_config_and_genesis(&Args { + config: Some(config_path), + submit_genesis: true, + terminal_colors: false, + })?; // Then @@ -642,8 +702,8 @@ mod tests { assert_eq!( config .dev_telemetry - .expect("dev telemetry should be set") .out_file + .expect("dev telemetry should be set") .absolutize()?, dir.path().join("logs/telemetry") ); @@ -673,7 +733,12 @@ mod tests { // When & Then - let report = read_config_and_genesis(Some(config_path), false).unwrap_err(); + let report = read_config_and_genesis(&Args { + config: Some(config_path), + submit_genesis: false, + terminal_colors: false, + }) + .unwrap_err(); assert_contains!( format!("{report:#}"), @@ -683,4 +748,49 @@ mod tests { Ok(()) } } + + #[test] + #[allow(clippy::bool_assert_comparison)] // for expressiveness + fn default_args() -> Result<()> { + let args = Args::try_parse_from(["test"])?; + + assert_eq!(args.terminal_colors, is_colouring_supported()); + assert_eq!(args.submit_genesis, false); + + Ok(()) + } + + #[test] + #[allow(clippy::bool_assert_comparison)] // for expressiveness + fn terminal_colors_works_as_expected() -> Result<()> { + fn try_with(arg: &str) -> Result { + Ok(Args::try_parse_from(["test", arg])?.terminal_colors) + } + + assert_eq!( + Args::try_parse_from(["test"])?.terminal_colors, + is_colouring_supported() + ); + assert_eq!(try_with("--terminal-colors")?, true); + assert_eq!(try_with("--terminal-colors=false")?, false); + assert_eq!(try_with("--terminal-colors=true")?, true); + assert!(try_with("--terminal-colors=random").is_err()); + + Ok(()) + } + + #[test] + fn user_provided_config_path_works() -> Result<()> { + let args = Args::try_parse_from(["test", "--config", "/home/custom/file.json"])?; + + assert_eq!(args.config, Some(PathBuf::from("/home/custom/file.json"))); + + Ok(()) + } + + #[test] + fn user_can_provide_any_extension() { + let _args = Args::try_parse_from(["test", "--config", "file.toml.but.not"]) + .expect("should allow doing this as well"); + } } diff --git a/cli/src/main.rs b/cli/src/main.rs index 34c7909ef9d..fe90dcd3a26 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,53 +1,9 @@ //! Iroha peer command-line interface. -use std::{env, path::PathBuf}; +use std::env; use clap::Parser; use color_eyre::eyre::Result; - -fn is_colouring_supported() -> bool { - supports_color::on(supports_color::Stream::Stdout).is_some() -} - -fn default_terminal_colors_str() -> clap::builder::OsStr { - is_colouring_supported().to_string().into() -} - -/// Iroha peer Command-Line Interface. -#[derive(Parser, Debug)] -#[command(name = "iroha", version = concat!("version=", env!("CARGO_PKG_VERSION"), " git_commit_sha=", env!("VERGEN_GIT_SHA")), author)] -struct Args { - /// Path to the configuration file - #[arg(long, short, value_name("PATH"), value_hint(clap::ValueHint::FilePath))] - config: Option, - /// Whether to enable ANSI colored output or not - /// - /// By default, Iroha determines whether the terminal supports colors or not. - /// - /// In order to disable this flag explicitly, pass `--terminal-colors=false`. - #[arg( - long, - env, - default_missing_value("true"), - default_value(default_terminal_colors_str()), - action(clap::ArgAction::Set), - require_equals(true), - num_args(0..=1), - )] - terminal_colors: bool, - /// Whether the current peer should submit the genesis block or not - /// - /// Only one peer in the network should submit the genesis block. - /// - /// This argument must be set alongside with `genesis.file` and `genesis.private_key` - /// configuration options. If not, Iroha will exit with an error. - /// - /// In case when the network consists only of this one peer, i.e. the amount of trusted - /// peers in the configuration (`sumeragi.trusted_peers`) is less than 2, this peer must - /// submit the genesis, since there are no other peers who can provide it. In this case, Iroha - /// will exit with an error if `--submit-genesis` is not set. - #[arg(long)] - submit_genesis: bool, -} +use iroha::Args; #[tokio::main] async fn main() -> Result<()> { @@ -57,8 +13,8 @@ async fn main() -> Result<()> { color_eyre::install()?; } - let (config, genesis) = iroha::read_config_and_genesis(args.config, args.submit_genesis)?; - let logger = iroha_logger::init_global(&config.logger, args.terminal_colors)?; + let (config, logger_config, genesis) = iroha::read_config_and_genesis(&args)?; + let logger = iroha_logger::init_global(logger_config)?; iroha_logger::info!( version = env!("CARGO_PKG_VERSION"), @@ -77,53 +33,3 @@ async fn main() -> Result<()> { Ok(()) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[allow(clippy::bool_assert_comparison)] // for expressiveness - fn default_args() -> Result<()> { - let args = Args::try_parse_from(["test"])?; - - assert_eq!(args.terminal_colors, is_colouring_supported()); - assert_eq!(args.submit_genesis, false); - - Ok(()) - } - - #[test] - #[allow(clippy::bool_assert_comparison)] // for expressiveness - fn terminal_colors_works_as_expected() -> Result<()> { - fn try_with(arg: &str) -> Result { - Ok(Args::try_parse_from(["test", arg])?.terminal_colors) - } - - assert_eq!( - Args::try_parse_from(["test"])?.terminal_colors, - is_colouring_supported() - ); - assert_eq!(try_with("--terminal-colors")?, true); - assert_eq!(try_with("--terminal-colors=false")?, false); - assert_eq!(try_with("--terminal-colors=true")?, true); - assert!(try_with("--terminal-colors=random").is_err()); - - Ok(()) - } - - #[test] - fn user_provided_config_path_works() -> Result<()> { - let args = Args::try_parse_from(["test", "--config", "/home/custom/file.json"])?; - - assert_eq!(args.config, Some(PathBuf::from("/home/custom/file.json"))); - - Ok(()) - } - - #[test] - fn user_can_provide_any_extension() { - let _args = Args::try_parse_from(["test", "--config", "file.toml.but.not"]) - .expect("should allow doing this as well"); - } -} diff --git a/client/Cargo.toml b/client/Cargo.toml index 1d38df505b9..a31dbab37d3 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -83,7 +83,7 @@ iroha_wasm_builder = { workspace = true } # TODO: These three activate `transparent_api` but client should never activate this feature. # Additionally there is a dependency on iroha_core in dev-dependencies in telemetry/derive # Hopefully, once the integration tests migration is finished these can be removed -iroha = { workspace = true, features = ["dev-telemetry", "telemetry"] } +iroha = { workspace = true } iroha_genesis = { workspace = true } test_network = { workspace = true } diff --git a/config/Cargo.toml b/config/Cargo.toml index 8c354f4372b..fd23c7f8cad 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -42,6 +42,3 @@ stacker = "0.1.15" expect-test = { workspace = true } trybuild = { workspace = true } hex = { workspace = true } - -[features] -tokio-console = [] diff --git a/config/src/parameters/actual.rs b/config/src/parameters/actual.rs index df47ba9c2ee..df23be982f7 100644 --- a/config/src/parameters/actual.rs +++ b/config/src/parameters/actual.rs @@ -16,7 +16,7 @@ use iroha_data_model::{ use iroha_primitives::{addr::SocketAddr, unique_vec::UniqueVec}; use serde::{Deserialize, Serialize}; use url::Url; -pub use user::{Logger, Queue, Snapshot}; +pub use user::{DevTelemetry, Logger, Queue, Snapshot}; use crate::{ kura::InitMode, @@ -42,7 +42,7 @@ pub struct Root { pub queue: Queue, pub snapshot: Snapshot, pub telemetry: Option, - pub dev_telemetry: Option, + pub dev_telemetry: DevTelemetry, pub chain_wide: ChainWide, } @@ -244,10 +244,3 @@ pub struct Telemetry { pub min_retry_period: Duration, pub max_retry_delay_exponent: u8, } - -/// Complete configuration needed to start dev telemetry. -#[derive(Debug, Clone)] -#[allow(missing_docs)] -pub struct DevTelemetry { - pub out_file: PathBuf, -} diff --git a/config/src/parameters/defaults.rs b/config/src/parameters/defaults.rs index ff55704ee09..d81c08b4ac9 100644 --- a/config/src/parameters/defaults.rs +++ b/config/src/parameters/defaults.rs @@ -24,13 +24,6 @@ pub mod kura { pub const DEFAULT_STORE_DIR: &str = "./storage"; } -#[cfg(feature = "tokio-console")] -pub mod logger { - use iroha_primitives::addr::{socket_addr, SocketAddr}; - - pub const DEFAULT_TOKIO_CONSOLE_ADDR: SocketAddr = socket_addr!(127.0.0.1:5555); -} - pub mod network { use super::*; diff --git a/config/src/parameters/user.rs b/config/src/parameters/user.rs index d395f50fbb6..1a0d7a5ced1 100644 --- a/config/src/parameters/user.rs +++ b/config/src/parameters/user.rs @@ -52,6 +52,7 @@ pub struct Root { queue: Queue, snapshot: Snapshot, telemetry: Telemetry, + dev_telemetry: DevTelemetry, torii: Torii, chain_wide: ChainWide, } @@ -119,7 +120,7 @@ impl RootPartial { patch!(self.genesis.file); patch!(self.snapshot.store_dir); patch!(self.kura.store_dir); - patch!(self.telemetry.dev.out_file); + patch!(self.dev_telemetry.out_file); } // FIXME workaround the inconvenient way `Merge::merge` works @@ -155,7 +156,15 @@ impl Root { Some, ); + // TODO: enable this check after fix of https://github.com/hyperledger/iroha/issues/4383 + // if let Some(actual::Genesis::Full { file, .. }) = &genesis { + // if !file.is_file() { + // emitter.emit(eyre!("unable to access `genesis.file`: {}", file.display())) + // } + // } + let kura = self.kura.parse(); + validate_directory_path(&mut emitter, &kura.store_dir, "kura.store_dir"); let sumeragi = self.sumeragi.parse().map_or_else( |err| { @@ -181,11 +190,23 @@ impl Root { let logger = self.logger; let queue = self.queue; + let snapshot = self.snapshot; + validate_directory_path(&mut emitter, &snapshot.store_dir, "snapshot.store_dir"); + + let dev_telemetry = self.dev_telemetry; + if let Some(path) = &dev_telemetry.out_file { + if path.parent().is_none() { + emitter.emit(eyre!("`dev_telemetry.out_file` is not a valid file path")) + } + if path.is_dir() { + emitter.emit(eyre!("`dev_telemetry.out_file` is expected to be a file path, but it is a directory: {}", path.display())) + } + } let (torii, live_query_store) = self.torii.parse(); - let telemetries = self.telemetry.parse().map_or_else( + let telemetry = self.telemetry.parse().map_or_else( |err| { emitter.emit(err); None @@ -208,7 +229,7 @@ impl Root { key_pair: key_pair.unwrap(), p2p_address, }; - let (telemetry, dev_telemetry) = telemetries.unwrap(); + let telemetry = telemetry.unwrap(); let genesis = genesis.unwrap(); let sumeragi = { let mut x = sumeragi.unwrap(); @@ -235,6 +256,20 @@ impl Root { } } +fn validate_directory_path( + emitter: &mut Emitter, + path: impl AsRef, + name: impl AsRef, +) { + if path.as_ref().is_file() { + emitter.emit(eyre!( + "`{}` is expected to be a directory path (existing or non-existing), but it points to an existing file: {}", + name.as_ref(), + path.as_ref().display() + )) + } +} + #[derive(Copy, Clone)] pub struct CliContext { pub submit_genesis: bool, @@ -461,8 +496,7 @@ pub struct Queue { pub future_threshold: Duration, } -#[allow(missing_copy_implementations)] // triggered without tokio-console -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, Default)] pub struct Logger { /// Level of logging verbosity // TODO: parse user provided value in a case insensitive way, @@ -471,21 +505,6 @@ pub struct Logger { pub level: Level, /// Output format pub format: LoggerFormat, - #[cfg(feature = "tokio-console")] - /// Address of tokio console (only available under "tokio-console" feature) - pub tokio_console_address: SocketAddr, -} - -#[allow(clippy::derivable_impls)] // triggers in absence of `tokio-console` feature -impl Default for Logger { - fn default() -> Self { - Self { - level: Level::default(), - format: LoggerFormat::default(), - #[cfg(feature = "tokio-console")] - tokio_console_address: super::defaults::logger::DEFAULT_TOKIO_CONSOLE_ADDR, - } - } } #[derive(Debug)] @@ -496,22 +515,20 @@ pub struct Telemetry { pub url: Option, pub min_retry_period: Option, pub max_retry_delay_exponent: Option, - pub dev: TelemetryDev, } -#[derive(Debug)] -pub struct TelemetryDev { +#[derive(Debug, Clone)] +pub struct DevTelemetry { pub out_file: Option, } impl Telemetry { - fn parse(self) -> Result<(Option, Option), Report> { + fn parse(self) -> Result, Report> { let Self { name, url, max_retry_delay_exponent, min_retry_period, - dev: TelemetryDev { out_file: file }, } = self; let regular = match (name, url) { @@ -532,11 +549,7 @@ impl Telemetry { } }; - let dev = file.map(|file| actual::DevTelemetry { - out_file: file.clone(), - }); - - Ok((regular, dev)) + Ok(regular) } } diff --git a/config/src/parameters/user/boilerplate.rs b/config/src/parameters/user/boilerplate.rs index 34474918934..2e9b8695474 100644 --- a/config/src/parameters/user/boilerplate.rs +++ b/config/src/parameters/user/boilerplate.rs @@ -32,8 +32,8 @@ use crate::{ defaults::{self, chain_wide::*, network::*, queue::*, torii::*}, user, user::{ - ChainWide, Genesis, Kura, KuraDebug, Logger, Network, Queue, Root, Snapshot, Sumeragi, - SumeragiDebug, Telemetry, TelemetryDev, Torii, + ChainWide, DevTelemetry, Genesis, Kura, KuraDebug, Logger, Network, Queue, Root, + Snapshot, Sumeragi, SumeragiDebug, Telemetry, Torii, }, }, snapshot::Mode as SnapshotMode, @@ -54,6 +54,7 @@ pub struct RootPartial { pub queue: QueuePartial, pub snapshot: SnapshotPartial, pub telemetry: TelemetryPartial, + pub dev_telemetry: DevTelemetryPartial, pub torii: ToriiPartial, pub chain_wide: ChainWidePartial, } @@ -102,6 +103,7 @@ impl UnwrapPartial for RootPartial { let queue = nested!(self.queue); let snapshot = nested!(self.snapshot); let telemetry = nested!(self.telemetry); + let dev_telemetry = nested!(self.dev_telemetry); let torii = nested!(self.torii); let chain_wide = nested!(self.chain_wide); @@ -115,6 +117,7 @@ impl UnwrapPartial for RootPartial { kura: kura.unwrap(), sumeragi: sumeragi.unwrap(), telemetry: telemetry.unwrap(), + dev_telemetry: dev_telemetry.unwrap(), logger: logger.unwrap(), queue: queue.unwrap(), snapshot: snapshot.unwrap(), @@ -156,6 +159,7 @@ impl FromEnv for RootPartial { let queue = emitter.try_from_env(env); let snapshot = emitter.try_from_env(env); let telemetry = emitter.try_from_env(env); + let dev_telemetry = emitter.try_from_env(env); let torii = emitter.try_from_env(env); let chain_wide = emitter.try_from_env(env); @@ -174,6 +178,7 @@ impl FromEnv for RootPartial { queue: queue.unwrap(), snapshot: snapshot.unwrap(), telemetry: telemetry.unwrap(), + dev_telemetry: dev_telemetry.unwrap(), torii: torii.unwrap(), chain_wide: chain_wide.unwrap(), }) @@ -500,19 +505,11 @@ impl UnwrapPartial for QueuePartial { impl FromEnvDefaultFallback for QueuePartial {} -/// 'Logger' configuration. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Merge)] -// `tokio_console_addr` is not `Copy`, but warning appears without `tokio-console` feature -#[allow(missing_copy_implementations)] #[serde(deny_unknown_fields, default)] pub struct LoggerPartial { - /// Level of logging verbosity pub level: UserField, - /// Output format pub format: UserField, - #[cfg(feature = "tokio-console")] - /// Address of tokio console (only available under "tokio-console" feature) - pub tokio_console_address: UserField, } impl UnwrapPartial for LoggerPartial { @@ -522,10 +519,6 @@ impl UnwrapPartial for LoggerPartial { Ok(Logger { level: self.level.unwrap_or_default(), format: self.format.unwrap_or_default(), - #[cfg(feature = "tokio-console")] - tokio_console_address: self.tokio_console_address.get().unwrap_or_else(|| { - super::super::defaults::logger::DEFAULT_TOKIO_CONSOLE_ADDR.clone() - }), }) } } @@ -560,20 +553,19 @@ pub struct TelemetryPartial { pub url: UserField, pub min_retry_period: UserField, pub max_retry_delay_exponent: UserField, - pub dev: TelemetryDevPartial, } #[derive(Clone, Deserialize, Serialize, Debug, Default, Merge)] #[serde(deny_unknown_fields, default)] -pub struct TelemetryDevPartial { +pub struct DevTelemetryPartial { pub out_file: UserField, } -impl UnwrapPartial for TelemetryDevPartial { - type Output = TelemetryDev; +impl UnwrapPartial for DevTelemetryPartial { + type Output = DevTelemetry; fn unwrap_partial(self) -> UnwrapPartialResult { - Ok(TelemetryDev { + Ok(DevTelemetry { out_file: self.out_file.get(), }) } @@ -588,7 +580,6 @@ impl UnwrapPartial for TelemetryPartial { url, max_retry_delay_exponent, min_retry_period, - dev, } = self; Ok(Telemetry { @@ -596,13 +587,14 @@ impl UnwrapPartial for TelemetryPartial { url: url.get(), max_retry_delay_exponent: max_retry_delay_exponent.get(), min_retry_period: min_retry_period.get().map(HumanDuration::get), - dev: dev.unwrap_partial()?, }) } } impl FromEnvDefaultFallback for TelemetryPartial {} +impl FromEnvDefaultFallback for DevTelemetryPartial {} + #[derive(Debug, Clone, Deserialize, Serialize, Default, Merge)] #[serde(deny_unknown_fields, default)] pub struct SnapshotPartial { diff --git a/config/tests/fixtures.rs b/config/tests/fixtures.rs index 1b3cadd98d2..0c0c5c586ca 100644 --- a/config/tests/fixtures.rs +++ b/config/tests/fixtures.rs @@ -114,7 +114,6 @@ fn minimal_config_snapshot() -> Result<()> { logger: Logger { level: INFO, format: Full, - tokio_console_address: 127.0.0.1:5555, }, queue: Queue { capacity: 65536, @@ -128,7 +127,9 @@ fn minimal_config_snapshot() -> Result<()> { store_dir: "./storage/snapshot", }, telemetry: None, - dev_telemetry: None, + dev_telemetry: DevTelemetry { + out_file: None, + }, chain_wide: ChainWide { max_transactions_in_block: 512, block_time: 2s, @@ -372,7 +373,6 @@ fn full_envs_set_is_consumed() -> Result<()> { format: Some( Pretty, ), - tokio_console_address: None, }, queue: QueuePartial { capacity: None, @@ -394,9 +394,9 @@ fn full_envs_set_is_consumed() -> Result<()> { url: None, min_retry_period: None, max_retry_delay_exponent: None, - dev: TelemetryDevPartial { - out_file: None, - }, + }, + dev_telemetry: DevTelemetryPartial { + out_file: None, }, torii: ToriiPartial { address: Some( @@ -494,7 +494,6 @@ fn multiple_extends_works() -> Result<()> { format: Some( Compact, ), - tokio_console_address: None, }"#]]; expected.assert_eq(&format!("{layer:#?}")); @@ -525,7 +524,7 @@ fn absolute_paths_are_preserved() { assert_eq!(cfg.kura.store_dir, PathBuf::from("/kura/store")); assert_eq!(cfg.snapshot.store_dir, PathBuf::from("/snapshot/store")); assert_eq!( - cfg.dev_telemetry.unwrap().out_file, + cfg.dev_telemetry.out_file.unwrap(), PathBuf::from("/telemetry/file.json") ); if let Genesis::Full { diff --git a/config/tests/fixtures/absolute_paths.toml b/config/tests/fixtures/absolute_paths.toml index c176502b257..7a5a043a4d0 100644 --- a/config/tests/fixtures/absolute_paths.toml +++ b/config/tests/fixtures/absolute_paths.toml @@ -6,7 +6,7 @@ store_dir = "/kura/store" [snapshot] store_dir = "/snapshot/store" -[telemetry.dev] +[dev_telemetry] out_file = "/telemetry/file.json" [genesis] diff --git a/config/tests/fixtures/full.toml b/config/tests/fixtures/full.toml index f38ad0e38ef..ef611ea97cf 100644 --- a/config/tests/fixtures/full.toml +++ b/config/tests/fixtures/full.toml @@ -38,7 +38,6 @@ force_soft_fork = true [logger] level = "TRACE" format = "compact" -tokio_console_address = "127.0.0.1:5555" [queue] capacity = 65536 @@ -57,8 +56,8 @@ url = "http://test.com" min_retry_period = 5_000 max_retry_delay_exponent = 4 -[telemetry.dev] -out_file = "./dev-telemetry.json5" +[dev_telemetry] +out_file = "./dev-telemetry.json" [chain_wide] max_transactions_in_block = 512 diff --git a/configs/peer.template.toml b/configs/peer.template.toml index bc01942940e..aa8691bcf76 100644 --- a/configs/peer.template.toml +++ b/configs/peer.template.toml @@ -42,7 +42,6 @@ [logger] # level = "INFO" # format = "full" -# tokio_console_address = "127.0.0.1:5555" ## Transactions Queue [queue] @@ -62,6 +61,6 @@ # min_retry_period = "1s" # max_retry_delay_exponent = 4 -[telemetry.dev] -## FIXME: is it JSON5? -# out_file = "./dev-telemetry.json5" +[dev_telemetry] +## A path to a file with JSON logs +# out_file = "./dev-telemetry.json" diff --git a/core/Cargo.toml b/core/Cargo.toml index 17d88b76cd3..0519a865991 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -18,15 +18,10 @@ categories.workspace = true workspace = true [features] -default = ["cli", "telemetry"] +default = ["telemetry"] # Support lightweight telemetry, including diagnostics telemetry = [] -# Support the included CLI -cli = [] -# Support developer-specific telemetry. -# Should not be enabled on production builds. -dev-telemetry = ["telemetry", "iroha_telemetry/dev-telemetry"] # Support Prometheus metrics. See https://prometheus.io/. expensive-telemetry = ["iroha_telemetry/metric-instrumentation"] # Profiler integration for wasmtime @@ -109,8 +104,8 @@ path = "benches/blocks/validate_blocks_oneshot.rs" [package.metadata.cargo-all-features] denylist = [ -"schema-endpoint", -"telemetry", -"test-network" + "schema-endpoint", + "telemetry", + "test-network" ] skip_optional_dependencies = true diff --git a/logger/Cargo.toml b/logger/Cargo.toml index 83aba591aea..fa0d977f260 100644 --- a/logger/Cargo.toml +++ b/logger/Cargo.toml @@ -21,7 +21,7 @@ tracing-core = "0.1.31" tracing-futures = { version = "0.2.5", default-features = false, features = ["std-future", "std"] } tracing-subscriber = { workspace = true, features = ["fmt", "ansi", "json"] } tokio = { workspace = true, features = ["sync", "rt", "macros"] } -console-subscriber = { version = "0.2.0", optional = true } +console-subscriber = { version = "0.2.0", optional = true } once_cell = { workspace = true } derive_more = { workspace = true } tracing-error = "0.2.0" @@ -32,6 +32,9 @@ tokio = { workspace = true, features = ["macros", "time", "rt"] } [features] -tokio-console = ["dep:console-subscriber", "tokio/tracing", "iroha_config/tokio-console"] +# When this feature is enabled, tokio console will be started anyway. +# Configured via ENV: +# https://docs.rs/console-subscriber/0.2.0/console_subscriber/struct.Builder.html#method.with_default_env +tokio-console = ["dep:console-subscriber", "tokio/tracing"] # Workaround to avoid activating `tokio-console` with `--all-features` flag, because `tokio-console` require `tokio_unstable` rustc flag no-tokio-console = [] diff --git a/logger/src/lib.rs b/logger/src/lib.rs index 87a5ac3ed60..35fd022889d 100644 --- a/logger/src/lib.rs +++ b/logger/src/lib.rs @@ -16,7 +16,7 @@ use color_eyre::{eyre::eyre, Report, Result}; use iroha_config::logger::into_tracing_level; pub use iroha_config::{ logger::{Format, Level}, - parameters::actual::Logger as Config, + parameters::actual::{DevTelemetry as DevTelemetryConfig, Logger as Config}, }; use tracing::subscriber::set_global_default; pub use tracing::{ @@ -41,6 +41,23 @@ fn try_set_logger() -> Result<()> { Ok(()) } +/// Configuration needed for [`init_global`]. It is a little extension of [`Config`]. +#[derive(Copy, Clone, Debug)] +pub struct InitConfig { + base: Config, + terminal_colors: bool, +} + +impl InitConfig { + /// Create new config from the base logger [`Config`] + pub fn new(base: Config, terminal_colors: bool) -> Self { + Self { + base, + terminal_colors, + } + } +} + /// Initializes the logger globally with given [`Configuration`]. /// /// Returns [`LoggerHandle`] to interact with the logger instance @@ -53,18 +70,18 @@ fn try_set_logger() -> Result<()> { /// If the logger is already set, raises a generic error. // TODO: refactor configuration in a way that `terminal_colors` is part of it // https://github.com/hyperledger/iroha/issues/3500 -pub fn init_global(configuration: &Config, terminal_colors: bool) -> Result { +pub fn init_global(config: InitConfig) -> Result { try_set_logger()?; let layer = tracing_subscriber::fmt::layer() - .with_ansi(terminal_colors) + .with_ansi(config.terminal_colors) .with_test_writer(); - match configuration.format { - Format::Full => step2(configuration, layer), - Format::Compact => step2(configuration, layer.compact()), - Format::Pretty => step2(configuration, layer.pretty()), - Format::Json => step2(configuration, layer.json()), + match config.base.format { + Format::Full => step2(config, layer), + Format::Compact => step2(config, layer.compact()), + Format::Pretty => step2(config, layer.pretty()), + Format::Json => step2(config, layer.json()), } } @@ -82,14 +99,12 @@ pub fn test_logger() -> LoggerHandle { // with ENV vars rather than by extending `test_logger` signature. This will both remain // `test_logger` simple and also will emphasise isolation which is necessary anyway in // case of singleton mocking (where the logger is the singleton). - #[allow(clippy::needless_update)] // triggers without "tokio-console" feature let config = Config { level: Level::DEBUG, format: Format::Pretty, - ..Config::default() }; - init_global(&config, true).expect( + init_global(InitConfig::new(config, true)).expect( "`init_global()` or `disable_global()` should not be called before `test_logger()`", ) }) @@ -106,11 +121,11 @@ pub fn disable_global() -> Result<()> { try_set_logger() } -fn step2(configuration: &Config, layer: L) -> Result +fn step2(config: InitConfig, layer: L) -> Result where L: tracing_subscriber::Layer + Debug + Send + Sync + 'static, { - let level: tracing::Level = into_tracing_level(configuration.level); + let level: tracing::Level = into_tracing_level(config.base.level); let level_filter = tracing_subscriber::filter::LevelFilter::from_level(level); let (level_filter, level_filter_handle) = reload::Layer::new(level_filter); let subscriber = Registry::default() @@ -119,18 +134,8 @@ where .with(tracing_error::ErrorLayer::default()); #[cfg(all(feature = "tokio-console", not(feature = "no-tokio-console")))] - let subscriber = { - let console_subscriber = console_subscriber::ConsoleLayer::builder() - .server_addr( - configuration - .tokio_console_addr - .into() - .expect("Invalid address for tokio console"), - ) - .spawn(); + let subscriber = subscriber.with(console_subscriber::spawn()); - subscriber.with(console_subscriber) - }; let (subscriber, receiver) = telemetry::Layer::with_capacity(subscriber, TELEMETRY_CAPACITY); set_global_default(subscriber)?; diff --git a/logger/tests/setting_logger.rs b/logger/tests/setting_logger.rs index 6f118366562..2df0de5f548 100644 --- a/logger/tests/setting_logger.rs +++ b/logger/tests/setting_logger.rs @@ -1,13 +1,13 @@ -use iroha_logger::{init_global, Config}; +use iroha_logger::{init_global, Config, InitConfig}; #[tokio::test] async fn setting_logger_twice_fails() { let cfg = Config::default(); - let first = init_global(&cfg, false); + let first = init_global(InitConfig::new(cfg, false)); assert!(first.is_ok()); - let second = init_global(&cfg, false); + let second = init_global(InitConfig::new(cfg, false)); assert!(second.is_err()); } diff --git a/telemetry/src/dev.rs b/telemetry/src/dev.rs index 257b980165d..43055a72339 100644 --- a/telemetry/src/dev.rs +++ b/telemetry/src/dev.rs @@ -1,52 +1,71 @@ -//! Module with development telemetry +//! Telemetry for development rather than production purposes -use eyre::{Result, WrapErr}; -use iroha_config::parameters::actual::DevTelemetry as Config; +use std::path::PathBuf; + +use eyre::{eyre, Result, WrapErr}; +use iroha_futures::FuturePollTelemetry; use iroha_logger::telemetry::Event as Telemetry; use tokio::{ - fs::OpenOptions, + fs::{File, OpenOptions}, io::AsyncWriteExt, sync::broadcast::Receiver, task::{self, JoinHandle}, }; use tokio_stream::{wrappers::BroadcastStream, StreamExt}; -/// Starts telemetry writing to a file +/// Starts telemetry writing to a file. Will create all parent directories. +/// /// # Errors /// Fails if unable to open the file -pub async fn start(config: Config, telemetry: Receiver) -> Result> { +pub async fn start_file_output( + path: PathBuf, + telemetry: Receiver, +) -> Result> { let mut stream = crate::futures::get_stream(BroadcastStream::new(telemetry).fuse()); + std::fs::create_dir_all( + path.parent() + .ok_or_else(|| eyre!("the dev telemetry output file should have a parent directory"))?, + ) + .wrap_err_with(|| { + eyre!( + "failed to recursively create directories for the dev telemetry output file: {}", + path.display() + ) + })?; + let mut file = OpenOptions::new() - .write(true) - // Fails to write full item at exit. that is why not append - // TODO: think of workaround with dropcheck? - // - //.append(true) - .create(true) - .truncate(true) - .open(config.out_file) - .await - .wrap_err("Failed to create and open file for telemetry")?; + .write(true) + .append(true) + .create(true) + .open(&path) + .await + .wrap_err_with(|| { + eyre!( + "failed to open the dev telemetry output file: {}", + path.display() + ) + })?; // Serde doesn't support async Read Write traits. // So let synchronous code be here. - // - // TODO: After migration to tokio move to https://docs.rs/tokio-serde let join_handle = task::spawn(async move { while let Some(item) = stream.next().await { - let telemetry_json = match serde_json::to_string(&item) { - Ok(json) => json, - Err(error) => { - iroha_logger::error!(%error, "Failed to serialize telemetry to json"); - continue; - } - }; - if let Err(error) = file.write_all(telemetry_json.as_bytes()).await { - iroha_logger::error!(%error, "Failed to write telemetry to file"); + if let Err(error) = write_telemetry(&mut file, &item).await { + iroha_logger::error!(%error, "failed to write telemetry") } } }); Ok(join_handle) } + +async fn write_telemetry(file: &mut File, item: &FuturePollTelemetry) -> Result<()> { + let mut json = + serde_json::to_string(&item).wrap_err("failed to serialize telemetry to JSON")?; + json.push_str("\n"); + file.write_all(json.as_bytes()) + .await + .wrap_err("failed to write data to the file")?; + Ok(()) +} diff --git a/torii/src/lib.rs b/torii/src/lib.rs index 46b37d5cfe0..ce43bbe8561 100644 --- a/torii/src/lib.rs +++ b/torii/src/lib.rs @@ -14,6 +14,8 @@ use std::{ use futures::{stream::FuturesUnordered, StreamExt}; use iroha_config::parameters::actual::Torii as Config; +#[cfg(feature = "telemetry")] +use iroha_core::sumeragi::SumeragiHandle; use iroha_core::{ kiso::{Error as KisoError, KisoHandle}, kura::Kura, @@ -21,7 +23,6 @@ use iroha_core::{ query::store::LiveQueryStoreHandle, queue::{self, Queue}, state::State, - sumeragi::SumeragiHandle, EventsSender, }; use iroha_data_model::ChainId; @@ -49,6 +50,7 @@ pub struct Torii { queue: Arc, events: EventsSender, notify_shutdown: Arc, + #[cfg(feature = "telemetry")] sumeragi: SumeragiHandle, query_service: LiveQueryStoreHandle, kura: Arc, @@ -67,7 +69,7 @@ impl Torii { queue: Arc, events: EventsSender, notify_shutdown: Arc, - sumeragi: SumeragiHandle, + #[cfg(feature = "telemetry")] sumeragi: SumeragiHandle, query_service: LiveQueryStoreHandle, kura: Arc, state: Arc, @@ -78,6 +80,7 @@ impl Torii { queue, events, notify_shutdown, + #[cfg(feature = "telemetry")] sumeragi, query_service, kura, diff --git a/torii/src/routing.rs b/torii/src/routing.rs index 89095543651..46281f62916 100644 --- a/torii/src/routing.rs +++ b/torii/src/routing.rs @@ -9,10 +9,9 @@ use eyre::{eyre, WrapErr}; use futures::TryStreamExt; use iroha_config::client_api::ConfigDTO; -use iroha_core::{ - query::store::LiveQueryStoreHandle, smartcontracts::query::ValidQueryRequest, - sumeragi::SumeragiHandle, -}; +#[cfg(feature = "telemetry")] +use iroha_core::sumeragi::SumeragiHandle; +use iroha_core::{query::store::LiveQueryStoreHandle, smartcontracts::query::ValidQueryRequest}; use iroha_data_model::{ block::{ stream::{BlockMessage, BlockSubscriptionRequest},