logged_stream/
logger.rs

1use crate::record::Record;
2use crate::RecordKind;
3use std::collections;
4use std::io::Write;
5use std::str::FromStr;
6use std::sync::mpsc;
7
8//////////////////////////////////////////////////////////////////////////////////////////////////////////////
9// Trait
10//////////////////////////////////////////////////////////////////////////////////////////////////////////////
11
12/// This trait allows to process log record ([`Record`]) using [`log`] method. It should be implemented for
13/// structures which are going to be used as logging part inside [`LoggedStream`]. Method [`log`] is called
14/// by [`LoggedStream`] for further log record processing (writing to the console, to the memory or database, etc.)
15/// after log record message was formatted by some implementation of [`BufferFormatter`] and the entire log record
16/// was filtered by some implementation of [`RecordFilter`].
17///
18/// [`log`]: Logger::log
19/// [`LoggedStream`]: crate::LoggedStream
20/// [`RecordFilter`]: crate::RecordFilter
21/// [`BufferFormatter`]: crate::BufferFormatter
22pub trait Logger: Send + 'static {
23    fn log(&mut self, record: Record);
24}
25
26impl Logger for Box<dyn Logger> {
27    fn log(&mut self, record: Record) {
28        (**self).log(record)
29    }
30}
31
32//////////////////////////////////////////////////////////////////////////////////////////////////////////////
33// ConsoleLogger
34//////////////////////////////////////////////////////////////////////////////////////////////////////////////
35
36/// This implementation of [`Logger`] trait writes log record ([`Record`]) into console using provided [`log::Level`].
37/// Log records with [`Error`] kind ignore provided [`log::Level`] and always write with [`log::Level::Error`].
38///
39/// [`Error`]: crate::RecordKind::Error
40#[derive(Debug, Clone)]
41pub struct ConsoleLogger {
42    level: log::Level,
43}
44
45impl ConsoleLogger {
46    /// Construct a new instance of [`ConsoleLogger`] using provided log level [`str`]. Returns an [`Err`] in
47    /// case if provided log level [`str`] was incorrect.
48    pub fn new(level: &str) -> Result<Self, log::ParseLevelError> {
49        let level = log::Level::from_str(level)?;
50        Ok(Self { level })
51    }
52
53    /// Construct a new instance of [`ConsoleLogger`] using provided log level [`str`]. Panics in case if
54    /// provided log level [`str`] was incorrect.
55    pub fn new_unchecked(level: &str) -> Self {
56        Self::new(level).unwrap()
57    }
58}
59
60impl Logger for ConsoleLogger {
61    fn log(&mut self, record: Record) {
62        let level = match record.kind {
63            RecordKind::Error => log::Level::Error,
64            _ => self.level,
65        };
66        log::log!(level, "{} {}", record.kind, record.message)
67    }
68}
69
70impl Logger for Box<ConsoleLogger> {
71    fn log(&mut self, record: Record) {
72        (**self).log(record)
73    }
74}
75
76//////////////////////////////////////////////////////////////////////////////////////////////////////////////
77// MemoryStorageLogger
78//////////////////////////////////////////////////////////////////////////////////////////////////////////////
79
80/// This implementation of [`Logger`] trait writes log record ([`Record`]) into inner collection ([`collections::VecDeque`]).
81/// Inner collection length limited by number provided during structure construction. You are able to retrieve
82/// accumulated log records from inner collection using [`get_log_records`] method and clean inner collection
83/// using [`clear_log_records`] method.
84///
85/// [`get_log_records`]: MemoryStorageLogger::get_log_records
86/// [`clear_log_records`]: MemoryStorageLogger::clear_log_records
87#[derive(Debug, Clone)]
88pub struct MemoryStorageLogger {
89    storage: collections::VecDeque<Record>,
90    max_length: usize,
91}
92
93impl MemoryStorageLogger {
94    /// Construct a new instance of [`MemoryStorageLogger`] using provided inner collection max length number,
95    pub fn new(max_length: usize) -> Self {
96        Self {
97            storage: collections::VecDeque::new(),
98            max_length,
99        }
100    }
101
102    /// Retrieve log records from inner collection.
103    #[inline]
104    pub fn get_log_records(&self) -> collections::VecDeque<Record> {
105        self.storage.clone()
106    }
107
108    /// Clear inner collection of log records.
109    #[inline]
110    pub fn clear_log_records(&mut self) {
111        self.storage.clear()
112    }
113}
114
115impl Logger for MemoryStorageLogger {
116    fn log(&mut self, record: Record) {
117        self.storage.push_back(record);
118        if self.storage.len() > self.max_length {
119            let _ = self.storage.pop_front();
120        }
121    }
122}
123
124impl Logger for Box<MemoryStorageLogger> {
125    fn log(&mut self, record: Record) {
126        (**self).log(record)
127    }
128}
129
130//////////////////////////////////////////////////////////////////////////////////////////////////////////////
131// ChannelLogger
132//////////////////////////////////////////////////////////////////////////////////////////////////////////////
133
134/// This implementation of [`Logger`] trait sends log records ([`Record`]) by the sending-half of underlying
135/// asynchronous channel. You are able to take receiving-half using [`take_receiver`] and [`take_receiver_unchecked`]
136/// methods.
137///
138/// [`take_receiver`]: ChannelLogger::take_receiver
139/// [`take_receiver_unchecked`]: ChannelLogger::take_receiver_unchecked
140#[derive(Debug)]
141pub struct ChannelLogger {
142    sender: mpsc::Sender<Record>,
143    receiver: Option<mpsc::Receiver<Record>>,
144}
145
146impl ChannelLogger {
147    /// Construct a new instance of [`ChannelLogger`].
148    pub fn new() -> Self {
149        let (sender, receiver) = mpsc::channel();
150        Self {
151            sender,
152            receiver: Some(receiver),
153        }
154    }
155
156    /// Take channel receiving-half. Returns [`None`] if it was already taken.
157    #[inline]
158    pub fn take_receiver(&mut self) -> Option<mpsc::Receiver<Record>> {
159        self.receiver.take()
160    }
161
162    /// Take channel receiving-half. Panics if it was already taken.
163    pub fn take_receiver_unchecked(&mut self) -> mpsc::Receiver<Record> {
164        self.take_receiver().unwrap()
165    }
166}
167
168impl Default for ChannelLogger {
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174impl Logger for ChannelLogger {
175    fn log(&mut self, record: Record) {
176        let _ = self.sender.send(record);
177    }
178}
179
180impl Logger for Box<ChannelLogger> {
181    fn log(&mut self, record: Record) {
182        (**self).log(record)
183    }
184}
185
186//////////////////////////////////////////////////////////////////////////////////////////////////////////////
187// FileLogger
188//////////////////////////////////////////////////////////////////////////////////////////////////////////////
189
190/// This implementation of [`Logger`] trait writes log records ([`Record`]) into provided file.
191pub struct FileLogger {
192    file: std::fs::File,
193}
194
195impl FileLogger {
196    /// Construct a new instance of [`FileLogger`] using provided file.
197    pub fn new(file: std::fs::File) -> Self {
198        Self { file }
199    }
200}
201
202impl Logger for FileLogger {
203    fn log(&mut self, record: Record) {
204        let _ = writeln!(
205            self.file,
206            "[{}] {} {}",
207            record.time.format("%+"),
208            record.kind,
209            record.message
210        );
211    }
212}
213
214impl Logger for Box<FileLogger> {
215    fn log(&mut self, record: Record) {
216        (**self).log(record)
217    }
218}
219
220//////////////////////////////////////////////////////////////////////////////////////////////////////////////
221// Tests
222//////////////////////////////////////////////////////////////////////////////////////////////////////////////
223
224#[cfg(test)]
225mod tests {
226    use crate::logger::ChannelLogger;
227    use crate::logger::ConsoleLogger;
228    use crate::logger::FileLogger;
229    use crate::logger::Logger;
230    use crate::logger::MemoryStorageLogger;
231    use crate::record::Record;
232    use crate::record::RecordKind;
233
234    fn assert_unpin<T: Unpin>() {}
235
236    #[test]
237    fn test_unpin() {
238        assert_unpin::<ConsoleLogger>();
239        assert_unpin::<ChannelLogger>();
240        assert_unpin::<MemoryStorageLogger>();
241        assert_unpin::<FileLogger>();
242    }
243
244    #[test]
245    fn test_trait_object_safety() {
246        // Assert traint object construct.
247        let mut console: Box<dyn Logger> = Box::new(ConsoleLogger::new_unchecked("debug"));
248        let mut memory: Box<dyn Logger> = Box::new(MemoryStorageLogger::new(100));
249        let mut channel: Box<dyn Logger> = Box::new(ChannelLogger::new());
250
251        let record = Record::new(RecordKind::Open, String::from("test log record"));
252
253        // Assert that trait object methods are dispatchable.
254        console.log(record.clone());
255        memory.log(record.clone());
256        channel.log(record);
257    }
258
259    fn assert_logger<T: Logger>() {}
260
261    #[test]
262    fn test_box() {
263        assert_logger::<Box<dyn Logger>>();
264        assert_logger::<Box<ConsoleLogger>>();
265        assert_logger::<Box<MemoryStorageLogger>>();
266        assert_logger::<Box<ChannelLogger>>();
267        assert_logger::<Box<FileLogger>>();
268    }
269
270    fn assert_send<T: Send>() {}
271
272    #[test]
273    fn test_send() {
274        assert_send::<ConsoleLogger>();
275        assert_send::<MemoryStorageLogger>();
276        assert_send::<ChannelLogger>();
277        assert_send::<FileLogger>();
278
279        assert_send::<Box<dyn Logger>>();
280        assert_send::<Box<ConsoleLogger>>();
281        assert_send::<Box<MemoryStorageLogger>>();
282        assert_send::<Box<ChannelLogger>>();
283        assert_send::<Box<FileLogger>>();
284    }
285}