Skip to main content

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}