Skip to main content

logging/
handler.rs

1use crate::formatter::Formatter;
2use crate::types::Record;
3use std::io::Write;
4use std::sync::Mutex;
5
6/// A sink for log records. Implementors render the record (via their
7/// [`Formatter`]) and deliver it somewhere (stdout, a file, ...).
8///
9/// Handlers are shared across threads ([`Send`] + [`Sync`]) since a logger may
10/// be logged to concurrently.
11pub trait Handler: Send + Sync {
12    /// Renders and writes a single record.
13    fn handle(&self, record: &Record);
14    /// The formatter this handler uses (the logger reads its `time_format`).
15    fn formatter(&self) -> &Formatter;
16}
17
18/// A [`Handler`] that writes formatted records to any [`Write`] sink, guarded
19/// by a [`Mutex`] so a single stream can be shared across threads.
20pub struct StreamHandler<W: Write + Send> {
21    formatter: Formatter,
22    stream: Mutex<W>,
23}
24
25impl<W: Write + Send> StreamHandler<W> {
26    /// Creates a handler from an explicit formatter and stream.
27    pub fn new(formatter: Formatter, stream: W) -> Self {
28        Self {
29            formatter,
30            stream: Mutex::new(stream),
31        }
32    }
33    /// Convenience constructor that builds a [`Formatter`] from `pattern`.
34    pub fn with_pattern(stream: W, pattern: &str) -> Self {
35        Self::new(Formatter::new(pattern), stream)
36    }
37}
38
39impl<W: Write + Send> Handler for StreamHandler<W> {
40    fn handle(&self, record: &Record) {
41        let message = self.formatter.format(record);
42        let mut stream = self
43            .stream
44            .lock()
45            .unwrap_or_else(|poisoned| poisoned.into_inner());
46        if let Err(error) = stream.write_all(message.as_bytes()) {
47            eprintln!("Error writing to stream: {error}");
48        }
49    }
50    fn formatter(&self) -> &Formatter {
51        &self.formatter
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use std::collections::HashMap;
59    use std::sync::Arc;
60
61    /// A `Write` sink that appends to a shared buffer we can inspect later.
62    #[derive(Clone)]
63    struct SharedBuf(Arc<Mutex<Vec<u8>>>);
64
65    impl Write for SharedBuf {
66        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
67            self.0.lock().unwrap().extend_from_slice(buf);
68            Ok(buf.len())
69        }
70        fn flush(&mut self) -> std::io::Result<()> {
71            Ok(())
72        }
73    }
74
75    #[test]
76    fn stream_handler_writes_formatted_record() {
77        let buf = Arc::new(Mutex::new(Vec::new()));
78        let handler = StreamHandler::with_pattern(SharedBuf(buf.clone()), "%(level): %(message)");
79
80        let record = HashMap::from([("level", "INFO"), ("message", "hello")]);
81        handler.handle(&record);
82
83        let written = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
84        assert_eq!(written, "INFO: hello\n");
85    }
86
87    #[test]
88    fn formatter_accessor_returns_the_handlers_formatter() {
89        let handler = StreamHandler::with_pattern(Vec::<u8>::new(), "%(message)");
90        // Render through the accessor to confirm it's the configured formatter.
91        let record = HashMap::from([("message", "x")]);
92        assert_eq!(handler.formatter().format(&record), "x\n");
93    }
94}