1use std::sync::OnceLock;
2use crate::error::ForgeError;
3use crate::macros::ErrorLevel;
4
5pub trait ErrorLogger: Send + Sync + 'static {
10 fn log_error(&self, error: &dyn ForgeError, level: ErrorLevel);
12
13 fn log_message(&self, message: &str, level: ErrorLevel);
15
16 fn log_panic(&self, info: &std::panic::PanicHookInfo);
18}
19
20static ERROR_LOGGER: OnceLock<Box<dyn ErrorLogger>> = OnceLock::new();
22
23pub 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
35pub fn logger() -> Option<&'static dyn ErrorLogger> {
37 ERROR_LOGGER.get().map(|boxed| boxed.as_ref())
38}
39
40pub 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#[cfg(feature = "log")]
57pub mod log_impl {
58 use super::*;
59 use log::{error, warn, info, debug};
60
61 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 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 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 pub fn init() -> Result<(), &'static str> {
132 register_logger(TracingAdapter)
133 }
134}
135
136pub mod custom {
138 use super::*;
139
140 type ErrorFn = Box<dyn Fn(&dyn ForgeError, ErrorLevel) + Send + Sync + 'static>;
143 type MessageFn = Box<dyn Fn(&str, ErrorLevel) + Send + Sync + 'static>;
145 type PanicFn = Box<dyn Fn(&std::panic::PanicHookInfo) + Send + Sync + 'static>;
147
148 #[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 pub fn new() -> Self {
159 Self::default()
160 }
161
162 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 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 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 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 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 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 let logs = Arc::new(Mutex::new(Vec::new()));
262 let logger = TestLogger { logs: Arc::clone(&logs) };
263
264 let _ = register_logger(logger);
267
268 let error = AppError::config("Test error");
270 log_error(&error);
271
272 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}