walletkit_core/logger.rs
1use std::sync::{Arc, OnceLock};
2
3/// Trait representing a logger that can log messages at various levels.
4///
5/// This trait should be implemented by any logger that wants to receive log messages.
6/// It is exported via `UniFFI` for use in foreign languages.
7///
8/// # Examples
9///
10/// Implementing the `Logger` trait:
11///
12/// ```rust
13/// use walletkit_core::logger::{Logger, LogLevel};
14///
15/// struct MyLogger;
16///
17/// impl Logger for MyLogger {
18/// fn log(&self, level: LogLevel, message: String) {
19/// println!("[{:?}] {}", level, message);
20/// }
21/// }
22/// ```
23///
24/// ## Swift
25///
26/// ```swift
27/// class WalletKitLoggerBridge: WalletKit.Logger {
28/// static let shared = WalletKitLoggerBridge()
29///
30/// func log(level: WalletKit.LogLevel, message: String) {
31/// Log.log(level.toCoreLevel(), message)
32/// }
33/// }
34///
35/// public func setupWalletKitLogger() {
36/// WalletKit.setLogger(logger: WalletKitLoggerBridge.shared)
37/// }
38/// ```
39///
40/// ### In app delegate
41///
42/// ```swift
43/// setupWalletKitLogger() // Call this only once!!!
44/// ```
45#[cfg_attr(feature = "ffi", uniffi::export(with_foreign))]
46pub trait Logger: Sync + Send {
47 /// Logs a message at the specified log level.
48 ///
49 /// # Arguments
50 ///
51 /// * `level` - The severity level of the log message.
52 /// * `message` - The log message to be recorded.
53 fn log(&self, level: LogLevel, message: String);
54}
55
56/// Enumeration of possible log levels.
57///
58/// This enum represents the severity levels that can be used when logging messages.
59#[derive(Debug, Clone)]
60#[cfg_attr(feature = "ffi", derive(uniffi::Enum))]
61pub enum LogLevel {
62 /// Designates very low priority, often extremely detailed messages.
63 Trace,
64 /// Designates lower priority debugging information.
65 Debug,
66 /// Designates informational messages that highlight the progress of the application.
67 Info,
68 /// Designates potentially harmful situations.
69 Warn,
70 /// Designates error events that might still allow the application to continue running.
71 Error,
72}
73
74/// A logger that forwards log messages to a user-provided `Logger` implementation.
75///
76/// This struct implements the `log::Log` trait and integrates with the Rust `log` crate.
77struct ForeignLogger;
78
79impl log::Log for ForeignLogger {
80 /// Determines if a log message with the specified metadata should be logged.
81 ///
82 /// This implementation logs all messages. Modify this method to implement log level filtering.
83 ///
84 /// # Arguments
85 ///
86 /// * `_metadata` - Metadata about the log message.
87 fn enabled(&self, _metadata: &log::Metadata) -> bool {
88 // Currently, we log all messages. Adjust this if you need to filter messages.
89 true
90 }
91
92 /// Logs a record.
93 ///
94 /// This method is called by the `log` crate when a log message needs to be logged.
95 /// It forwards the log message to the user-provided `Logger` implementation if available.
96 ///
97 /// # Arguments
98 ///
99 /// * `record` - The log record containing the message and metadata.
100 fn log(&self, record: &log::Record) {
101 // Determine if the record originates from the "walletkit" module.
102 let is_record_from_walletkit = record
103 .module_path()
104 .is_some_and(|module_path| module_path.starts_with("walletkit"));
105
106 // Determine if the log level is Debug or Trace.
107 let is_debug_or_trace_level =
108 record.level() == log::Level::Debug || record.level() == log::Level::Trace;
109
110 // Skip logging Debug or Trace level messages that are not from the "walletkit" module.
111 if is_debug_or_trace_level && !is_record_from_walletkit {
112 return;
113 }
114
115 // Forward the log message to the user-provided logger if available.
116 if let Some(logger) = LOGGER_INSTANCE.get() {
117 let level = log_level(record.level());
118 let message = format!("{}", record.args());
119 logger.log(level, message);
120 } else {
121 // Handle the case when the logger is not set.
122 eprintln!("Logger not set: {}", record.args());
123 }
124 }
125
126 /// Flushes any buffered records.
127 ///
128 /// This implementation does nothing because buffering is not used.
129 fn flush(&self) {}
130}
131
132/// Converts a `log::Level` to a `LogLevel`.
133///
134/// This function maps the log levels from the `log` crate to your own `LogLevel` enum.
135///
136/// # Arguments
137///
138/// * `level` - The `log::Level` to convert.
139///
140/// # Returns
141///
142/// A corresponding `LogLevel`.
143const fn log_level(level: log::Level) -> LogLevel {
144 match level {
145 log::Level::Error => LogLevel::Error,
146 log::Level::Warn => LogLevel::Warn,
147 log::Level::Info => LogLevel::Info,
148 log::Level::Debug => LogLevel::Debug,
149 log::Level::Trace => LogLevel::Trace,
150 }
151}
152
153/// A global instance of the user-provided logger.
154///
155/// This static variable holds the logger provided by the user and is accessed by `ForeignLogger` to forward log messages.
156static LOGGER_INSTANCE: OnceLock<Arc<dyn Logger>> = OnceLock::new();
157
158/// Sets the global logger.
159///
160/// This function allows you to provide your own implementation of the `Logger` trait.
161/// It initializes the logging system and should be called before any logging occurs.
162///
163/// # Arguments
164///
165/// * `logger` - An `Arc` containing your logger implementation.
166///
167/// # Panics
168///
169/// Panics if the logger has already been set.
170///
171/// # Note
172///
173/// If the logger has already been set, this function will print a message and do nothing.
174#[cfg_attr(feature = "ffi", uniffi::export)]
175pub fn set_logger(logger: Arc<dyn Logger>) {
176 match LOGGER_INSTANCE.set(logger) {
177 Ok(()) => (),
178 Err(_) => println!("Logger already set"),
179 }
180
181 // Initialize the logger system.
182 if let Err(e) = init_logger() {
183 eprintln!("Failed to set logger: {e}");
184 }
185}
186
187/// Initializes the logger system.
188///
189/// This function sets up the global logger with the `ForeignLogger` implementation and sets the maximum log level.
190///
191/// # Returns
192///
193/// A `Result` indicating success or failure.
194///
195/// # Errors
196///
197/// Returns a `log::SetLoggerError` if the logger could not be set (e.g., if a logger was already set).
198fn init_logger() -> Result<(), log::SetLoggerError> {
199 static LOGGER: ForeignLogger = ForeignLogger;
200 log::set_logger(&LOGGER)?;
201 log::set_max_level(log::LevelFilter::Trace);
202 Ok(())
203}