Skip to main content

async_logger_log/
lib.rs

1//! Asynchronous logger is a performant implementation of [log](https://docs.rs/log) facade. The implementation is
2//! based on [async_logger](https://docs.rs/async_logger) crate, and allows non-blocking writes of 
3//! references of log records in memory buffer. The messages in turn then processed in separate thread 
4//! by writer (see more details in `async_logger` documentation).
5//!
6//! Default log record format includes date, time, timezone, log level, target, and log message
7//! itself. Log record example:
8//!
9//! > [2020-03-15 11:47:32.339865887+0100 WARN thread]: log message.
10//!
11//! The log record format, and other parameters are customizable with `LoggerBuilder`.
12//!
13//! # Examples
14//!
15//! ```
16//! use async_logger_log::Logger;
17//! use log::{info, warn};
18//!
19//! let logger = Logger::new("/tmp", 256, 10*1024*1024).expect("Failed to create Logger instance");
20//!
21//! log::set_boxed_logger(Box::new(logger)).expect("Failed to set logger");
22//! log::set_max_level(log::LevelFilter::Info);
23//!
24//! info!("{}", "test msg");
25//! warn!("{}", "warning msg");
26//!
27//! log::logger().flush();
28//! ```
29//!
30//! Custom writer and formatter:
31//!
32//! ```
33//! use async_logger_log::Logger;
34//! use async_logger::Writer;
35//! use log::{debug, Record};
36//!
37//! // Custom formatting of `log::Record`
38//! fn custom_formatter(record: &Record) -> String {
39//!     format!("log record: {}\n", record.args())
40//! }
41//! 
42//! struct StdoutWriter {}
43//! 
44//! // Writer simply prints log messages to stdout
45//! impl Writer<Box<String>> for StdoutWriter {
46//! 
47//!     fn process_slice(&mut self, slice: &[Box<String>]) {
48//!         for item in slice {
49//!             println!("{}", **item);
50//!         }
51//!     }
52//! 
53//!     fn flush(&mut self) { }
54//! }
55//! 
56//! let logger = Logger::builder()
57//!     .buf_size(256)
58//!     .formatter(custom_formatter)
59//!     .writer(Box::new(StdoutWriter {}))
60//!     .build()
61//!     .unwrap();
62//! 
63//! log::set_boxed_logger(Box::new(logger)).expect("Failed to set logger");
64//! log::set_max_level(log::LevelFilter::Trace);
65//! 
66//! debug!("{}", "Hello, Wrold!");
67//! 
68//! log::logger().flush();
69//! ```
70//!
71//! ### Notes
72//!
73//! The formatting is done by the caller of logging macros. This operation produces `String` insance 
74//! containing complete log message. Then the reference of that `String` instance is passed to
75//! the underling non-blocking queue. The log message is then fetched, processed, and dropped by the writer thread. 
76//! So, the cost for the client mostly consists of allocation and building of log message string. 
77//!
78//! The logger doesn't drop the log messages if the queue is full. In that case the operation
79//! blocks until there is free slot in one of the queue buffers.
80//!
81//! Dependency on `time` crate is optional and can be excluded by adding in Cargo.toml:
82//!
83//! ``` toml
84//! [dependencies.async_logger_log]
85//! default-features = false
86//! ```
87
88
89
90extern crate async_logger;
91extern crate log;
92
93#[cfg(feature="time")]
94extern crate time;
95
96
97use log::{Log, Metadata, Record};
98use async_logger::{AsyncLoggerNB, FileWriter, Error};
99use std::sync::Arc;
100
101#[cfg(feature="time")]
102use time::OffsetDateTime;
103
104
105const DEFAULT_BUF_SIZE: usize = 256;
106const DEFAULT_LOG_FILE_SIZE: usize = 10*1024*1024;
107
108
109/// Log trait implementation.
110pub struct Logger {
111    async_logger: Arc<AsyncLoggerNB<Box<String>>>,
112    formatter: fn(&Record) -> String,
113}
114
115impl Logger {
116
117    /// Creates a new logger instance and registers itself in the log facade.
118    ///
119    /// `buf_sz`: number of messages that internal buffer can hold.
120    ///
121    /// `file_size`: the size in bytes after which log file rotation occurs.
122    pub fn new(log_dir: &str, buf_sz: usize, file_size: usize) -> Result<Logger, Error> {
123
124        let writer = FileWriter::new(log_dir, file_size)?;
125
126        let async_logger = Arc::new(AsyncLoggerNB::new(Box::new(writer), buf_sz)?);
127
128        let formatter = Logger::format_msg;
129
130        Ok(Logger {
131            async_logger,
132            formatter,
133        })
134    }
135
136    /// Return `LoggerBuilder`
137    pub fn builder() -> LoggerBuilder {
138        LoggerBuilder {
139            buf_sz: None,
140            writer: None,
141            formatter: None,
142        }
143    }
144
145    fn format_msg(record: &Record) -> String {
146
147        let time;
148        #[cfg(feature="time")] 
149        {
150            time = OffsetDateTime::now_local().format("%Y-%m-%d %H:%M:%S.%N%z");
151        }
152        #[cfg(not(feature="time"))] 
153        {
154            time = std::time::SystemTime::now()
155                .duration_since(std::time::UNIX_EPOCH)
156                .unwrap_or(std::time::Duration::new(0,0))
157                .as_secs();
158        }
159
160        format!("[{} {} {}]: {}\n", time, record.level(), record.target(), record.args())
161    }
162}
163
164impl Log for Logger {
165
166    fn enabled(&self, metadata: &Metadata) -> bool {
167
168        return metadata.level() <= log::max_level();
169    }
170
171    fn log(&self, record: &Record) {
172
173        let msg = (self.formatter)(record);
174
175        let _ = self.async_logger.write_value(Box::new(msg));
176    }
177
178    fn flush(&self) {
179
180        AsyncLoggerNB::flush(&self.async_logger);
181    }
182}
183
184/// Builder of `Logger` instance. It can be used to provide custom record formatter, and writer
185/// implementation.
186pub struct LoggerBuilder {
187    buf_sz: Option<usize>,
188    writer: Option<Box<dyn async_logger::Writer<Box<String>>>>,
189    formatter: Option<fn(&Record) -> String>,
190}
191
192impl LoggerBuilder {
193
194    /// Set the size of the pair of underlying buffers. The default is 256 messages each.
195    pub fn buf_size(mut self, size: usize) -> Self {
196        self.buf_sz = Some(size);
197        self
198    }
199
200    /// Set custom formatter of log records.
201    pub fn formatter(mut self, formatter: fn(&Record) -> String) -> Self {
202        self.formatter = Some(formatter);
203        self
204    }
205
206    /// Set custom writer implementation. The default is `FileWriter`.
207    pub fn writer(mut self, writer: Box<dyn async_logger::Writer<Box<String>>>) -> Self {
208        self.writer = Some(writer);
209        self
210    }
211
212    /// Build the `Logger` instance.
213    pub fn build(self) -> Result<Logger,Error> {
214
215        let buf_sz = match self.buf_sz {
216            Some(buf_sz) => buf_sz,
217            None => DEFAULT_BUF_SIZE,
218        };
219        
220        let writer = match self.writer {
221            Some(writer) => writer,
222            None => Box::new(FileWriter::new(".", DEFAULT_LOG_FILE_SIZE)?),
223        };
224
225        let formatter = match self.formatter {
226            Some(formatter) => formatter,
227            None => Logger::format_msg,
228        };
229
230        let async_logger = Arc::new(AsyncLoggerNB::new(writer, buf_sz)?);
231
232        Ok(Logger {
233            async_logger,
234            formatter,
235        })
236    }
237}
238
239#[cfg(test)]
240mod tests {
241
242    use super::*;
243    use async_logger::ErrorKind;
244    use std::path::Path;
245
246
247    const LOG_DIR: &str = "/tmp/AsyncLoggerNBTest_000239400377";
248    const NONEXISTING_LOG_DIR: &str = "/tmp/AsyncLoggerNBTest_85003857407";
249    const LOG_FILE_SIZE: usize = 4096;
250
251    #[test]
252    fn test_error() {
253
254        // via new
255
256        if Path::new(LOG_DIR).exists() {
257            std::fs::remove_dir_all(LOG_DIR).expect("Failed to delete test dir on cleanup");
258        }
259
260        std::fs::create_dir(LOG_DIR).expect("Failed to create test dir");
261
262        match Logger::new(LOG_DIR, 0, LOG_FILE_SIZE) {
263            Err(e) if e.kind() == ErrorKind::IncorrectBufferSize => {},
264            _ => panic!("Expected error, got Ok!"),
265        }
266
267        std::fs::remove_dir_all(LOG_DIR).expect("Failed to delete test dir on cleanup");
268        std::fs::create_dir(LOG_DIR).expect("Failed to create test dir");
269
270        match Logger::new(LOG_DIR, std::usize::MAX, LOG_FILE_SIZE) {
271            Err(e) if e.kind() == ErrorKind::IncorrectBufferSize => {},
272            _ => panic!("Expected error, got Ok!"),
273        }
274
275        std::fs::remove_dir_all(LOG_DIR).expect("Failed to delete test dir on cleanup");
276
277        match Logger::new(NONEXISTING_LOG_DIR, 100, LOG_FILE_SIZE) {
278            Err(e) if e.kind() == ErrorKind::IoError => {
279            },
280            _ => panic!("Expected error, got Ok!"),
281        }
282
283        // via builder 
284
285        std::fs::create_dir(LOG_DIR).expect("Failed to create test dir");
286
287        let writer = FileWriter::new(LOG_DIR, LOG_FILE_SIZE).expect("Failed to create file writer");
288
289        match Logger::builder()
290            .buf_size(0)
291            .writer(Box::new(writer))
292            .build() 
293        {
294            Err(e) if e.kind() == ErrorKind::IncorrectBufferSize => {},
295            _ => panic!("Expected error, got Ok!"),
296        }
297
298        std::fs::remove_dir_all(LOG_DIR).expect("Failed to delete test dir on cleanup");
299        std::fs::create_dir(LOG_DIR).expect("Failed to create test dir");
300
301        let writer = FileWriter::new(LOG_DIR, LOG_FILE_SIZE).expect("Failed to create file writer");
302
303        match Logger::builder()
304            .buf_size(std::usize::MAX)
305            .writer(Box::new(writer))
306            .build() 
307        {
308            Err(e) if e.kind() == ErrorKind::IncorrectBufferSize => {},
309            _ => panic!("Expected error, got Ok!"),
310        }
311
312        std::fs::remove_dir_all(LOG_DIR).expect("Failed to delete test dir on cleanup");
313    }
314}