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}