1use crate::error::ForgeError;
2use crate::macros::ErrorLevel;
3use std::sync::OnceLock;
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::{debug, error, info, warn};
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 => {
70 error!(target: "error-forge", "[CRITICAL] [{kind}] {message}")
71 }
72 ErrorLevel::Error => error!(target: "error-forge", "[ERROR] [{kind}] {message}"),
73 ErrorLevel::Warning => warn!(target: "error-forge", "[WARNING] [{kind}] {message}"),
74 ErrorLevel::Info => info!(target: "error-forge", "[INFO] [{kind}] {message}"),
75 ErrorLevel::Debug => debug!(target: "error-forge", "[DEBUG] [{kind}] {message}"),
76 }
77 }
78
79 fn log_message(&self, message: &str, level: ErrorLevel) {
80 match level {
81 ErrorLevel::Critical | ErrorLevel::Error => {
82 error!(target: "error-forge", "{message}")
83 }
84 ErrorLevel::Warning => warn!(target: "error-forge", "{message}"),
85 ErrorLevel::Info => info!(target: "error-forge", "{message}"),
86 ErrorLevel::Debug => debug!(target: "error-forge", "{message}"),
87 }
88 }
89
90 fn log_panic(&self, info: &std::panic::PanicHookInfo) {
91 error!(target: "error-forge", "PANIC: {info}");
92 }
93 }
94
95 pub fn init() -> Result<(), &'static str> {
97 register_logger(LogAdapter)
98 }
99}
100
101#[cfg(feature = "tracing")]
102pub mod tracing_impl {
103 use super::*;
104 use tracing::{debug, error, info, warn};
105
106 pub struct TracingAdapter;
108
109 impl ErrorLogger for TracingAdapter {
110 fn log_error(&self, error: &dyn ForgeError, level: ErrorLevel) {
111 match level {
112 ErrorLevel::Critical => {
113 error!(target: "error-forge", kind = %error.kind(), message = %error.dev_message(), "Critical error")
114 }
115 ErrorLevel::Error => {
116 error!(target: "error-forge", kind = %error.kind(), message = %error.dev_message(), "Error")
117 }
118 ErrorLevel::Warning => {
119 warn!(target: "error-forge", kind = %error.kind(), message = %error.dev_message(), "Warning")
120 }
121 ErrorLevel::Info => {
122 info!(target: "error-forge", kind = %error.kind(), message = %error.dev_message(), "Info")
123 }
124 ErrorLevel::Debug => {
125 debug!(target: "error-forge", kind = %error.kind(), message = %error.dev_message(), "Debug")
126 }
127 }
128 }
129
130 fn log_message(&self, message: &str, level: ErrorLevel) {
131 match level {
132 ErrorLevel::Critical | ErrorLevel::Error => {
133 error!(target: "error-forge", "{message}")
134 }
135 ErrorLevel::Warning => warn!(target: "error-forge", "{message}"),
136 ErrorLevel::Info => info!(target: "error-forge", "{message}"),
137 ErrorLevel::Debug => debug!(target: "error-forge", "{message}"),
138 }
139 }
140
141 fn log_panic(&self, info: &std::panic::PanicHookInfo) {
142 error!(target: "error-forge", panic = %info, "Panic occurred");
143 }
144 }
145
146 pub fn init() -> Result<(), &'static str> {
148 register_logger(TracingAdapter)
149 }
150}
151
152pub mod custom {
154 use super::*;
155
156 type ErrorFn = Box<dyn Fn(&dyn ForgeError, ErrorLevel) + Send + Sync + 'static>;
159 type MessageFn = Box<dyn Fn(&str, ErrorLevel) + Send + Sync + 'static>;
161 type PanicFn = Box<dyn Fn(&std::panic::PanicHookInfo) + Send + Sync + 'static>;
163
164 #[derive(Default)]
166 pub struct ErrorLoggerBuilder {
167 error_fn: Option<ErrorFn>,
168 message_fn: Option<MessageFn>,
169 panic_fn: Option<PanicFn>,
170 }
171
172 impl ErrorLoggerBuilder {
173 pub fn new() -> Self {
175 Self::default()
176 }
177
178 pub fn with_error_fn<F>(mut self, f: F) -> Self
180 where
181 F: Fn(&dyn ForgeError, ErrorLevel) + Send + Sync + 'static,
182 {
183 self.error_fn = Some(Box::new(f));
184 self
185 }
186
187 pub fn with_message_fn<F>(mut self, f: F) -> Self
189 where
190 F: Fn(&str, ErrorLevel) + Send + Sync + 'static,
191 {
192 self.message_fn = Some(Box::new(f));
193 self
194 }
195
196 pub fn with_panic_fn<F>(mut self, f: F) -> Self
198 where
199 F: Fn(&std::panic::PanicHookInfo) + Send + Sync + 'static,
200 {
201 self.panic_fn = Some(Box::new(f));
202 self
203 }
204
205 pub fn build(self) -> CustomErrorLogger {
207 CustomErrorLogger {
208 error_fn: self.error_fn,
209 message_fn: self.message_fn,
210 panic_fn: self.panic_fn,
211 }
212 }
213 }
214
215 pub struct CustomErrorLogger {
217 error_fn: Option<ErrorFn>,
218 message_fn: Option<MessageFn>,
219 panic_fn: Option<PanicFn>,
220 }
221
222 impl ErrorLogger for CustomErrorLogger {
223 fn log_error(&self, error: &dyn ForgeError, level: ErrorLevel) {
224 if let Some(error_fn) = &self.error_fn {
225 error_fn(error, level);
226 }
227 }
228
229 fn log_message(&self, message: &str, level: ErrorLevel) {
230 if let Some(message_fn) = &self.message_fn {
231 message_fn(message, level);
232 }
233 }
234
235 fn log_panic(&self, info: &std::panic::PanicHookInfo) {
236 if let Some(panic_fn) = &self.panic_fn {
237 panic_fn(info);
238 }
239 }
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use crate::AppError;
247 use std::sync::Arc;
248 use std::sync::Mutex;
249
250 #[test]
251 fn test_custom_logger() {
252 struct TestLogger {
254 logs: Arc<Mutex<Vec<String>>>,
255 }
256
257 impl ErrorLogger for TestLogger {
258 fn log_error(&self, error: &dyn ForgeError, level: ErrorLevel) {
259 let kind = error.kind();
260 let message = error.dev_message();
261 let log = format!("{level:?}: [{kind}] {message}");
262 self.logs.lock().unwrap().push(log);
263 }
264
265 fn log_message(&self, message: &str, level: ErrorLevel) {
266 let log = format!("{level:?}: {message}");
267 self.logs.lock().unwrap().push(log);
268 }
269
270 fn log_panic(&self, info: &std::panic::PanicHookInfo) {
271 let log = format!("PANIC: {info}");
272 self.logs.lock().unwrap().push(log);
273 }
274 }
275
276 let logs = Arc::new(Mutex::new(Vec::new()));
278 let logger = TestLogger {
279 logs: Arc::clone(&logs),
280 };
281
282 let _ = register_logger(logger);
285
286 let error = AppError::config("Test error");
288 log_error(&error);
289
290 let captured_logs = logs.lock().unwrap();
292 assert!(!captured_logs.is_empty());
293 assert!(captured_logs[0].contains("[Config]"));
294 assert!(captured_logs[0].contains("Test error"));
295 }
296}