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#[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, uniffi::Enum)]
60pub enum LogLevel {
61 /// Designates very low priority, often extremely detailed messages.
62 Trace,
63 /// Designates lower priority debugging information.
64 Debug,
65 /// Designates informational messages that highlight the progress of the application.
66 Info,
67 /// Designates potentially harmful situations.
68 Warn,
69 /// Designates error events that might still allow the application to continue running.
70 Error,
71}
72
73/// A logger that forwards log messages to a user-provided `Logger` implementation.
74///
75/// This struct implements the `log::Log` trait and integrates with the Rust `log` crate.
76struct ForeignLogger;
77
78impl log::Log for ForeignLogger {
79 /// Determines if a log message with the specified metadata should be logged.
80 ///
81 /// This implementation logs all messages. Modify this method to implement log level filtering.
82 ///
83 /// # Arguments
84 ///
85 /// * `_metadata` - Metadata about the log message.
86 fn enabled(&self, _metadata: &log::Metadata) -> bool {
87 // Currently, we log all messages. Adjust this if you need to filter messages.
88 true
89 }
90
91 /// Logs a record.
92 ///
93 /// This method is called by the `log` crate when a log message needs to be logged.
94 /// It forwards the log message to the user-provided `Logger` implementation if available.
95 ///
96 /// # Arguments
97 ///
98 /// * `record` - The log record containing the message and metadata.
99 fn log(&self, record: &log::Record) {
100 // Determine if the record originates from the "walletkit" module.
101 let is_record_from_walletkit = record
102 .module_path()
103 .is_some_and(|module_path| module_path.starts_with("walletkit"));
104
105 // Determine if the log level is Debug or Trace.
106 let is_debug_or_trace_level =
107 record.level() == log::Level::Debug || record.level() == log::Level::Trace;
108
109 // Skip logging Debug or Trace level messages that are not from the "walletkit" module.
110 if is_debug_or_trace_level && !is_record_from_walletkit {
111 return;
112 }
113
114 // Forward the log message to the user-provided logger if available.
115 if let Some(logger) = LOGGER_INSTANCE.get() {
116 let level = log_level(record.level());
117 let message = format!("{}", record.args());
118 logger.log(level, message);
119 } else {
120 // Handle the case when the logger is not set.
121 eprintln!("Logger not set: {}", record.args());
122 }
123 }
124
125 /// Flushes any buffered records.
126 ///
127 /// This implementation does nothing because buffering is not used.
128 fn flush(&self) {}
129}
130
131/// Converts a `log::Level` to a `LogLevel`.
132///
133/// This function maps the log levels from the `log` crate to your own `LogLevel` enum.
134///
135/// # Arguments
136///
137/// * `level` - The `log::Level` to convert.
138///
139/// # Returns
140///
141/// A corresponding `LogLevel`.
142const fn log_level(level: log::Level) -> LogLevel {
143 match level {
144 log::Level::Error => LogLevel::Error,
145 log::Level::Warn => LogLevel::Warn,
146 log::Level::Info => LogLevel::Info,
147 log::Level::Debug => LogLevel::Debug,
148 log::Level::Trace => LogLevel::Trace,
149 }
150}
151
152/// A global instance of the user-provided logger.
153///
154/// This static variable holds the logger provided by the user and is accessed by `ForeignLogger` to forward log messages.
155static LOGGER_INSTANCE: OnceLock<Arc<dyn Logger>> = OnceLock::new();
156
157/// Sets the global logger.
158///
159/// This function allows you to provide your own implementation of the `Logger` trait.
160/// It initializes the logging system and should be called before any logging occurs.
161///
162/// # Arguments
163///
164/// * `logger` - An `Arc` containing your logger implementation.
165///
166/// # Panics
167///
168/// Panics if the logger has already been set.
169///
170/// # Note
171///
172/// If the logger has already been set, this function will print a message and do nothing.
173#[uniffi::export]
174pub fn set_logger(logger: Arc<dyn Logger>) {
175 match LOGGER_INSTANCE.set(logger) {
176 Ok(()) => (),
177 Err(_) => println!("Logger already set"),
178 }
179
180 // Initialize the logger system.
181 if let Err(e) = init_logger() {
182 eprintln!("Failed to set logger: {e}");
183 }
184}
185
186/// Initializes the logger system.
187///
188/// This function sets up the global logger with the `ForeignLogger` implementation and sets the maximum log level.
189///
190/// # Returns
191///
192/// A `Result` indicating success or failure.
193///
194/// # Errors
195///
196/// Returns a `log::SetLoggerError` if the logger could not be set (e.g., if a logger was already set).
197fn init_logger() -> Result<(), log::SetLoggerError> {
198 static LOGGER: ForeignLogger = ForeignLogger;
199 log::set_logger(&LOGGER)?;
200 log::set_max_level(log::LevelFilter::Trace);
201 Ok(())
202}