diff --git a/Cargo.toml b/Cargo.toml index 73fe0a1b7..d65cd8258 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ panic = 'abort' # Abort on panic [features] default = [] +log_relay = ["log"] [dependencies] lightning = { version = "0.0.125", features = ["std"] } @@ -75,7 +76,7 @@ libc = "0.2" uniffi = { version = "0.27.3", features = ["build"], optional = true } serde = { version = "1.0.210", default-features = false, features = ["std", "derive"] } serde_json = { version = "1.0.128", default-features = false, features = ["std"] } - +log = { version = "0.4.22", optional = true} vss-client = "0.3" prost = { version = "0.11.6", default-features = false} diff --git a/src/builder.rs b/src/builder.rs index fac2ae0c5..4cbe63e46 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -6,7 +6,10 @@ // accordance with one or both of these licenses. use crate::chain::{ChainSource, DEFAULT_ESPLORA_SERVER_URL}; -use crate::config::{default_user_config, Config, EsploraSyncConfig, WALLET_KEYS_SEED_LEN}; +use crate::config::{ + default_user_config, Config, EsploraSyncConfig, FormatterConfig, LoggerConfig, WriterConfig, + WriterType, WALLET_KEYS_SEED_LEN, +}; use crate::connection::ConnectionManager; use crate::event::EventQueue; @@ -16,7 +19,7 @@ use crate::io::sqlite_store::SqliteStore; use crate::io::utils::{read_node_metrics, write_node_metrics}; use crate::io::vss_store::VssStore; use crate::liquidity::LiquiditySource; -use crate::logger::{log_error, log_info, FilesystemLogger, Logger}; +use crate::logger::{build_formatter, log_error, log_info, LdkNodeLogger, Logger, Writer}; use crate::message_handler::NodeCustomMessageHandler; use crate::payment::store::PaymentStore; use crate::peer_store::PeerStore; @@ -27,8 +30,8 @@ use crate::types::{ }; use crate::wallet::persist::KVStoreWalletPersister; use crate::wallet::Wallet; +use crate::Node; use crate::{io, NodeMetrics}; -use crate::{LogLevel, Node}; use lightning::chain::{chainmonitor, BestBlock, Watch}; use lightning::io::Cursor; @@ -298,9 +301,15 @@ impl NodeBuilder { self } - /// Sets the log file path if the log file needs to live separate from the storage directory path. - pub fn set_log_file_path(&mut self, log_dir_path: String) -> &mut Self { - self.config.log_file_path = Some(log_dir_path); + /// Sets the logger's writer config. + pub fn set_log_writer_config(&mut self, writer_config: WriterConfig) -> &mut Self { + self.config.logger_config.writer = writer_config; + self + } + + /// Sets the logger's formatter config. + pub fn set_log_formatter_config(&mut self, formatter_config: FormatterConfig) -> &mut Self { + self.config.logger_config.formatter = formatter_config; self } @@ -333,12 +342,6 @@ impl NodeBuilder { Ok(self) } - /// Sets the level at which [`Node`] will log messages. - pub fn set_log_level(&mut self, level: LogLevel) -> &mut Self { - self.config.log_level = level; - self - } - /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. pub fn build(&self) -> Result { @@ -391,7 +394,7 @@ impl NodeBuilder { ) -> Result { use bitcoin::key::Secp256k1; - let logger = setup_logger(&self.config)?; + let logger = setup_logger(&self.config.logger_config)?; let seed_bytes = seed_bytes_from_config( &self.config, @@ -456,7 +459,7 @@ impl NodeBuilder { pub fn build_with_vss_store_and_header_provider( &self, vss_url: String, store_id: String, header_provider: Arc, ) -> Result { - let logger = setup_logger(&self.config)?; + let logger = setup_logger(&self.config.logger_config)?; let seed_bytes = seed_bytes_from_config( &self.config, @@ -488,7 +491,7 @@ impl NodeBuilder { /// Builds a [`Node`] instance according to the options previously configured. pub fn build_with_store(&self, kv_store: Arc) -> Result { - let logger = setup_logger(&self.config)?; + let logger = setup_logger(&self.config.logger_config)?; let seed_bytes = seed_bytes_from_config( &self.config, self.entropy_source_config.as_ref(), @@ -610,9 +613,14 @@ impl ArcedNodeBuilder { self.inner.write().unwrap().set_storage_dir_path(storage_dir_path); } - /// Sets the log file path if logs need to live separate from the storage directory path. - pub fn set_log_file_path(&self, log_file_path: String) { - self.inner.write().unwrap().set_log_file_path(log_file_path); + /// Sets the logger's writer config. + pub fn set_log_writer_config(&mut self, writer_config: WriterConfig) -> &mut Self { + self.inner.write().unwrap().set_log_writer_config(writer_config); + } + + /// Sets the logger's formatter config. + pub fn set_log_formatter_config(&mut self, formatter_config: FormatterConfig) -> &mut Self { + self.inner.write().unwrap().set_log_formatter_config(formatter_config); } /// Sets the Bitcoin network used. @@ -635,11 +643,6 @@ impl ArcedNodeBuilder { self.inner.write().unwrap().set_node_alias(node_alias).map(|_| ()) } - /// Sets the level at which [`Node`] will log messages. - pub fn set_log_level(&self, level: LogLevel) { - self.inner.write().unwrap().set_log_level(level); - } - /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. pub fn build(&self) -> Result, BuildError> { @@ -734,7 +737,7 @@ fn build_with_store_internal( config: Arc, chain_data_source_config: Option<&ChainDataSourceConfig>, gossip_source_config: Option<&GossipSourceConfig>, liquidity_source_config: Option<&LiquiditySourceConfig>, seed_bytes: [u8; 64], - logger: Arc, kv_store: Arc, + logger: Arc, kv_store: Arc, ) -> Result { // Initialize the status fields. let is_listening = Arc::new(AtomicBool::new(false)); @@ -1231,23 +1234,28 @@ fn build_with_store_internal( }) } -/// Sets up the node logger, creating a new log file if it does not exist, or utilizing -/// the existing log file. -fn setup_logger(config: &Config) -> Result, BuildError> { - let log_file_path = match &config.log_file_path { - Some(log_dir) => String::from(log_dir), - None => format!("{}/{}", config.storage_dir_path.clone(), "ldk_node.log"), +/// Sets up the node logger. +fn setup_logger(config: &LoggerConfig) -> Result, BuildError> { + let level = match &config.writer.writer_type { + WriterType::File(file_writer_config) => file_writer_config.level, + WriterType::LogRelay(log_relay_writer_config) => log_relay_writer_config.level, + WriterType::Custom(custom_writer_config) => custom_writer_config.level, }; - Ok(Arc::new( - FilesystemLogger::new(log_file_path, config.log_level) - .map_err(|_| BuildError::LoggerSetupFailed)?, - )) + let writer = + Writer::new(&config.writer.writer_type).map_err(|_e| BuildError::LoggerSetupFailed)?; + + let formatter = build_formatter(config.formatter.clone()); + + let ldk_node_logger = + LdkNodeLogger::new(level, formatter, writer).map_err(|_e| BuildError::LoggerSetupFailed)?; + + Ok(Arc::new(ldk_node_logger)) } fn seed_bytes_from_config( config: &Config, entropy_source_config: Option<&EntropySourceConfig>, - logger: Arc, + logger: Arc, ) -> Result<[u8; 64], BuildError> { match entropy_source_config { Some(EntropySourceConfig::SeedBytes(bytes)) => Ok(bytes.clone()), @@ -1269,7 +1277,7 @@ fn seed_bytes_from_config( } fn derive_vss_xprv( - config: Arc, seed_bytes: &[u8; 64], logger: Arc, + config: Arc, seed_bytes: &[u8; 64], logger: Arc, ) -> Result { use bitcoin::key::Secp256k1; diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 3c5ffc27b..f8cb4cd77 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -21,7 +21,7 @@ use crate::fee_estimator::{ ConfirmationTarget, OnchainFeeEstimator, }; use crate::io::utils::write_node_metrics; -use crate::logger::{log_bytes, log_error, log_info, log_trace, FilesystemLogger, Logger}; +use crate::logger::{log_bytes, log_error, log_info, log_trace, LdkNodeLogger, Logger}; use crate::types::{Broadcaster, ChainMonitor, ChannelManager, DynStore, Sweeper, Wallet}; use crate::{Error, NodeMetrics}; @@ -112,13 +112,13 @@ pub(crate) enum ChainSource { esplora_client: EsploraAsyncClient, onchain_wallet: Arc, onchain_wallet_sync_status: Mutex, - tx_sync: Arc>>, + tx_sync: Arc>>, lightning_wallet_sync_status: Mutex, fee_estimator: Arc, tx_broadcaster: Arc, kv_store: Arc, config: Arc, - logger: Arc, + logger: Arc, node_metrics: Arc>, }, BitcoindRpc { @@ -131,7 +131,7 @@ pub(crate) enum ChainSource { tx_broadcaster: Arc, kv_store: Arc, config: Arc, - logger: Arc, + logger: Arc, node_metrics: Arc>, }, } @@ -140,7 +140,7 @@ impl ChainSource { pub(crate) fn new_esplora( server_url: String, sync_config: EsploraSyncConfig, onchain_wallet: Arc, fee_estimator: Arc, tx_broadcaster: Arc, - kv_store: Arc, config: Arc, logger: Arc, + kv_store: Arc, config: Arc, logger: Arc, node_metrics: Arc>, ) -> Self { let mut client_builder = esplora_client::Builder::new(&server_url); @@ -170,7 +170,7 @@ impl ChainSource { host: String, port: u16, rpc_user: String, rpc_password: String, onchain_wallet: Arc, fee_estimator: Arc, tx_broadcaster: Arc, kv_store: Arc, config: Arc, - logger: Arc, node_metrics: Arc>, + logger: Arc, node_metrics: Arc>, ) -> Self { let bitcoind_rpc_client = Arc::new(BitcoindRpcClient::new(host, port, rpc_user, rpc_password)); @@ -1123,7 +1123,7 @@ impl Filter for ChainSource { fn periodically_archive_fully_resolved_monitors( channel_manager: Arc, chain_monitor: Arc, - kv_store: Arc, logger: Arc, node_metrics: Arc>, + kv_store: Arc, logger: Arc, node_metrics: Arc>, ) -> Result<(), Error> { let mut locked_node_metrics = node_metrics.write().unwrap(); let cur_height = channel_manager.current_best_block().height; diff --git a/src/config.rs b/src/config.rs index 00b147e21..d308a856a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,6 +7,7 @@ //! Objects for configuring the node. +use crate::logger::LogWriter; use crate::payment::SendingParameters; use lightning::ln::msgs::SocketAddress; @@ -14,21 +15,23 @@ use lightning::routing::gossip::NodeAlias; use lightning::util::config::ChannelConfig as LdkChannelConfig; use lightning::util::config::MaxDustHTLCExposure as LdkMaxDustHTLCExposure; use lightning::util::config::UserConfig; -use lightning::util::logger::Level as LogLevel; +use lightning::util::logger::Level; use bitcoin::secp256k1::PublicKey; use bitcoin::Network; +use std::sync::Arc; use std::time::Duration; // Config defaults -const DEFAULT_STORAGE_DIR_PATH: &str = "/tmp/ldk_node/"; +const DEFAULT_STORAGE_DIR_PATH: &str = "/tmp/ldk_node"; +const DEFAULT_LOG_FILE_PATH: &str = "/tmp/ldk_node/ldk_node.log"; const DEFAULT_NETWORK: Network = Network::Bitcoin; const DEFAULT_BDK_WALLET_SYNC_INTERVAL_SECS: u64 = 80; const DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS: u64 = 30; const DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS: u64 = 60 * 10; const DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER: u64 = 3; -const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Debug; +const DEFAULT_LOG_LEVEL: Level = Level::Debug; const DEFAULT_ANCHOR_PER_CHANNEL_RESERVE_SATS: u64 = 25_000; // The 'stop gap' parameter used by BDK's wallet sync. This seems to configure the threshold @@ -103,11 +106,8 @@ pub(crate) const WALLET_KEYS_SEED_LEN: usize = 64; pub struct Config { /// The path where the underlying LDK and BDK persist their data. pub storage_dir_path: String, - /// The path where logs are stored. - /// - /// If set to `None`, logs can be found in `ldk_node.log` in the [`Config::storage_dir_path`] - /// directory. - pub log_file_path: Option, + /// The configuration options for the logger. + pub logger_config: LoggerConfig, /// The used Bitcoin network. pub network: Network, /// The addresses on which the node will listen for incoming connections. @@ -133,10 +133,6 @@ pub struct Config { /// Channels with available liquidity less than the required amount times this value won't be /// used to send pre-flight probes. pub probing_liquidity_limit_multiplier: u64, - /// The level at which we log messages. - /// - /// Any messages below this level will be excluded from the logs. - pub log_level: LogLevel, /// Configuration options pertaining to Anchor channels, i.e., channels for which the /// `option_anchors_zero_fee_htlc_tx` channel type is negotiated. /// @@ -168,12 +164,11 @@ impl Default for Config { fn default() -> Self { Self { storage_dir_path: DEFAULT_STORAGE_DIR_PATH.to_string(), - log_file_path: None, + logger_config: LoggerConfig::default(), network: DEFAULT_NETWORK, listening_addresses: None, trusted_peers_0conf: Vec::new(), probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER, - log_level: DEFAULT_LOG_LEVEL, anchor_channels_config: Some(AnchorChannelsConfig::default()), sending_parameters: None, node_alias: None, @@ -181,6 +176,109 @@ impl Default for Config { } } +/// Logger configuration. +#[derive(Debug, Clone)] +pub struct LoggerConfig { + /// Writer configuration. + pub writer: WriterConfig, + /// Formatter configuration. + pub formatter: FormatterConfig, +} + +impl Default for LoggerConfig { + fn default() -> Self { + Self { writer: WriterConfig::default(), formatter: FormatterConfig::default() } + } +} + +/// Logger formatter configuration. +#[derive(Debug, Clone)] +pub struct FormatterConfig { + /// Specifies if timestamps should be included in the log messages. + pub include_timestamp: bool, + /// Specifies timestamp format , e.g., "%Y-%m-%d %H:%M:%S". + pub timestamp_format: Option, + /// Specifies if log levels should be included in the log messages. + pub include_level: bool, + /// Specifies the template for log message format, e.g., "{timestamp} [{level}] {message}". + pub message_template: Option, +} + +impl Default for FormatterConfig { + fn default() -> Self { + Self { + include_timestamp: true, + timestamp_format: Some("%Y-%m-%d %H:%M:%S".to_string()), + include_level: true, + message_template: Some( + "{timestamp} {level} [{module_path}:{line}] {message}\n".to_string(), + ), + } + } +} + +/// Logger writer configuration. +#[derive(Debug, Clone)] +pub struct WriterConfig { + /// Writer type for the logger. + pub writer_type: WriterType, +} + +impl Default for WriterConfig { + fn default() -> Self { + WriterConfig { + writer_type: WriterType::File(FileWriterConfig::new( + DEFAULT_LOG_FILE_PATH, + DEFAULT_LOG_LEVEL, + )), + } + } +} + +/// Log writer configuration type. +#[derive(Debug, Clone)] +pub enum WriterType { + /// Wraps configuration options for logging to the filesystem. + File(FileWriterConfig), + /// Wraps configuration options for relaying logs to [`log`]. + LogRelay(LogRelayWriterConfig), + /// Wraps configuration options for relaying logs to a custom logger. + Custom(CustomWriterConfig), +} + +/// Configuration for writing to the filesystem. +#[derive(Debug, Clone)] +pub struct FileWriterConfig { + /// Specifies the file path for the logs. + pub log_file_path: String, + /// Specifies the log level. + pub level: Level, +} + +impl FileWriterConfig { + /// Creates a new configuration given the path to the log file + /// and the log level. + pub fn new(log_file_path: &str, level: Level) -> Self { + Self { log_file_path: log_file_path.to_string(), level } + } +} + +/// Configuration options for [`log`]'s writer. +#[derive(Debug, Clone)] +pub struct LogRelayWriterConfig { + /// Specifies the log level. + pub level: Level, +} + +/// Configuration options for a custom log writer. +#[derive(Debug, Clone)] +pub struct CustomWriterConfig { + /// Pointer to any custom log writer. + pub inner: Arc, + /// Specifies the log level. + pub level: Level, +} + /// Configuration options pertaining to 'Anchor' channels, i.e., channels for which the /// `option_anchors_zero_fee_htlc_tx` channel type is negotiated. /// diff --git a/src/gossip.rs b/src/gossip.rs index 450b5b5ee..65ce6fe92 100644 --- a/src/gossip.rs +++ b/src/gossip.rs @@ -6,7 +6,7 @@ // accordance with one or both of these licenses. use crate::config::RGS_SYNC_TIMEOUT_SECS; -use crate::logger::{log_trace, FilesystemLogger, Logger}; +use crate::logger::{log_trace, LdkNodeLogger, Logger}; use crate::types::{GossipSync, Graph, P2PGossipSync, RapidGossipSync}; use crate::Error; @@ -24,12 +24,12 @@ pub(crate) enum GossipSource { gossip_sync: Arc, server_url: String, latest_sync_timestamp: AtomicU32, - logger: Arc, + logger: Arc, }, } impl GossipSource { - pub fn new_p2p(network_graph: Arc, logger: Arc) -> Self { + pub fn new_p2p(network_graph: Arc, logger: Arc) -> Self { let gossip_sync = Arc::new(P2PGossipSync::new( network_graph, None::>, @@ -40,7 +40,7 @@ impl GossipSource { pub fn new_rgs( server_url: String, latest_sync_timestamp: u32, network_graph: Arc, - logger: Arc, + logger: Arc, ) -> Self { let gossip_sync = Arc::new(RapidGossipSync::new(network_graph, Arc::clone(&logger))); let latest_sync_timestamp = AtomicU32::new(latest_sync_timestamp); diff --git a/src/io/utils.rs b/src/io/utils.rs index 218fec473..e557ae10f 100644 --- a/src/io/utils.rs +++ b/src/io/utils.rs @@ -13,7 +13,7 @@ use crate::fee_estimator::OnchainFeeEstimator; use crate::io::{ NODE_METRICS_KEY, NODE_METRICS_PRIMARY_NAMESPACE, NODE_METRICS_SECONDARY_NAMESPACE, }; -use crate::logger::{log_error, FilesystemLogger}; +use crate::logger::{log_error, LdkNodeLogger}; use crate::peer_store::PeerStore; use crate::sweep::DeprecatedSpendableOutputInfo; use crate::types::{Broadcaster, DynStore, KeysManager, Sweeper}; @@ -226,7 +226,7 @@ where pub(crate) fn read_output_sweeper( broadcaster: Arc, fee_estimator: Arc, chain_data_source: Arc, keys_manager: Arc, kv_store: Arc, - logger: Arc, + logger: Arc, ) -> Result { let mut reader = Cursor::new(kv_store.read( OUTPUT_SWEEPER_PERSISTENCE_PRIMARY_NAMESPACE, @@ -600,7 +600,7 @@ impl_read_write_change_set_type!( // Reads the full BdkWalletChangeSet or returns default fields pub(crate) fn read_bdk_wallet_change_set( - kv_store: Arc, logger: Arc, + kv_store: Arc, logger: Arc, ) -> Result, std::io::Error> { let mut change_set = BdkWalletChangeSet::default(); diff --git a/src/lib.rs b/src/lib.rs index cce4352c7..26b61553b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,7 +142,7 @@ use types::{ }; pub use types::{ChannelDetails, PeerDetails, UserChannelId}; -use logger::{log_error, log_info, log_trace, FilesystemLogger, Logger}; +use logger::{log_error, log_info, log_trace, LdkNodeLogger, Logger}; use lightning::chain::BestBlock; use lightning::events::bump_transaction::Wallet as LdkWallet; @@ -180,23 +180,23 @@ pub struct Node { wallet: Arc, chain_source: Arc, tx_broadcaster: Arc, - event_queue: Arc>>, + event_queue: Arc>>, channel_manager: Arc, chain_monitor: Arc, output_sweeper: Arc, peer_manager: Arc, onion_messenger: Arc, - connection_manager: Arc>>, + connection_manager: Arc>>, keys_manager: Arc, network_graph: Arc, gossip_source: Arc, - liquidity_source: Option>>>, + liquidity_source: Option>>>, kv_store: Arc, - logger: Arc, + logger: Arc, _router: Arc, scorer: Arc>, - peer_store: Arc>>, - payment_store: Arc>>, + peer_store: Arc>>, + payment_store: Arc>>, is_listening: Arc, node_metrics: Arc>, } diff --git a/src/logger.rs b/src/logger.rs index bde4faff0..12c1fcccd 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -11,54 +11,177 @@ pub(crate) use lightning::{log_bytes, log_debug, log_error, log_info, log_trace} use lightning::util::logger::{Level, Record}; use chrono::Utc; +#[cfg(feature = "log_relay")] +use log::{debug, error, info, trace, warn}; +use std::fmt::Debug; use std::fs; use std::io::Write; use std::path::Path; +use std::sync::{Arc, Mutex}; -pub(crate) struct FilesystemLogger { - file_path: String, - level: Level, +use crate::config::{FormatterConfig, WriterType}; + +/// [`LogWriter`] trait to write/forward/relay logs to different destinations +/// such as the filesystem, and other loggers. +pub trait LogWriter: Debug + Send + Sync { + /// Write log to destination. + fn write(&self, level: Level, message: &str); +} + +/// [`LdkNodeLogger`] writer variants. +#[derive(Debug)] +pub enum Writer { + /// Writes logs to filesystem. + FileWriter { log_file: Mutex }, + /// Relays logs to [`log`] logger. + #[cfg(feature = "log_relay")] + LogRelayWriter, + /// Forwards logs to a custom logger. + CustomWriter { inner: Arc }, +} + +impl Writer { + /// Creates a new writer given the writer's type. + pub fn new(writer_type: &WriterType) -> Result { + match &writer_type { + // Initial logic for Writer that writes directly to a + // specified file on the filesystem. + WriterType::File(file_writer_config) => { + let log_file_path = &file_writer_config.log_file_path; + if let Some(parent_dir) = Path::new(log_file_path).parent() { + fs::create_dir_all(parent_dir).map_err(|e| { + eprintln!("ERROR: Failed to create log file directory: {}", e); + () + })?; + } + + let log_file = Mutex::new( + fs::OpenOptions::new().create(true).append(true).open(&log_file_path).map_err( + |e| { + eprintln!("ERROR: Failed to open log file: {}", e); + () + }, + )?, + ); + let writer = Writer::FileWriter { log_file }; + + Ok(writer) + }, + // Initial logic for Writer that forwards to any logger that + // implements the `log` facade. + #[cfg(feature = "log_relay")] + WriterType::LogRelay(_log_relay_writer_config) => Ok(Writer::LogRelayWriter), + // Initial logic for Writer that forwards to any custom logger. + WriterType::Custom(custom_writer_config) => { + Ok(Writer::CustomWriter { inner: custom_writer_config.inner.clone() }) + }, + #[cfg(not(feature = "log_relay"))] + _ => { + eprintln!("ERROR: log_relay feature is not enabled"); + Err(()) + }, + } + } } -impl FilesystemLogger { - /// Creates a new filesystem logger given the path to the log file and the log level. - pub(crate) fn new(log_file_path: String, level: Level) -> Result { - if let Some(parent_dir) = Path::new(&log_file_path).parent() { - fs::create_dir_all(parent_dir) - .map_err(|e| eprintln!("ERROR: Failed to create log parent directory: {}", e))?; - - // make sure the file exists. - fs::OpenOptions::new() - .create(true) - .append(true) - .open(&log_file_path) - .map_err(|e| eprintln!("ERROR: Failed to open log file: {}", e))?; +impl LogWriter for Writer { + fn write(&self, level: Level, message: &str) { + match self { + Writer::FileWriter { log_file } => log_file + .lock() + .expect("log file lock poisoned") + .write_all(message.as_bytes()) + .expect("Failed to write to log file"), + #[cfg(feature = "log_relay")] + Writer::LogRelayWriter => match level { + Level::Gossip => { + // trace!(..) used for gossip logs here. + trace!("{message}") + }, + Level::Trace => trace!("{message}"), + Level::Debug => debug!("{message}"), + Level::Info => info!("{message}"), + Level::Warn => warn!("{message}"), + Level::Error => error!("{message}"), + }, + Writer::CustomWriter { inner } => { + inner.write(level, message); + }, } + } +} + +pub type Formatter = Box String + Send + Sync>; + +/// Logger for LDK Node. +pub struct LdkNodeLogger { + /// Specifies the log level. + level: Level, + /// Specifies the logger's formatter. + formatter: Formatter, + /// Specifies the logger's writer. + writer: Writer, +} - Ok(Self { file_path: log_file_path, level }) +impl LdkNodeLogger { + pub fn new( + level: Level, formatter: Box String + Send + Sync>, writer: Writer, + ) -> Result { + Ok(Self { level, formatter, writer }) } } -impl Logger for FilesystemLogger { + +impl Debug for LdkNodeLogger { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "LdkNodeLogger level: {}", self.level) + } +} + +impl Logger for LdkNodeLogger { fn log(&self, record: Record) { if record.level < self.level { return; } - let raw_log = record.args.to_string(); - let log = format!( - "{} {:<5} [{}:{}] {}\n", - Utc::now().format("%Y-%m-%d %H:%M:%S"), - record.level.to_string(), - record.module_path, - record.line, - raw_log - ); - fs::OpenOptions::new() - .create(true) - .append(true) - .open(self.file_path.clone()) - .expect("Failed to open log file") - .write_all(log.as_bytes()) - .expect("Failed to write to log file") + let message = (self.formatter)(&record); + self.writer.write(self.level, &message) } } + +/// Builds a formatter given the formatter's configuration options. +pub(crate) fn build_formatter(formatter_config: FormatterConfig) -> Formatter { + let fn_closure = move |record: &Record| { + let raw_log = record.args.to_string(); + + let ts_format = formatter_config.timestamp_format.as_deref().unwrap_or("%Y-%m-%d %H:%M:%S"); + + let msg_tmpl = formatter_config + .message_template + .as_deref() + .unwrap_or("{timestamp} {level:<5} [{module_path}:{line}] {message}\n"); + + let timestamp = if formatter_config.include_timestamp { + Utc::now().format(&ts_format).to_string() + } else { + String::new() + }; + + let level = if formatter_config.include_level { + format!("{:<5}", record.level) + } else { + String::new() + }; + + let mut log = msg_tmpl.to_string(); + log = log + .replace("{timestamp}", ×tamp) + .replace("{level}", &level) + .replace("{module_path}", &record.module_path) + .replace("{line}", &format!("{}", record.line)) + .replace("{message}", &raw_log); + + log + }; + + Box::new(fn_closure) +} diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index 708c127bd..8dc788a8a 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -13,7 +13,7 @@ use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT}; use crate::connection::ConnectionManager; use crate::error::Error; use crate::liquidity::LiquiditySource; -use crate::logger::{log_error, log_info, FilesystemLogger, Logger}; +use crate::logger::{log_error, log_info, LdkNodeLogger, Logger}; use crate::payment::store::{ LSPFeeLimits, PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus, PaymentStore, @@ -48,25 +48,25 @@ use std::time::SystemTime; pub struct Bolt11Payment { runtime: Arc>>>, channel_manager: Arc, - connection_manager: Arc>>, + connection_manager: Arc>>, keys_manager: Arc, - liquidity_source: Option>>>, - payment_store: Arc>>, - peer_store: Arc>>, + liquidity_source: Option>>>, + payment_store: Arc>>, + peer_store: Arc>>, config: Arc, - logger: Arc, + logger: Arc, } impl Bolt11Payment { pub(crate) fn new( runtime: Arc>>>, channel_manager: Arc, - connection_manager: Arc>>, + connection_manager: Arc>>, keys_manager: Arc, - liquidity_source: Option>>>, - payment_store: Arc>>, - peer_store: Arc>>, config: Arc, - logger: Arc, + liquidity_source: Option>>>, + payment_store: Arc>>, + peer_store: Arc>>, config: Arc, + logger: Arc, ) -> Self { Self { runtime, diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index 90024b7d3..05bedeb83 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -11,7 +11,7 @@ use crate::config::LDK_PAYMENT_RETRY_TIMEOUT; use crate::error::Error; -use crate::logger::{log_error, log_info, FilesystemLogger, Logger}; +use crate::logger::{log_error, log_info, LdkNodeLogger, Logger}; use crate::payment::store::{ PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, PaymentStore, }; @@ -39,15 +39,15 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; pub struct Bolt12Payment { runtime: Arc>>>, channel_manager: Arc, - payment_store: Arc>>, - logger: Arc, + payment_store: Arc>>, + logger: Arc, } impl Bolt12Payment { pub(crate) fn new( runtime: Arc>>>, - channel_manager: Arc, - payment_store: Arc>>, logger: Arc, + channel_manager: Arc, payment_store: Arc>>, + logger: Arc, ) -> Self { Self { runtime, channel_manager, payment_store, logger } } diff --git a/src/payment/onchain.rs b/src/payment/onchain.rs index d46eba2b5..32a87a3d5 100644 --- a/src/payment/onchain.rs +++ b/src/payment/onchain.rs @@ -9,7 +9,7 @@ use crate::config::Config; use crate::error::Error; -use crate::logger::{log_info, FilesystemLogger, Logger}; +use crate::logger::{log_info, LdkNodeLogger, Logger}; use crate::types::{ChannelManager, Wallet}; use crate::wallet::OnchainSendAmount; @@ -27,13 +27,13 @@ pub struct OnchainPayment { wallet: Arc, channel_manager: Arc, config: Arc, - logger: Arc, + logger: Arc, } impl OnchainPayment { pub(crate) fn new( runtime: Arc>>>, wallet: Arc, - channel_manager: Arc, config: Arc, logger: Arc, + channel_manager: Arc, config: Arc, logger: Arc, ) -> Self { Self { runtime, wallet, channel_manager, config, logger } } diff --git a/src/payment/spontaneous.rs b/src/payment/spontaneous.rs index 3be244bb5..581f54fe7 100644 --- a/src/payment/spontaneous.rs +++ b/src/payment/spontaneous.rs @@ -9,7 +9,7 @@ use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT}; use crate::error::Error; -use crate::logger::{log_error, log_info, FilesystemLogger, Logger}; +use crate::logger::{log_error, log_info, LdkNodeLogger, Logger}; use crate::payment::store::{ PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, PaymentStore, }; @@ -37,17 +37,17 @@ pub struct SpontaneousPayment { runtime: Arc>>>, channel_manager: Arc, keys_manager: Arc, - payment_store: Arc>>, + payment_store: Arc>>, config: Arc, - logger: Arc, + logger: Arc, } impl SpontaneousPayment { pub(crate) fn new( runtime: Arc>>>, channel_manager: Arc, keys_manager: Arc, - payment_store: Arc>>, config: Arc, - logger: Arc, + payment_store: Arc>>, config: Arc, + logger: Arc, ) -> Self { Self { runtime, channel_manager, keys_manager, payment_store, config, logger } } diff --git a/src/payment/unified_qr.rs b/src/payment/unified_qr.rs index 88d372456..c36b8937a 100644 --- a/src/payment/unified_qr.rs +++ b/src/payment/unified_qr.rs @@ -12,7 +12,7 @@ //! [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md //! [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md use crate::error::Error; -use crate::logger::{log_error, FilesystemLogger, Logger}; +use crate::logger::{log_error, LdkNodeLogger, Logger}; use crate::payment::{Bolt11Payment, Bolt12Payment, OnchainPayment}; use crate::Config; @@ -50,13 +50,13 @@ pub struct UnifiedQrPayment { bolt11_invoice: Arc, bolt12_payment: Arc, config: Arc, - logger: Arc, + logger: Arc, } impl UnifiedQrPayment { pub(crate) fn new( onchain_payment: Arc, bolt11_invoice: Arc, - bolt12_payment: Arc, config: Arc, logger: Arc, + bolt12_payment: Arc, config: Arc, logger: Arc, ) -> Self { Self { onchain_payment, bolt11_invoice, bolt12_payment, config, logger } } diff --git a/src/types.rs b/src/types.rs index 9fae37e18..152c8513e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -8,7 +8,7 @@ use crate::chain::ChainSource; use crate::config::ChannelConfig; use crate::fee_estimator::OnchainFeeEstimator; -use crate::logger::FilesystemLogger; +use crate::logger::LdkNodeLogger; use crate::message_handler::NodeCustomMessageHandler; use lightning::chain::chainmonitor; @@ -38,7 +38,7 @@ pub(crate) type ChainMonitor = chainmonitor::ChainMonitor< Arc, Arc, Arc, - Arc, + Arc, Arc, >; @@ -47,8 +47,8 @@ pub(crate) type PeerManager = lightning::ln::peer_handler::PeerManager< Arc, Arc, Arc, - Arc, - Arc>>, + Arc, + Arc>>, Arc, >; @@ -63,51 +63,51 @@ pub(crate) type ChannelManager = lightning::ln::channelmanager::ChannelManager< Arc, Arc, Arc, - Arc, + Arc, >; -pub(crate) type Broadcaster = crate::tx_broadcaster::TransactionBroadcaster>; +pub(crate) type Broadcaster = crate::tx_broadcaster::TransactionBroadcaster>; pub(crate) type Wallet = - crate::wallet::Wallet, Arc, Arc>; + crate::wallet::Wallet, Arc, Arc>; pub(crate) type KeysManager = crate::wallet::WalletKeysManager< Arc, Arc, - Arc, + Arc, >; pub(crate) type Router = DefaultRouter< Arc, - Arc, + Arc, Arc, Arc>, ProbabilisticScoringFeeParameters, Scorer, >; -pub(crate) type Scorer = ProbabilisticScorer, Arc>; +pub(crate) type Scorer = ProbabilisticScorer, Arc>; -pub(crate) type Graph = gossip::NetworkGraph>; +pub(crate) type Graph = gossip::NetworkGraph>; pub(crate) type UtxoLookup = dyn lightning::routing::utxo::UtxoLookup + Send + Sync; pub(crate) type P2PGossipSync = - lightning::routing::gossip::P2PGossipSync, Arc, Arc>; + lightning::routing::gossip::P2PGossipSync, Arc, Arc>; pub(crate) type RapidGossipSync = - lightning_rapid_gossip_sync::RapidGossipSync, Arc>; + lightning_rapid_gossip_sync::RapidGossipSync, Arc>; pub(crate) type GossipSync = lightning_background_processor::GossipSync< Arc, Arc, Arc, Arc, - Arc, + Arc, >; pub(crate) type OnionMessenger = lightning::onion_message::messenger::OnionMessenger< Arc, Arc, - Arc, + Arc, Arc, Arc, Arc, @@ -117,7 +117,7 @@ pub(crate) type OnionMessenger = lightning::onion_message::messenger::OnionMesse pub(crate) type MessageRouter = lightning::onion_message::messenger::DefaultMessageRouter< Arc, - Arc, + Arc, Arc, >; @@ -127,16 +127,16 @@ pub(crate) type Sweeper = OutputSweeper< Arc, Arc, Arc, - Arc, + Arc, Arc, >; pub(crate) type BumpTransactionEventHandler = lightning::events::bump_transaction::BumpTransactionEventHandler< Arc, - Arc, Arc>>, + Arc, Arc>>, Arc, - Arc, + Arc, >; /// A local, potentially user-provided, identifier of a channel. diff --git a/src/wallet/persist.rs b/src/wallet/persist.rs index 06af541a2..5d2f627df 100644 --- a/src/wallet/persist.rs +++ b/src/wallet/persist.rs @@ -10,7 +10,7 @@ use crate::io::utils::{ write_bdk_wallet_indexer, write_bdk_wallet_local_chain, write_bdk_wallet_network, write_bdk_wallet_tx_graph, }; -use crate::logger::{log_error, FilesystemLogger}; +use crate::logger::{log_error, LdkNodeLogger}; use crate::types::DynStore; use lightning::util::logger::Logger; @@ -22,11 +22,11 @@ use std::sync::Arc; pub(crate) struct KVStoreWalletPersister { latest_change_set: Option, kv_store: Arc, - logger: Arc, + logger: Arc, } impl KVStoreWalletPersister { - pub(crate) fn new(kv_store: Arc, logger: Arc) -> Self { + pub(crate) fn new(kv_store: Arc, logger: Arc) -> Self { Self { latest_change_set: None, kv_store, logger } } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 26aff3d11..a3a16be04 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -11,7 +11,7 @@ use ldk_node::config::{Config, EsploraSyncConfig}; use ldk_node::io::sqlite_store::SqliteStore; use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus}; -use ldk_node::{Builder, Event, LightningBalance, LogLevel, Node, NodeError, PendingSweepBalance}; +use ldk_node::{Builder, Event, LightningBalance, Node, NodeError, PendingSweepBalance}; use lightning::ln::msgs::SocketAddress; use lightning::ln::{PaymentHash, PaymentPreimage}; @@ -231,8 +231,6 @@ pub(crate) fn random_config(anchor_channels: bool) -> Config { println!("Setting random LDK node alias: {:?}", alias); config.node_alias = alias; - config.log_level = LogLevel::Gossip; - config } diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 31990440e..948a2adbb 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -234,8 +234,8 @@ fn start_stop_reinit() { node.sync_wallets().unwrap(); assert_eq!(node.list_balances().spendable_onchain_balance_sats, expected_amount.to_sat()); - let log_file = format!("{}/ldk_node.log", config.clone().storage_dir_path); - assert!(std::path::Path::new(&log_file).exists()); + let log_conf = &config.logger_config; + assert_eq!(log_conf.formatter.include_level, true); node.stop().unwrap(); assert_eq!(node.stop(), Err(NodeError::NotRunning));