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