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