error_forge/
logging.rs

1use std::sync::OnceLock;
2use crate::error::ForgeError;
3use crate::macros::ErrorLevel;
4
5/// Trait for error logging adapters
6/// 
7/// Implement this trait to create a custom logger for error-forge
8/// that integrates with your logging system
9pub trait ErrorLogger: Send + Sync + 'static {
10    /// Log an error with the given level
11    fn log_error(&self, error: &dyn ForgeError, level: ErrorLevel);
12    
13    /// Log a message with the given level
14    fn log_message(&self, message: &str, level: ErrorLevel);
15    
16    /// Called when a panic occurs (if panic hook is registered)
17    fn log_panic(&self, info: &std::panic::PanicHookInfo);
18}
19
20// The global error logger
21static ERROR_LOGGER: OnceLock<Box<dyn ErrorLogger>> = OnceLock::new();
22
23/// Register a logger for errors
24/// 
25/// Only one logger can be registered at a time.
26/// If a logger is already registered, this will return an error.
27pub fn register_logger(logger: impl ErrorLogger) -> Result<(), &'static str> {
28    let boxed = Box::new(logger);
29    match ERROR_LOGGER.set(boxed) {
30        Ok(_) => Ok(()),
31        Err(_) => Err("Error logger already registered"),
32    }
33}
34
35/// Get the current logger, if one is registered
36pub fn logger() -> Option<&'static dyn ErrorLogger> {
37    ERROR_LOGGER.get().map(|boxed| boxed.as_ref())
38}
39
40/// Log an error with the appropriate level
41pub fn log_error(error: &dyn ForgeError) {
42    if let Some(logger) = logger() {
43        let level = if error.is_fatal() {
44            ErrorLevel::Critical
45        } else if !error.is_retryable() {
46            ErrorLevel::Error
47        } else {
48            ErrorLevel::Warning
49        };
50        
51        logger.log_error(error, level);
52    }
53}
54
55/// Standard logging implementation for common logging crates
56#[cfg(feature = "log")]
57pub mod log_impl {
58    use super::*;
59    use log::{error, warn, info, debug};
60    
61    /// A logger that uses the `log` crate
62    pub struct LogAdapter;
63    
64    impl ErrorLogger for LogAdapter {
65        fn log_error(&self, error: &dyn ForgeError, level: ErrorLevel) {
66            let kind = error.kind();
67            let message = error.dev_message();
68            match level {
69                ErrorLevel::Critical => error!(target: "error-forge", "[CRITICAL] [{kind}] {message}"),
70                ErrorLevel::Error => error!(target: "error-forge", "[ERROR] [{kind}] {message}"),
71                ErrorLevel::Warning => warn!(target: "error-forge", "[WARNING] [{kind}] {message}"),
72                ErrorLevel::Info => info!(target: "error-forge", "[INFO] [{kind}] {message}"),
73                ErrorLevel::Debug => debug!(target: "error-forge", "[DEBUG] [{kind}] {message}"),
74            }
75        }
76        
77        fn log_message(&self, message: &str, level: ErrorLevel) {
78            match level {
79                ErrorLevel::Critical | ErrorLevel::Error => error!(target: "error-forge", "{message}"),
80                ErrorLevel::Warning => warn!(target: "error-forge", "{message}"),
81                ErrorLevel::Info => info!(target: "error-forge", "{message}"),
82                ErrorLevel::Debug => debug!(target: "error-forge", "{message}"),
83            }
84        }
85        
86        fn log_panic(&self, info: &std::panic::PanicHookInfo) {
87            error!(target: "error-forge", "PANIC: {info}");
88        }
89    }
90    
91    /// Initialize logging with the log crate adapter
92    pub fn init() -> Result<(), &'static str> {
93        register_logger(LogAdapter)
94    }
95}
96
97#[cfg(feature = "tracing")]
98pub mod tracing_impl {
99    use super::*;
100    use tracing::{error, warn, info, debug};
101    
102    /// A logger that uses the `tracing` crate
103    pub struct TracingAdapter;
104    
105    impl ErrorLogger for TracingAdapter {
106        fn log_error(&self, error: &dyn ForgeError, level: ErrorLevel) {
107            match level {
108                ErrorLevel::Critical => error!(target: "error-forge", kind = %error.kind(), message = %error.dev_message(), "Critical error"),
109                ErrorLevel::Error => error!(target: "error-forge", kind = %error.kind(), message = %error.dev_message(), "Error"),
110                ErrorLevel::Warning => warn!(target: "error-forge", kind = %error.kind(), message = %error.dev_message(), "Warning"),
111                ErrorLevel::Info => info!(target: "error-forge", kind = %error.kind(), message = %error.dev_message(), "Info"),
112                ErrorLevel::Debug => debug!(target: "error-forge", kind = %error.kind(), message = %error.dev_message(), "Debug"),
113            }
114        }
115        
116        fn log_message(&self, message: &str, level: ErrorLevel) {
117            match level {
118                ErrorLevel::Critical | ErrorLevel::Error => error!(target: "error-forge", "{message}"),
119                ErrorLevel::Warning => warn!(target: "error-forge", "{message}"),
120                ErrorLevel::Info => info!(target: "error-forge", "{message}"),
121                ErrorLevel::Debug => debug!(target: "error-forge", "{message}"),
122            }
123        }
124        
125        fn log_panic(&self, info: &std::panic::PanicHookInfo) {
126            error!(target: "error-forge", panic = %info, "Panic occurred");
127        }
128    }
129    
130    /// Initialize logging with the tracing adapter
131    pub fn init() -> Result<(), &'static str> {
132        register_logger(TracingAdapter)
133    }
134}
135
136/// Build your own error logger - example implementation
137pub mod custom {
138    use super::*;
139    
140    // Type aliases for complex types
141    /// Function type for error logging
142    type ErrorFn = Box<dyn Fn(&dyn ForgeError, ErrorLevel) + Send + Sync + 'static>;
143    /// Function type for message logging
144    type MessageFn = Box<dyn Fn(&str, ErrorLevel) + Send + Sync + 'static>;
145    /// Function type for panic logging
146    type PanicFn = Box<dyn Fn(&std::panic::PanicHookInfo) + Send + Sync + 'static>;
147    
148    /// Builder for creating a custom error logger
149    #[derive(Default)]
150    pub struct ErrorLoggerBuilder {
151        error_fn: Option<ErrorFn>,
152        message_fn: Option<MessageFn>,
153        panic_fn: Option<PanicFn>,
154    }
155
156    impl ErrorLoggerBuilder {
157        /// Create a new error logger builder
158        pub fn new() -> Self {
159            Self::default()
160        }
161        
162        /// Set the function to use for logging errors
163        pub fn with_error_fn<F>(mut self, f: F) -> Self
164        where
165            F: Fn(&dyn ForgeError, ErrorLevel) + Send + Sync + 'static,
166        {
167            self.error_fn = Some(Box::new(f));
168            self
169        }
170        
171        /// Set the function to use for logging messages
172        pub fn with_message_fn<F>(mut self, f: F) -> Self
173        where
174            F: Fn(&str, ErrorLevel) + Send + Sync + 'static,
175        {
176            self.message_fn = Some(Box::new(f));
177            self
178        }
179        
180        /// Set the function to use for logging panics
181        pub fn with_panic_fn<F>(mut self, f: F) -> Self
182        where
183            F: Fn(&std::panic::PanicHookInfo) + Send + Sync + 'static,
184        {
185            self.panic_fn = Some(Box::new(f));
186            self
187        }
188        
189        /// Build the error logger
190        pub fn build(self) -> CustomErrorLogger {
191            CustomErrorLogger {
192                error_fn: self.error_fn,
193                message_fn: self.message_fn,
194                panic_fn: self.panic_fn,
195            }
196        }
197    }
198    
199    /// A custom error logger that uses user-provided functions
200    pub struct CustomErrorLogger {
201        error_fn: Option<ErrorFn>,
202        message_fn: Option<MessageFn>,
203        panic_fn: Option<PanicFn>,
204    }
205    
206    impl ErrorLogger for CustomErrorLogger {
207        fn log_error(&self, error: &dyn ForgeError, level: ErrorLevel) {
208            if let Some(error_fn) = &self.error_fn {
209                error_fn(error, level);
210            }
211        }
212        
213        fn log_message(&self, message: &str, level: ErrorLevel) {
214            if let Some(message_fn) = &self.message_fn {
215                message_fn(message, level);
216            }
217        }
218        
219        fn log_panic(&self, info: &std::panic::PanicHookInfo) {
220            if let Some(panic_fn) = &self.panic_fn {
221                panic_fn(info);
222            }
223        }
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use crate::AppError;
231    use std::sync::Mutex;
232    use std::sync::Arc;
233    
234    #[test]
235    fn test_custom_logger() {
236        // A simple test logger that captures logs in a Vec
237        struct TestLogger {
238            logs: Arc<Mutex<Vec<String>>>,
239        }
240        
241        impl ErrorLogger for TestLogger {
242            fn log_error(&self, error: &dyn ForgeError, level: ErrorLevel) {
243                let kind = error.kind();
244                let message = error.dev_message();
245                let log = format!("{level:?}: [{kind}] {message}");
246                self.logs.lock().unwrap().push(log);
247            }
248            
249            fn log_message(&self, message: &str, level: ErrorLevel) {
250                let log = format!("{level:?}: {message}");
251                self.logs.lock().unwrap().push(log);
252            }
253            
254            fn log_panic(&self, info: &std::panic::PanicHookInfo) {
255                let log = format!("PANIC: {info}");
256                self.logs.lock().unwrap().push(log);
257            }
258        }
259        
260        // Create and register the logger
261        let logs = Arc::new(Mutex::new(Vec::new()));
262        let logger = TestLogger { logs: Arc::clone(&logs) };
263        
264        // We need to make sure we have a fresh state for this test
265        // In a real app, you'd only register once at startup
266        let _ = register_logger(logger);
267        
268        // Log an error
269        let error = AppError::config("Test error");
270        log_error(&error);
271        
272        // Check that the log was captured
273        let captured_logs = logs.lock().unwrap();
274        assert!(!captured_logs.is_empty());
275        assert!(captured_logs[0].contains("[Config]"));
276        assert!(captured_logs[0].contains("Test error"));
277    }
278}