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