diff --git a/Cargo.lock b/Cargo.lock index d5a47118..83138eac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1082,6 +1082,7 @@ dependencies = [ "toml", "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", "tracing", + "tracing-appender", "tracing-subscriber", ] @@ -1144,6 +1145,15 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "diff" version = "0.1.13" @@ -1987,6 +1997,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -2149,6 +2165,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2889,6 +2911,37 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a693d0c8cf16973fac5a93fbe47b8c6452e7097d4fcac49f3d7a18e39c76e62e" +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2978,6 +3031,8 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", + "hashbrown 0.14.5", "pin-project-lite", "slab", "tokio", @@ -3084,6 +3139,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.27" diff --git a/Cargo.toml b/Cargo.toml index c3588190..304669a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,6 +122,7 @@ tokio-stream = { version = "0.1", default-features = false } tokio = { version = "1", default-features = false } tower = { git = "https://github.com/Cuprate/tower.git", rev = "6c7faf0", default-features = false } # toml = { version = "0.8", default-features = false } +tracing-appender = { version = "0.2", default-features = false } tracing-subscriber = { version = "0.3", default-features = false } tracing = { version = "0.1", default-features = false } diff --git a/Cuprated.toml b/Cuprated.toml index a0446251..9a55ddae 100644 --- a/Cuprated.toml +++ b/Cuprated.toml @@ -10,7 +10,7 @@ network = "Mainnet" ## Tracing config. -[tracing] +[tracing.stdout] ## The minimum level for log events to be displayed. level = "info" diff --git a/binaries/cuprated/Cargo.toml b/binaries/cuprated/Cargo.toml index e8199c49..f60e692f 100644 --- a/binaries/cuprated/Cargo.toml +++ b/binaries/cuprated/Cargo.toml @@ -68,11 +68,12 @@ serde_json = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } thread_local = { workspace = true } -tokio-util = { workspace = true } +tokio-util = { workspace = true, features = ["rt"] } tokio-stream = { workspace = true } tokio = { workspace = true } toml = { workspace = true, features = ["parse", "display"]} tower = { workspace = true } +tracing-appender = { workspace = true } tracing-subscriber = { workspace = true, features = ["std", "fmt", "default"] } tracing = { workspace = true, features = ["default"] } diff --git a/binaries/cuprated/src/blockchain/manager.rs b/binaries/cuprated/src/blockchain/manager.rs index 2166795e..210c078a 100644 --- a/binaries/cuprated/src/blockchain/manager.rs +++ b/binaries/cuprated/src/blockchain/manager.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, sync::Arc}; use futures::StreamExt; use monero_serai::block::Block; use tokio::sync::{mpsc, oneshot, Notify}; +use tokio_util::sync::CancellationToken; use tower::{Service, ServiceExt}; use tracing::error; diff --git a/binaries/cuprated/src/blockchain/manager/handler.rs b/binaries/cuprated/src/blockchain/manager/handler.rs index 5d1cd2d6..91452033 100644 --- a/binaries/cuprated/src/blockchain/manager/handler.rs +++ b/binaries/cuprated/src/blockchain/manager/handler.rs @@ -9,7 +9,7 @@ use rayon::prelude::*; use std::ops::ControlFlow; use std::{collections::HashMap, sync::Arc}; use tower::{Service, ServiceExt}; -use tracing::info; +use tracing::{info, instrument}; use cuprate_blockchain::service::{BlockchainReadHandle, BlockchainWriteHandle}; use cuprate_consensus::{ @@ -120,6 +120,7 @@ impl super::BlockchainManager { /// /// This function will panic if the batch is empty or if any internal service returns an unexpected /// error that we cannot recover from or if the incoming batch contains no blocks. + #[instrument(name = "incoming_block_batch" skip_all, level = "info", fields(start_height = batch.blocks.first().unwrap().0.number().unwrap(), len = batch.blocks.len()))] pub async fn handle_incoming_block_batch(&mut self, batch: BlockBatch) { let (first_block, _) = batch .blocks @@ -146,10 +147,7 @@ impl super::BlockchainManager { /// This function will panic if any internal service returns an unexpected error that we cannot /// recover from or if the incoming batch contains no blocks. async fn handle_incoming_block_batch_main_chain(&mut self, batch: BlockBatch) { - info!( - "Handling batch to main chain height: {}", - batch.blocks.first().unwrap().0.number().unwrap() - ); + let start_height = batch.blocks.first().unwrap().0.number().unwrap(); let batch_prep_res = self .block_verifier_service @@ -192,6 +190,7 @@ impl super::BlockchainManager { self.add_valid_block_to_main_chain(verified_block).await; } + info!("Successfully added block batch"); } /// Handles an incoming [`BlockBatch`] that does not follow the main-chain. diff --git a/binaries/cuprated/src/config.rs b/binaries/cuprated/src/config.rs index 897d4eb0..77688e8c 100644 --- a/binaries/cuprated/src/config.rs +++ b/binaries/cuprated/src/config.rs @@ -80,7 +80,7 @@ pub struct Config { /// The storage config. storage: StorageConfig, - fs: FileSystemConfig, + pub fs: FileSystemConfig, } impl Config { diff --git a/binaries/cuprated/src/config/tracing_config.rs b/binaries/cuprated/src/config/tracing_config.rs index 8b2f2dcd..88d274af 100644 --- a/binaries/cuprated/src/config/tracing_config.rs +++ b/binaries/cuprated/src/config/tracing_config.rs @@ -2,15 +2,22 @@ use serde::{Deserialize, Serialize}; use tracing::level_filters::LevelFilter; /// [`tracing`] config. -#[derive(Deserialize, Serialize)] +#[derive(Default, Deserialize, Serialize)] #[serde(deny_unknown_fields, default)] pub struct TracingConfig { + pub stdout: StdoutTracingConfig, + pub file: FileTracingConfig, +} + +#[derive(Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct StdoutTracingConfig { /// The default minimum log level. #[serde(with = "level_filter_serde")] pub level: LevelFilter, } -impl Default for TracingConfig { +impl Default for StdoutTracingConfig { fn default() -> Self { Self { level: LevelFilter::INFO, @@ -18,6 +25,25 @@ impl Default for TracingConfig { } } +#[derive(Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct FileTracingConfig { + /// The default minimum log level. + #[serde(with = "level_filter_serde")] + pub level: LevelFilter, + + pub max_log_files: usize, +} + +impl Default for FileTracingConfig { + fn default() -> Self { + Self { + level: LevelFilter::DEBUG, + max_log_files: 7, + } + } +} + mod level_filter_serde { use std::str::FromStr; diff --git a/binaries/cuprated/src/logging.rs b/binaries/cuprated/src/logging.rs new file mode 100644 index 00000000..b91bfad8 --- /dev/null +++ b/binaries/cuprated/src/logging.rs @@ -0,0 +1,105 @@ +use crate::config::Config; +use cuprate_helper::fs::logs_path; +use std::mem::forget; +use std::sync::OnceLock; +use tracing::instrument::WithSubscriber; +use tracing::level_filters::LevelFilter; +use tracing::subscriber::Interest; +use tracing::Metadata; +use tracing_appender::non_blocking::NonBlocking; +use tracing_appender::rolling::Rotation; +use tracing_subscriber::filter::Filtered; +use tracing_subscriber::fmt::format::{DefaultFields, Format}; +use tracing_subscriber::layer::{Context, Layered, SubscriberExt}; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{ + fmt::Layer as FmtLayer, + layer::Filter, + reload::{Handle, Layer as ReloadLayer}, + Layer, +}; +use tracing_subscriber::{reload, Registry}; + +static FILE_WRITER_FILTER_HANDLE: OnceLock> = + OnceLock::new(); + +static STDOUT_FILTER_HANDLE: OnceLock< + Handle< + CupratedTracingFilter, + Layered< + Filtered< + FmtLayer, + ReloadLayer, + Registry, + >, + Registry, + Registry, + >, + >, +> = OnceLock::new(); + +pub struct CupratedTracingFilter { + pub level: LevelFilter, +} + +impl Filter for CupratedTracingFilter { + fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool { + Filter::::enabled(&self.level, meta, cx) + } + + fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest { + Filter::::callsite_enabled(&self.level, meta) + } + + fn max_level_hint(&self) -> Option { + Some(self.level) + } +} + +pub fn init_logging(config: &Config) { + use tracing_subscriber::{fmt, Layer}; + + let (stdout_filter, stdout_handle) = reload::Layer::new(CupratedTracingFilter { + level: config.tracing.stdout.level, + }); + drop(STDOUT_FILTER_HANDLE.set(stdout_handle)); + + let stdout_layer = fmt::Layer::default() + .with_target(false) + .with_filter(stdout_filter); + + let appender_config = &config.tracing.file; + let (appender, guard) = tracing_appender::non_blocking( + tracing_appender::rolling::Builder::new() + .rotation(Rotation::DAILY) + .max_log_files(appender_config.max_log_files) + .build(logs_path(&config.fs.data_directory, config.network())) + .unwrap(), + ); + + forget(guard); + + let (appender_filter, appender_handle) = reload::Layer::new(CupratedTracingFilter { + level: appender_config.level, + }); + drop(FILE_WRITER_FILTER_HANDLE.set(appender_handle)); + + let appender_layer = fmt::layer() + .with_target(false) + .with_ansi(false) + .with_writer(appender) + .with_filter(appender_filter); + + tracing_subscriber::registry() + .with(appender_layer) + .with(stdout_layer) + .init(); +} + +pub fn modify_stdout_output(f: impl FnOnce(&mut CupratedTracingFilter)) { + STDOUT_FILTER_HANDLE.get().unwrap().modify(f).unwrap(); +} + +pub fn modify_file_output(f: impl FnOnce(&mut CupratedTracingFilter)) { + FILE_WRITER_FILTER_HANDLE.get().unwrap().modify(f).unwrap(); +} diff --git a/binaries/cuprated/src/main.rs b/binaries/cuprated/src/main.rs index 1b9e90f8..812a8fc7 100644 --- a/binaries/cuprated/src/main.rs +++ b/binaries/cuprated/src/main.rs @@ -30,6 +30,7 @@ mod blockchain; mod commands; mod config; mod constants; +mod logging; mod p2p; mod rpc; mod signals; @@ -42,7 +43,7 @@ fn main() { let config = config::read_config_and_args(); - let log_level_handle = init_logging(&config); + logging::init_logging(&config); let rt = init_tokio_rt(); @@ -98,21 +99,10 @@ fn main() { let (command_tx, command_rx) = mpsc::channel(1); std::thread::spawn(|| commands::command_listener(command_tx)); - io_loop(command_rx, log_level_handle).await; + io_loop(command_rx).await; }); } -fn init_logging(config: &Config) -> Handle { - let (filter, handle) = tracing_subscriber::reload::Layer::new(config.tracing.level); - - tracing_subscriber::registry() - .with(filter) - .with(tracing_subscriber::fmt::Layer::default().with_target(false)) - .init(); - - handle -} - fn init_tokio_rt() -> tokio::runtime::Runtime { tokio::runtime::Builder::new_multi_thread() .enable_all() @@ -120,16 +110,11 @@ fn init_tokio_rt() -> tokio::runtime::Runtime { .unwrap() } -async fn io_loop( - mut incoming_commands: mpsc::Receiver, - tracing_level_handle: Handle, -) -> ! { +async fn io_loop(mut incoming_commands: mpsc::Receiver) -> ! { while let Some(command) = incoming_commands.recv().await { match command { Command::SetLog { level } => { - tracing_level_handle - .modify(|filter| *filter = level) - .unwrap(); + logging::modify_stdout_output(|filter| filter.level = level); println!("LOG LEVEL CHANGED: {level}"); } diff --git a/helper/src/fs.rs b/helper/src/fs.rs index 60d61a37..f1a64ecd 100644 --- a/helper/src/fs.rs +++ b/helper/src/fs.rs @@ -200,6 +200,21 @@ pub fn txpool_path(data_dir: &Path, network: Network) -> PathBuf { path_with_network(data_dir, network).join("txpool") } +/// Cuprate's logs directory. +/// +/// This is the PATH used for all Cuprate log files. +/// +/// ```rust +/// use cuprate_helper::{network::Network, fs::{CUPRATE_DATA_DIR, logs_path}}; +/// +/// assert_eq!(logs_path(&**CUPRATE_DATA_DIR, Network::Mainnet).as_path(), CUPRATE_DATA_DIR.join("logs")); +/// assert_eq!(logs_path(&**CUPRATE_DATA_DIR, Network::Stagenet).as_path(), CUPRATE_DATA_DIR.join(Network::Stagenet.to_string()).join("logs")); +/// assert_eq!(logs_path(&**CUPRATE_DATA_DIR, Network::Testnet).as_path(), CUPRATE_DATA_DIR.join(Network::Testnet.to_string()).join("logs")); +/// ``` +pub fn logs_path(data_dir: &Path, network: Network) -> PathBuf { + path_with_network(data_dir, network).join("logs") +} + /// Cuprate's address-book directory. /// /// This is the PATH used for any Cuprate address-book files. diff --git a/p2p/address-book/src/store.rs b/p2p/address-book/src/store.rs index 47994ae5..98521e97 100644 --- a/p2p/address-book/src/store.rs +++ b/p2p/address-book/src/store.rs @@ -42,7 +42,11 @@ pub(crate) fn save_peers_to_disk( let file = cfg .peer_store_directory .join(format!("{}_p2p_state", Z::NAME)); - spawn_blocking(move || fs::write(&file, &data)) + + spawn_blocking(move || { + fs::create_dir_all(file.parent().unwrap())?; + fs::write(&file, &data) + }) } pub(crate) async fn read_peers_from_disk(