Skip to content

Commit 22cb1df

Browse files
authored
Merge pull request #463 from enigbe/2025-02-test-logging-interface
Test logging interface
2 parents edb0560 + 56d5dc9 commit 22cb1df

8 files changed

+225
-57
lines changed

bindings/ldk_node.udl

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ interface Builder {
7474
void set_liquidity_source_lsps2(PublicKey node_id, SocketAddress address, string? token);
7575
void set_storage_dir_path(string storage_dir_path);
7676
void set_filesystem_logger(string? log_file_path, LogLevel? max_log_level);
77-
void set_log_facade_logger(LogLevel? max_log_level);
77+
void set_log_facade_logger();
7878
void set_custom_logger(LogWriter log_writer);
7979
void set_network(Network network);
8080
[Throws=BuildError]

src/builder.rs

+7-18
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ struct LiquiditySourceConfig {
113113
#[derive(Clone)]
114114
enum LogWriterConfig {
115115
File { log_file_path: Option<String>, max_log_level: Option<LogLevel> },
116-
Log { max_log_level: Option<LogLevel> },
116+
Log,
117117
Custom(Arc<dyn LogWriter>),
118118
}
119119

@@ -125,9 +125,7 @@ impl std::fmt::Debug for LogWriterConfig {
125125
.field("max_log_level", max_log_level)
126126
.field("log_file_path", log_file_path)
127127
.finish(),
128-
LogWriterConfig::Log { max_log_level } => {
129-
f.debug_tuple("Log").field(max_log_level).finish()
130-
},
128+
LogWriterConfig::Log => write!(f, "LogWriterConfig::Log"),
131129
LogWriterConfig::Custom(_) => {
132130
f.debug_tuple("Custom").field(&"<config internal to custom log writer>").finish()
133131
},
@@ -385,11 +383,8 @@ impl NodeBuilder {
385383
}
386384

387385
/// Configures the [`Node`] instance to write logs to the [`log`](https://crates.io/crates/log) facade.
388-
///
389-
/// If set, the `max_log_level` sets the maximum log level. Otherwise, the latter defaults to
390-
/// [`DEFAULT_LOG_LEVEL`].
391-
pub fn set_log_facade_logger(&mut self, max_log_level: Option<LogLevel>) -> &mut Self {
392-
self.log_writer_config = Some(LogWriterConfig::Log { max_log_level });
386+
pub fn set_log_facade_logger(&mut self) -> &mut Self {
387+
self.log_writer_config = Some(LogWriterConfig::Log);
393388
self
394389
}
395390

@@ -750,11 +745,8 @@ impl ArcedNodeBuilder {
750745
}
751746

752747
/// Configures the [`Node`] instance to write logs to the [`log`](https://crates.io/crates/log) facade.
753-
///
754-
/// If set, the `max_log_level` sets the maximum log level. Otherwise, the latter defaults to
755-
/// [`DEFAULT_LOG_LEVEL`].
756-
pub fn set_log_facade_logger(&self, log_level: Option<LogLevel>) {
757-
self.inner.write().unwrap().set_log_facade_logger(log_level);
748+
pub fn set_log_facade_logger(&self) {
749+
self.inner.write().unwrap().set_log_facade_logger();
758750
}
759751

760752
/// Configures the [`Node`] instance to write logs to the provided custom [`LogWriter`].
@@ -1421,10 +1413,7 @@ fn setup_logger(
14211413
Logger::new_fs_writer(log_file_path, max_log_level)
14221414
.map_err(|_| BuildError::LoggerSetupFailed)?
14231415
},
1424-
Some(LogWriterConfig::Log { max_log_level }) => {
1425-
let max_log_level = max_log_level.unwrap_or_else(|| DEFAULT_LOG_LEVEL);
1426-
Logger::new_log_facade(max_log_level)
1427-
},
1416+
Some(LogWriterConfig::Log) => Logger::new_log_facade(),
14281417

14291418
Some(LogWriterConfig::Custom(custom_log_writer)) => {
14301419
Logger::new_custom_writer(Arc::clone(&custom_log_writer))

src/logger.rs

+5-11
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ pub(crate) enum Writer {
108108
/// Writes logs to the file system.
109109
FileWriter { file_path: String, max_log_level: LogLevel },
110110
/// Forwards logs to the `log` facade.
111-
LogFacadeWriter { max_log_level: LogLevel },
111+
LogFacadeWriter,
112112
/// Forwards logs to a custom writer.
113113
CustomWriter(Arc<dyn LogWriter>),
114114
}
@@ -138,10 +138,7 @@ impl LogWriter for Writer {
138138
.write_all(log.as_bytes())
139139
.expect("Failed to write to log file")
140140
},
141-
Writer::LogFacadeWriter { max_log_level } => {
142-
if record.level < *max_log_level {
143-
return;
144-
}
141+
Writer::LogFacadeWriter => {
145142
macro_rules! log_with_level {
146143
($log_level:expr, $target: expr, $($args:tt)*) => {
147144
match $log_level {
@@ -186,8 +183,8 @@ impl Logger {
186183
Ok(Self { writer: Writer::FileWriter { file_path, max_log_level } })
187184
}
188185

189-
pub fn new_log_facade(max_log_level: LogLevel) -> Self {
190-
Self { writer: Writer::LogFacadeWriter { max_log_level } }
186+
pub fn new_log_facade() -> Self {
187+
Self { writer: Writer::LogFacadeWriter }
191188
}
192189

193190
pub fn new_custom_writer(log_writer: Arc<dyn LogWriter>) -> Self {
@@ -204,10 +201,7 @@ impl LdkLogger for Logger {
204201
}
205202
self.writer.log(record.into());
206203
},
207-
Writer::LogFacadeWriter { max_log_level } => {
208-
if record.level < *max_log_level {
209-
return;
210-
}
204+
Writer::LogFacadeWriter => {
211205
self.writer.log(record.into());
212206
},
213207
Writer::CustomWriter(_arc) => {

tests/common/logging.rs

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
use chrono::Utc;
2+
#[cfg(not(feature = "uniffi"))]
3+
use ldk_node::logger::LogRecord;
4+
use ldk_node::logger::{LogLevel, LogWriter};
5+
#[cfg(not(feature = "uniffi"))]
6+
use log::Record as LogFacadeRecord;
7+
use log::{Level as LogFacadeLevel, LevelFilter as LogFacadeLevelFilter, Log as LogFacadeLog};
8+
use std::sync::{Arc, Mutex};
9+
10+
#[derive(Clone)]
11+
pub(crate) enum TestLogWriter {
12+
FileWriter,
13+
LogFacade,
14+
Custom(Arc<dyn LogWriter>),
15+
}
16+
17+
impl Default for TestLogWriter {
18+
fn default() -> Self {
19+
TestLogWriter::FileWriter
20+
}
21+
}
22+
23+
pub(crate) struct MockLogFacadeLogger {
24+
logs: Arc<Mutex<Vec<String>>>,
25+
}
26+
27+
impl MockLogFacadeLogger {
28+
pub fn new() -> Self {
29+
Self { logs: Arc::new(Mutex::new(Vec::new())) }
30+
}
31+
32+
pub fn retrieve_logs(&self) -> Vec<String> {
33+
self.logs.lock().unwrap().to_vec()
34+
}
35+
}
36+
37+
impl LogFacadeLog for MockLogFacadeLogger {
38+
fn enabled(&self, _metadata: &log::Metadata) -> bool {
39+
true
40+
}
41+
42+
fn log(&self, record: &log::Record) {
43+
let message = format!(
44+
"{} {:<5} [{}:{}] {}",
45+
Utc::now().format("%Y-%m-%d %H:%M:%S"),
46+
record.level().to_string(),
47+
record.module_path().unwrap(),
48+
record.line().unwrap(),
49+
record.args()
50+
);
51+
println!("{message}");
52+
self.logs.lock().unwrap().push(message);
53+
}
54+
55+
fn flush(&self) {}
56+
}
57+
58+
#[cfg(not(feature = "uniffi"))]
59+
impl LogWriter for MockLogFacadeLogger {
60+
fn log<'a>(&self, record: LogRecord) {
61+
let record = MockLogRecord(record).into();
62+
LogFacadeLog::log(self, &record);
63+
}
64+
}
65+
66+
#[cfg(not(feature = "uniffi"))]
67+
struct MockLogRecord<'a>(LogRecord<'a>);
68+
struct MockLogLevel(LogLevel);
69+
70+
impl From<MockLogLevel> for LogFacadeLevel {
71+
fn from(level: MockLogLevel) -> Self {
72+
match level.0 {
73+
LogLevel::Gossip | LogLevel::Trace => LogFacadeLevel::Trace,
74+
LogLevel::Debug => LogFacadeLevel::Debug,
75+
LogLevel::Info => LogFacadeLevel::Info,
76+
LogLevel::Warn => LogFacadeLevel::Warn,
77+
LogLevel::Error => LogFacadeLevel::Error,
78+
}
79+
}
80+
}
81+
82+
#[cfg(not(feature = "uniffi"))]
83+
impl<'a> From<MockLogRecord<'a>> for LogFacadeRecord<'a> {
84+
fn from(log_record: MockLogRecord<'a>) -> Self {
85+
let log_record = log_record.0;
86+
let level = MockLogLevel(log_record.level).into();
87+
88+
let mut record_builder = LogFacadeRecord::builder();
89+
let record = record_builder
90+
.level(level)
91+
.module_path(Some(&log_record.module_path))
92+
.line(Some(log_record.line))
93+
.args(log_record.args);
94+
95+
record.build()
96+
}
97+
}
98+
99+
pub(crate) fn init_log_logger(level: LogFacadeLevelFilter) -> Arc<MockLogFacadeLogger> {
100+
let logger = Arc::new(MockLogFacadeLogger::new());
101+
log::set_boxed_logger(Box::new(logger.clone())).unwrap();
102+
log::set_max_level(level);
103+
104+
logger
105+
}
106+
107+
pub(crate) fn validate_log_entry(entry: &String) {
108+
let parts = entry.splitn(4, ' ').collect::<Vec<_>>();
109+
assert_eq!(parts.len(), 4);
110+
let (day, time, level, path_and_msg) = (parts[0], parts[1], parts[2], parts[3]);
111+
112+
let day_parts = day.split('-').collect::<Vec<_>>();
113+
assert_eq!(day_parts.len(), 3);
114+
let (year, month, day) = (day_parts[0], day_parts[1], day_parts[2]);
115+
assert!(year.len() == 4 && month.len() == 2 && day.len() == 2);
116+
assert!(
117+
year.chars().all(|c| c.is_digit(10))
118+
&& month.chars().all(|c| c.is_digit(10))
119+
&& day.chars().all(|c| c.is_digit(10))
120+
);
121+
122+
let time_parts = time.split(':').collect::<Vec<_>>();
123+
assert_eq!(time_parts.len(), 3);
124+
let (hour, minute, second) = (time_parts[0], time_parts[1], time_parts[2]);
125+
assert!(hour.len() == 2 && minute.len() == 2 && second.len() == 2);
126+
assert!(
127+
hour.chars().all(|c| c.is_digit(10))
128+
&& minute.chars().all(|c| c.is_digit(10))
129+
&& second.chars().all(|c| c.is_digit(10))
130+
);
131+
132+
assert!(["GOSSIP", "TRACE", "DEBUG", "INFO", "WARN", "ERROR"].contains(&level),);
133+
134+
let path = path_and_msg.split_whitespace().next().unwrap();
135+
assert!(path.contains('[') && path.contains(']'));
136+
let module_path = &path[1..path.len() - 1];
137+
let path_parts = module_path.rsplitn(2, ':').collect::<Vec<_>>();
138+
assert_eq!(path_parts.len(), 2);
139+
let (line_number, module_name) = (path_parts[0], path_parts[1]);
140+
assert!(module_name.chars().all(|c| c.is_alphanumeric() || c == '_' || c == ':'));
141+
assert!(line_number.chars().all(|c| c.is_digit(10)));
142+
143+
let msg_start_index = path_and_msg.find(']').unwrap() + 1;
144+
let msg = &path_and_msg[msg_start_index..];
145+
assert!(!msg.is_empty());
146+
}

tests/common/mod.rs

+35-16
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
#![cfg(any(test, cln_test, vss_test))]
99
#![allow(dead_code)]
1010

11+
pub(crate) mod logging;
12+
13+
use logging::TestLogWriter;
14+
1115
use ldk_node::config::{Config, EsploraSyncConfig};
1216
use ldk_node::io::sqlite_store::SqliteStore;
13-
use ldk_node::logger::LogLevel;
1417
use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus};
1518
use ldk_node::{
1619
Builder, CustomTlvRecord, Event, LightningBalance, Node, NodeError, PendingSweepBalance,
@@ -221,29 +224,29 @@ pub(crate) fn random_node_alias() -> Option<NodeAlias> {
221224
Some(NodeAlias(bytes))
222225
}
223226

224-
pub(crate) fn random_config(anchor_channels: bool) -> Config {
225-
let mut config = Config::default();
227+
pub(crate) fn random_config(anchor_channels: bool) -> TestConfig {
228+
let mut node_config = Config::default();
226229

227230
if !anchor_channels {
228-
config.anchor_channels_config = None;
231+
node_config.anchor_channels_config = None;
229232
}
230233

231-
config.network = Network::Regtest;
232-
println!("Setting network: {}", config.network);
234+
node_config.network = Network::Regtest;
235+
println!("Setting network: {}", node_config.network);
233236

234237
let rand_dir = random_storage_path();
235238
println!("Setting random LDK storage dir: {}", rand_dir.display());
236-
config.storage_dir_path = rand_dir.to_str().unwrap().to_owned();
239+
node_config.storage_dir_path = rand_dir.to_str().unwrap().to_owned();
237240

238241
let rand_listening_addresses = random_listening_addresses();
239242
println!("Setting random LDK listening addresses: {:?}", rand_listening_addresses);
240-
config.listening_addresses = Some(rand_listening_addresses);
243+
node_config.listening_addresses = Some(rand_listening_addresses);
241244

242245
let alias = random_node_alias();
243246
println!("Setting random LDK node alias: {:?}", alias);
244-
config.node_alias = alias;
247+
node_config.node_alias = alias;
245248

246-
config
249+
TestConfig { node_config, ..Default::default() }
247250
}
248251

249252
#[cfg(feature = "uniffi")]
@@ -257,6 +260,12 @@ pub(crate) enum TestChainSource<'a> {
257260
BitcoindRpc(&'a BitcoinD),
258261
}
259262

263+
#[derive(Clone, Default)]
264+
pub(crate) struct TestConfig {
265+
pub node_config: Config,
266+
pub log_writer: TestLogWriter,
267+
}
268+
260269
macro_rules! setup_builder {
261270
($builder: ident, $config: expr) => {
262271
#[cfg(feature = "uniffi")]
@@ -279,10 +288,11 @@ pub(crate) fn setup_two_nodes(
279288
println!("\n== Node B ==");
280289
let mut config_b = random_config(anchor_channels);
281290
if allow_0conf {
282-
config_b.trusted_peers_0conf.push(node_a.node_id());
291+
config_b.node_config.trusted_peers_0conf.push(node_a.node_id());
283292
}
284293
if anchor_channels && anchors_trusted_no_reserve {
285294
config_b
295+
.node_config
286296
.anchor_channels_config
287297
.as_mut()
288298
.unwrap()
@@ -294,9 +304,9 @@ pub(crate) fn setup_two_nodes(
294304
}
295305

296306
pub(crate) fn setup_node(
297-
chain_source: &TestChainSource, config: Config, seed_bytes: Option<Vec<u8>>,
307+
chain_source: &TestChainSource, config: TestConfig, seed_bytes: Option<Vec<u8>>,
298308
) -> TestNode {
299-
setup_builder!(builder, config);
309+
setup_builder!(builder, config.node_config);
300310
match chain_source {
301311
TestChainSource::Esplora(electrsd) => {
302312
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
@@ -315,14 +325,23 @@ pub(crate) fn setup_node(
315325
},
316326
}
317327

318-
let log_file_path = format!("{}/{}", config.storage_dir_path, "ldk_node.log");
319-
builder.set_filesystem_logger(Some(log_file_path), Some(LogLevel::Gossip));
328+
match &config.log_writer {
329+
TestLogWriter::FileWriter => {
330+
builder.set_filesystem_logger(None, None);
331+
},
332+
TestLogWriter::LogFacade => {
333+
builder.set_log_facade_logger();
334+
},
335+
TestLogWriter::Custom(custom_log_writer) => {
336+
builder.set_custom_logger(Arc::clone(custom_log_writer));
337+
},
338+
}
320339

321340
if let Some(seed) = seed_bytes {
322341
builder.set_entropy_seed_bytes(seed).unwrap();
323342
}
324343

325-
let test_sync_store = Arc::new(TestSyncStore::new(config.storage_dir_path.into()));
344+
let test_sync_store = Arc::new(TestSyncStore::new(config.node_config.storage_dir_path.into()));
326345
let node = builder.build_with_store(test_sync_store).unwrap();
327346
node.start().unwrap();
328347
assert!(node.status().is_running);

tests/integration_tests_cln.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ fn test_cln() {
4545

4646
// Setup LDK Node
4747
let config = common::random_config(true);
48-
let mut builder = Builder::from_config(config);
48+
let mut builder = Builder::from_config(config.node_config);
4949
builder.set_chain_source_esplora("http://127.0.0.1:3002".to_string(), None);
5050

5151
let node = builder.build().unwrap();

0 commit comments

Comments
 (0)