ldk_node/
logger.rs

1// This file is Copyright its original authors, visible in version control history.
2//
3// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6// accordance with one or both of these licenses.
7
8//! Logging-related objects.
9
10pub(crate) use lightning::util::logger::{Logger as LdkLogger, Record as LdkRecord};
11pub(crate) use lightning::{log_bytes, log_debug, log_error, log_info, log_trace};
12
13pub use lightning::util::logger::Level as LogLevel;
14
15use chrono::Utc;
16use log::Level as LogFacadeLevel;
17use log::Record as LogFacadeRecord;
18
19#[cfg(not(feature = "uniffi"))]
20use core::fmt;
21use std::fs;
22use std::io::Write;
23use std::path::Path;
24use std::sync::Arc;
25
26/// A unit of logging output with metadata to enable filtering `module_path`,
27/// `file`, and `line` to inform on log's source.
28#[cfg(not(feature = "uniffi"))]
29pub struct LogRecord<'a> {
30	/// The verbosity level of the message.
31	pub level: LogLevel,
32	/// The message body.
33	pub args: fmt::Arguments<'a>,
34	/// The module path of the message.
35	pub module_path: &'a str,
36	/// The line containing the message.
37	pub line: u32,
38}
39
40/// A unit of logging output with metadata to enable filtering `module_path`,
41/// `file`, and `line` to inform on log's source.
42///
43/// This version is used when the `uniffi` feature is enabled.
44/// It is similar to the non-`uniffi` version, but it omits the lifetime parameter
45/// for the `LogRecord`, as the Uniffi-exposed interface cannot handle lifetimes.
46#[cfg(feature = "uniffi")]
47pub struct LogRecord {
48	/// The verbosity level of the message.
49	pub level: LogLevel,
50	/// The message body.
51	pub args: String,
52	/// The module path of the message.
53	pub module_path: String,
54	/// The line containing the message.
55	pub line: u32,
56}
57
58#[cfg(feature = "uniffi")]
59impl<'a> From<LdkRecord<'a>> for LogRecord {
60	fn from(record: LdkRecord) -> Self {
61		Self {
62			level: record.level,
63			args: record.args.to_string(),
64			module_path: record.module_path.to_string(),
65			line: record.line,
66		}
67	}
68}
69
70#[cfg(not(feature = "uniffi"))]
71impl<'a> From<LdkRecord<'a>> for LogRecord<'a> {
72	fn from(record: LdkRecord<'a>) -> Self {
73		Self {
74			level: record.level,
75			args: record.args,
76			module_path: record.module_path,
77			line: record.line,
78		}
79	}
80}
81
82/// Defines the behavior required for writing log records.
83///
84/// Implementors of this trait are responsible for handling log messages,
85/// which may involve formatting, filtering, and forwarding them to specific
86/// outputs.
87#[cfg(not(feature = "uniffi"))]
88pub trait LogWriter: Send + Sync {
89	/// Log the record.
90	fn log<'a>(&self, record: LogRecord<'a>);
91}
92
93/// Defines the behavior required for writing log records.
94///
95/// Implementors of this trait are responsible for handling log messages,
96/// which may involve formatting, filtering, and forwarding them to specific
97/// outputs.
98/// This version is used when the `uniffi` feature is enabled.
99/// It is similar to the non-`uniffi` version, but it omits the lifetime parameter
100/// for the `LogRecord`, as the Uniffi-exposed interface cannot handle lifetimes.
101#[cfg(feature = "uniffi")]
102pub trait LogWriter: Send + Sync {
103	/// Log the record.
104	fn log(&self, record: LogRecord);
105}
106
107/// Defines a writer for [`Logger`].
108pub(crate) enum Writer {
109	/// Writes logs to the file system.
110	FileWriter { file_path: String, max_log_level: LogLevel },
111	/// Forwards logs to the `log` facade.
112	LogFacadeWriter,
113	/// Forwards logs to a custom writer.
114	CustomWriter(Arc<dyn LogWriter>),
115}
116
117impl LogWriter for Writer {
118	fn log(&self, record: LogRecord) {
119		match self {
120			Writer::FileWriter { file_path, max_log_level } => {
121				if record.level < *max_log_level {
122					return;
123				}
124
125				let log = format!(
126					"{} {:<5} [{}:{}] {}\n",
127					Utc::now().format("%Y-%m-%d %H:%M:%S"),
128					record.level.to_string(),
129					record.module_path,
130					record.line,
131					record.args
132				);
133
134				fs::OpenOptions::new()
135					.create(true)
136					.append(true)
137					.open(file_path)
138					.expect("Failed to open log file")
139					.write_all(log.as_bytes())
140					.expect("Failed to write to log file")
141			},
142			Writer::LogFacadeWriter => {
143				let mut builder = LogFacadeRecord::builder();
144
145				match record.level {
146					LogLevel::Gossip | LogLevel::Trace => builder.level(LogFacadeLevel::Trace),
147					LogLevel::Debug => builder.level(LogFacadeLevel::Debug),
148					LogLevel::Info => builder.level(LogFacadeLevel::Info),
149					LogLevel::Warn => builder.level(LogFacadeLevel::Warn),
150					LogLevel::Error => builder.level(LogFacadeLevel::Error),
151				};
152
153				#[cfg(not(feature = "uniffi"))]
154				log::logger().log(
155					&builder
156						.module_path(Some(record.module_path))
157						.line(Some(record.line))
158						.args(format_args!("{}", record.args))
159						.build(),
160				);
161				#[cfg(feature = "uniffi")]
162				log::logger().log(
163					&builder
164						.module_path(Some(&record.module_path))
165						.line(Some(record.line))
166						.args(format_args!("{}", record.args))
167						.build(),
168				);
169			},
170			Writer::CustomWriter(custom_logger) => custom_logger.log(record),
171		}
172	}
173}
174
175pub(crate) struct Logger {
176	/// Specifies the logger's writer.
177	writer: Writer,
178}
179
180impl Logger {
181	/// Creates a new logger with a filesystem writer. The parameters to this function
182	/// are the path to the log file, and the log level.
183	pub fn new_fs_writer(file_path: String, max_log_level: LogLevel) -> Result<Self, ()> {
184		if let Some(parent_dir) = Path::new(&file_path).parent() {
185			fs::create_dir_all(parent_dir)
186				.map_err(|e| eprintln!("ERROR: Failed to create log parent directory: {}", e))?;
187
188			// make sure the file exists.
189			fs::OpenOptions::new()
190				.create(true)
191				.append(true)
192				.open(&file_path)
193				.map_err(|e| eprintln!("ERROR: Failed to open log file: {}", e))?;
194		}
195
196		Ok(Self { writer: Writer::FileWriter { file_path, max_log_level } })
197	}
198
199	pub fn new_log_facade() -> Self {
200		Self { writer: Writer::LogFacadeWriter }
201	}
202
203	pub fn new_custom_writer(log_writer: Arc<dyn LogWriter>) -> Self {
204		Self { writer: Writer::CustomWriter(log_writer) }
205	}
206}
207
208impl LdkLogger for Logger {
209	fn log(&self, record: LdkRecord) {
210		match &self.writer {
211			Writer::FileWriter { file_path: _, max_log_level } => {
212				if record.level < *max_log_level {
213					return;
214				}
215				self.writer.log(record.into());
216			},
217			Writer::LogFacadeWriter => {
218				self.writer.log(record.into());
219			},
220			Writer::CustomWriter(_arc) => {
221				self.writer.log(record.into());
222			},
223		}
224	}
225}