1use std::fmt;
2use std::io::{self, Write};
3use std::sync::Mutex;
4
5use crate::formatters::Formatter;
6use crate::level::LogLevel;
7use crate::record::Record;
8
9use super::{Handler, HandlerFilter};
10
11pub struct DebugWrite {
13 writer: Mutex<Box<dyn Write + Send + Sync>>,
14}
15
16impl fmt::Debug for DebugWrite {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 f.debug_struct("DebugWrite")
19 .field("writer", &"<writer>")
20 .finish()
21 }
22}
23
24pub struct ConsoleHandler {
26 level: LogLevel,
28 enabled: bool,
30 formatter: Formatter,
32 output: DebugWrite,
34 filter: Option<HandlerFilter>,
36}
37
38impl Clone for ConsoleHandler {
39 fn clone(&self) -> Self {
40 Self {
41 level: self.level,
42 enabled: self.enabled,
43 formatter: self.formatter.clone(),
44 output: DebugWrite {
45 writer: Mutex::new(Box::new(io::stdout())),
46 },
47 filter: self.filter.clone(),
48 }
49 }
50}
51
52impl ConsoleHandler {
53 pub fn stdout(level: LogLevel) -> Self {
55 Self {
56 level,
57 enabled: true,
58 formatter: Formatter::text()
59 .with_pattern("{level} - {message}")
60 .with_colors(true),
61 output: DebugWrite {
62 writer: Mutex::new(Box::new(io::stdout())),
63 },
64 filter: None,
65 }
66 }
67
68 pub fn stderr(level: LogLevel) -> Self {
70 Self {
71 level,
72 enabled: true,
73 formatter: Formatter::text()
74 .with_pattern("{level} - {message}")
75 .with_colors(true),
76 output: DebugWrite {
77 writer: Mutex::new(Box::new(io::stderr())),
78 },
79 filter: None,
80 }
81 }
82
83 pub fn with_writer(level: LogLevel, writer: Box<dyn Write + Send + Sync>) -> Self {
85 Self {
86 level,
87 enabled: true,
88 formatter: Formatter::text()
89 .with_pattern("{level} - {message}")
90 .with_colors(true),
91 output: DebugWrite {
92 writer: Mutex::new(writer),
93 },
94 filter: None,
95 }
96 }
97
98 pub fn with_colors(mut self, use_colors: bool) -> Self {
100 self.formatter = self.formatter.with_colors(use_colors);
101 self
102 }
103
104 pub fn with_pattern(self, pattern: impl Into<String>) -> Self {
106 let mut handler = self;
107 let formatter = handler.formatter.with_pattern(pattern);
108 handler.formatter = formatter;
109 handler
110 }
111
112 pub fn with_format<F>(mut self, format_fn: F) -> Self
114 where
115 F: Fn(&Record) -> String + Send + Sync + 'static,
116 {
117 self.formatter = self.formatter.with_format(format_fn);
118 self
119 }
120
121 pub fn with_formatter(mut self, formatter: Formatter) -> Self {
122 self.formatter = formatter;
123 self
124 }
125
126 pub fn with_filter(mut self, filter: HandlerFilter) -> Self {
127 self.filter = Some(filter);
128 self
129 }
130}
131
132impl Default for ConsoleHandler {
133 fn default() -> Self {
134 Self::stdout(LogLevel::Info)
135 }
136}
137
138impl Handler for ConsoleHandler {
139 fn handle(&self, record: &Record) -> Result<(), String> {
140 if !self.enabled || record.level() < self.level {
141 return Ok(());
142 }
143 if let Some(filter) = &self.filter {
144 if !(filter)(record) {
145 return Ok(());
146 }
147 }
148 let formatted = self.formatter.format(record);
149 let mut writer = self
150 .output
151 .writer
152 .lock()
153 .map_err(|e| format!("Failed to lock writer: {}", e))?;
154 write!(writer, "{}", formatted)
155 .map_err(|e| format!("Failed to write to console: {}", e))?;
156 writer
157 .flush()
158 .map_err(|e| format!("Failed to flush console: {}", e))?;
159 Ok(())
160 }
161
162 fn level(&self) -> LogLevel {
163 self.level
164 }
165
166 fn set_level(&mut self, level: LogLevel) {
167 self.level = level;
168 }
169
170 fn is_enabled(&self) -> bool {
171 self.enabled
172 }
173
174 fn set_enabled(&mut self, enabled: bool) {
175 self.enabled = enabled;
176 }
177
178 fn formatter(&self) -> &Formatter {
179 &self.formatter
180 }
181
182 fn set_formatter(&mut self, formatter: Formatter) {
183 self.formatter = formatter;
184 }
185
186 fn set_filter(&mut self, filter: Option<HandlerFilter>) {
187 self.filter = filter;
188 }
189
190 fn filter(&self) -> Option<&HandlerFilter> {
191 self.filter.as_ref()
192 }
193
194 fn handle_batch(&self, records: &[Record]) -> Result<(), String> {
195 for record in records {
196 self.handle(record)?;
197 }
198 Ok(())
199 }
200
201 fn init(&mut self) -> Result<(), String> {
202 Ok(())
203 }
204
205 fn flush(&self) -> Result<(), String> {
206 Ok(())
207 }
208
209 fn shutdown(&mut self) -> Result<(), String> {
210 Ok(())
211 }
212}
213
214impl fmt::Debug for ConsoleHandler {
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 f.debug_struct("ConsoleHandler")
217 .field("level", &self.level)
218 .field("enabled", &self.enabled)
219 .field("formatter", &self.formatter)
220 .field("output", &self.output)
221 .finish()
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use std::sync::{Arc, Mutex};
229
230 struct TestOutput {
231 buffer: Arc<Mutex<Vec<u8>>>,
232 }
233
234 impl Clone for TestOutput {
235 fn clone(&self) -> Self {
236 Self {
237 buffer: self.buffer.clone(),
238 }
239 }
240 }
241
242 impl Write for TestOutput {
243 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
244 self.buffer.lock().unwrap().extend_from_slice(buf);
245 Ok(buf.len())
246 }
247
248 fn flush(&mut self) -> io::Result<()> {
249 Ok(())
250 }
251 }
252
253 impl TestOutput {
254 fn new() -> Self {
255 Self {
256 buffer: Arc::new(Mutex::new(Vec::new())),
257 }
258 }
259
260 fn contents(&self) -> String {
261 let buffer = self.buffer.lock().unwrap();
262 String::from_utf8_lossy(&buffer).to_string()
263 }
264 }
265
266 #[test]
267 fn test_console_handler_level_filtering() {
268 let output = TestOutput::new();
269 let mut handler = ConsoleHandler::with_writer(LogLevel::Warning, Box::new(output.clone()));
270 handler.set_level(LogLevel::Warning);
271
272 let info_record = Record::new(
273 LogLevel::Info,
274 "info message",
275 Some("test".to_string()),
276 Some("test.rs".to_string()),
277 Some(42),
278 );
279 let warning_record = Record::new(
280 LogLevel::Warning,
281 "warning message",
282 Some("test".to_string()),
283 Some("test.rs".to_string()),
284 Some(42),
285 );
286
287 assert!(handler.handle(&info_record).is_ok());
288 assert!(handler.handle(&warning_record).is_ok());
289 assert!(output.contents().contains("warning message"));
290 }
291
292 #[test]
293 fn test_console_handler_disabled() {
294 let output = TestOutput::new();
295 let mut handler = ConsoleHandler::with_writer(LogLevel::Warning, Box::new(output.clone()));
296 handler.set_enabled(false);
297
298 let record = Record::new(
299 LogLevel::Info,
300 "Test message",
301 Some("test".to_string()),
302 Some("test.rs".to_string()),
303 Some(42),
304 );
305
306 assert!(handler.handle(&record).is_ok());
307 assert!(output.contents().is_empty());
308 }
309
310 #[test]
311 fn test_console_handler_formatting() {
312 let output = TestOutput::new();
313 let handler = ConsoleHandler::with_writer(LogLevel::Info, Box::new(output.clone()))
314 .with_pattern("{level} - {message}")
315 .with_colors(false);
316
317 let record = Record::new(
318 LogLevel::Info,
319 "Test message",
320 Some("test".to_string()),
321 Some("test.rs".to_string()),
322 Some(42),
323 );
324
325 assert!(handler.handle(&record).is_ok());
326 assert!(output.contents().contains("INFO - Test message"));
327 }
328
329 #[test]
330 fn test_console_handler_metadata() {
331 let output = TestOutput::new();
332 let handler = ConsoleHandler::with_writer(LogLevel::Info, Box::new(output.clone()));
333
334 let mut record = Record::new(
335 LogLevel::Info,
336 "Test message",
337 Some("test".to_string()),
338 Some("test.rs".to_string()),
339 Some(42),
340 );
341 record = record.with_metadata("key1", "value1");
342 record = record.with_metadata("key2", "value2");
343
344 assert!(handler.handle(&record).is_ok());
345 let contents = output.contents();
346 assert!(contents.contains("key1=value1"));
347 assert!(contents.contains("key2=value2"));
348 }
349
350 #[test]
351 fn test_console_handler_structured_data() {
352 let output = TestOutput::new();
353 let handler = ConsoleHandler::with_writer(LogLevel::Info, Box::new(output.clone()))
354 .with_pattern(r#"{{"level":"{level}","message":"{message}","module":"{module}"}}"#)
355 .with_colors(false);
356
357 let record = Record::new(
358 LogLevel::Info,
359 "Test message",
360 Some("test".to_string()),
361 Some("test.rs".to_string()),
362 Some(42),
363 );
364
365 assert!(handler.handle(&record).is_ok());
366 let output = output.contents();
367 assert!(output.contains(r#""level":"INFO""#));
368 assert!(output.contains(r#""message":"Test message""#));
369 assert!(output.contains(r#""module":"test""#));
370 }
371
372 #[test]
373 fn test_handle_uses_configured_writer() {
374 let output = TestOutput::new();
375 let handler = ConsoleHandler::with_writer(LogLevel::Info, Box::new(output.clone()));
376 let record = Record::new(
377 LogLevel::Info,
378 "test message",
379 Some("test".to_string()),
380 Some("test.rs".to_string()),
381 Some(42),
382 );
383
384 assert!(handler.handle(&record).is_ok());
385 assert!(output.contents().contains("test message"));
386 }
387
388 #[test]
389 fn test_handle_respects_disabled() {
390 let output = TestOutput::new();
391 let mut handler = ConsoleHandler::with_writer(LogLevel::Info, Box::new(output.clone()));
392 handler.set_enabled(false);
393 let record = Record::new(
394 LogLevel::Info,
395 "test message",
396 Some("test".to_string()),
397 Some("test.rs".to_string()),
398 Some(42),
399 );
400
401 assert!(handler.handle(&record).is_ok());
402 assert!(output.contents().is_empty());
403 }
404
405 #[test]
406 fn test_handle_respects_level() {
407 let output = TestOutput::new();
408 let mut handler = ConsoleHandler::with_writer(LogLevel::Info, Box::new(output.clone()));
409 handler.set_level(LogLevel::Error);
410 let record = Record::new(
411 LogLevel::Info,
412 "test message",
413 Some("test".to_string()),
414 Some("test.rs".to_string()),
415 Some(42),
416 );
417
418 assert!(handler.handle(&record).is_ok());
419 assert!(output.contents().is_empty());
420 }
421
422 #[test]
423 fn test_console_handler_filtering() {
424 let output = TestOutput::new();
425 let filter = std::sync::Arc::new(|record: &Record| record.message().contains("pass"));
426 let handler = ConsoleHandler::with_writer(LogLevel::Info, Box::new(output.clone()))
427 .with_filter(filter.clone());
428 let record1 = Record::new(
429 LogLevel::Info,
430 "should pass",
431 None::<String>,
432 None::<String>,
433 None,
434 );
435 let record2 = Record::new(
436 LogLevel::Info,
437 "should fail",
438 None::<String>,
439 None::<String>,
440 None,
441 );
442 assert!(handler.handle(&record1).is_ok());
443 assert!(handler.handle(&record2).is_ok());
444 let contents = output.contents();
445 assert!(contents.contains("should pass"));
446 assert!(!contents.contains("should fail"));
447 }
448
449 #[test]
450 fn test_console_handler_batch() {
451 let output = TestOutput::new();
452 let handler = ConsoleHandler::with_writer(LogLevel::Info, Box::new(output.clone()));
453 let records = vec![
454 Record::new(LogLevel::Info, "msg1", None::<String>, None::<String>, None),
455 Record::new(LogLevel::Info, "msg2", None::<String>, None::<String>, None),
456 ];
457 assert!(handler.handle_batch(&records).is_ok());
458 let contents = output.contents();
459 assert!(contents.contains("msg1"));
460 assert!(contents.contains("msg2"));
461 }
462}