mod_logger/
lib.rs

1//! A consumer for the log crate
2//!
3//! The crate implements a logger that allows module-wise configuration of logging through
4//! configuration files or an API.
5//!
6//! Features
7//! * Log output can be written to stdout, stderr, to file or to a memory buffer.
8//! * Log output can be colored.
9//! * Features can be set using a configuration file or the API
10//!
11//! The configuration file can be enabled by setting the environment variable ```LOG_CONFIG``` to the
12//! path of the file. The configuration is specified in YAML format and allows to set the following
13//! values. All values are optional.
14//!
15//! * default_level: The default log level, one of trace, debug, info, warn, error, defaults to info
16//! * mod_level: A list of module name and log level pairs
17//! * log_dest: One of stdout, stderr, stream, buffer, streamstdout, streamstderr, bufferstdout, bufferstderr.
18//! * log_stream: The log file name for stream variants of log_dest
19//! * color: one of ```true``` or ```false```
20//! * brief_info: one of ```true``` or ```false```
21//!
22//! Sample:
23//! ```yaml
24//! log_level: warn
25//! log_dest: streamstderr
26//! log_stream: debug.log
27//! color: true
28//! brief_info: true
29//! mod_level:
30//!   'test_mod': debug
31//!   'test_mod::test_test': trace
32//! ```
33//!
34
35use chrono::Local;
36use colored::*;
37use log::{Log, Metadata, Record};
38use regex::Regex;
39use std::env;
40use std::fs::File;
41#[cfg(feature = "config")]
42use std::fs::OpenOptions;
43use std::io::{stderr, stdout, BufWriter, Write};
44use std::mem;
45use std::sync::{Arc, Mutex, Once};
46
47//, BufWriter};
48mod error;
49
50use error::{Error, ErrorKind, Result};
51use std::path::Path;
52
53#[cfg(feature = "config")]
54pub mod config;
55
56#[cfg(feature = "config")]
57pub use config::LogConfig;
58
59#[cfg(feature = "config")]
60pub use config::LogConfigBuilder;
61
62mod logger_params;
63
64pub use logger_params::LogDestination;
65use logger_params::LoggerParams;
66
67pub(crate) const DEFAULT_LOG_LEVEL: Level = Level::Info;
68
69// cannot be STREAM !!
70pub(crate) const DEFAULT_LOG_DEST: LogDestination = LogDestination::Stderr;
71
72pub const NO_STREAM: Option<Box<dyn 'static + Write + Send>> = None;
73
74use crate::error::ToError;
75pub use log::Level;
76
77// TODO: implement size limit for memory buffer
78// TODO: Drop initialise functions and rather use a set_config function that can repeatedly reset the configuration
79
80/// The Logger struct holds a singleton containing all relevant information.
81///
82/// struct Logger has a private constructor. It is used via its static interface which will
83/// instantiate a Logger or use an existing one.
84#[derive(Clone)]
85pub struct Logger {
86    inner: Arc<Mutex<LoggerParams>>,
87    module_re: Regex,
88    exe_name: Option<String>,
89}
90
91impl Logger {
92    /// Create a new Logger or retrieve the existing one.\
93    /// The function is private, Logger is meant to be used via its static interface
94    /// Any of the static functions will initialise a Logger instance
95    fn new() -> Logger {
96        static mut LOGGER: *const Logger = 0 as *const Logger;
97        static ONCE: Once = Once::new();
98
99        // dbg!("Logger::new: entered");
100
101        let exe_name = match env::current_exe() {
102            Ok(exe_name) => match exe_name.file_name() {
103                Some(exe_name) => exe_name
104                    .to_str()
105                    .map(|name| name.to_owned().replace('-', "_")),
106                None => None,
107            },
108            Err(_why) => None,
109        };
110
111        let logger = unsafe {
112            ONCE.call_once(|| {
113                let singleton = Logger {
114                    module_re: Regex::new(r#"^([^:]+)::(.*)$"#).unwrap(),
115                    inner: Arc::new(Mutex::new(LoggerParams::new(DEFAULT_LOG_LEVEL))),
116                    exe_name,
117                };
118
119                // Put it in the heap so it can outlive this call
120                LOGGER = mem::transmute(Box::new(singleton));
121            });
122
123            (*LOGGER).clone()
124        };
125
126        //  is initialised tests and sets the flag
127        if !logger.inner.lock().unwrap().initialised() {
128            // looks like we only just created it
129            // look for LOG_CONFIG in ENV
130            #[cfg(feature = "config")]
131            if let Ok(config_path) = env::var("LOG_CONFIG") {
132                // eprintln!("LOG_CONFIG={}", config_path);
133                match LogConfigBuilder::from_file(&config_path) {
134                    Ok(ref log_config) => match logger.int_set_log_config(log_config.build()) {
135                        Ok(_res) => (),
136                        Err(why) => {
137                            eprintln!(
138                                "Failed to apply log config from file: '{}', error: {:?}",
139                                config_path, why
140                            );
141                        }
142                    },
143                    Err(why) => {
144                        eprintln!(
145                            "Failed to read log config from file: '{}', error: {:?}",
146                            config_path, why
147                        );
148                    }
149                }
150            }
151
152            // potential race condition here regarding max_level
153
154            match log::set_boxed_logger(Box::new(logger.clone())) {
155                Ok(_dummy) => (),
156                Err(why) => {
157                    dbg!(why);
158                }
159            }
160
161            log::set_max_level(logger.inner.lock().unwrap().max_level().to_level_filter());
162        }
163
164        // dbg!("Logger::new: done");
165        // Now we give out a copy of the data that is safe to use concurrently.
166        logger
167    }
168
169    /// Flush the contents of log buffers
170    pub fn flush() {
171        Logger::new().flush();
172    }
173
174    /// create a default logger
175    pub fn create() {
176        let _logger = Logger::new();
177    }
178
179    /// Initialise a Logger with the given default log_level or modify the default log level of the
180    /// existing logger
181    pub fn set_default_level(log_level: Level) {
182        let logger = Logger::new();
183        let mut guarded_params = logger.inner.lock().unwrap();
184        let last_max_level = *guarded_params.max_level();
185        let max_level = guarded_params.set_default_level(log_level);
186
187        if last_max_level != max_level {
188            log::set_max_level(max_level.to_level_filter());
189        }
190    }
191
192    /// Retrieve the default level of the logger
193    pub fn get_default_level(&self) -> Level {
194        let guarded_params = self.inner.lock().unwrap();
195        guarded_params.get_default_level()
196    }
197
198    /// Modify the log level for a module
199    pub fn set_mod_level(module: &str, log_level: Level) {
200        let logger = Logger::new();
201        let mut guarded_params = logger.inner.lock().unwrap();
202        let last_max_level = *guarded_params.max_level();
203        let max_level = guarded_params.set_mod_level(module, log_level);
204        if last_max_level != *max_level {
205            log::set_max_level(max_level.to_level_filter());
206        }
207    }
208
209    /// Retrieve the current log buffer, if available
210    pub fn get_buffer() -> Option<Vec<u8>> {
211        let logger = Logger::new();
212        let mut guarded_params = logger.inner.lock().unwrap();
213        guarded_params.retrieve_log_buffer()
214    }
215
216    /// Set the log destination
217    pub fn set_log_dest<S: 'static + Write + Send>(
218        dest: &LogDestination,
219        stream: Option<S>,
220    ) -> Result<()> {
221        let logger = Logger::new();
222        logger.flush();
223        let mut guarded_params = logger.inner.lock().unwrap();
224        guarded_params.set_log_dest(dest, stream)
225    }
226
227    /// Set log destination  and log file.
228    pub fn set_log_file(log_dest: &LogDestination, log_file: &Path, buffered: bool) -> Result<()> {
229        let dest = if log_dest.is_stdout() {
230            LogDestination::StreamStdout
231        } else if log_dest.is_stderr() {
232            LogDestination::StreamStderr
233        } else {
234            LogDestination::Stream
235        };
236
237        let mut stream: Box<dyn Write + Send> = if buffered {
238            Box::new(BufWriter::new(
239                File::create(log_file).upstream_with_context(&format!(
240                    "Failed to create file: '{}'",
241                    log_file.display()
242                ))?,
243            ))
244        } else {
245            Box::new(File::create(log_file).upstream_with_context(&format!(
246                "Failed to create file: '{}'",
247                log_file.display()
248            ))?)
249        };
250
251        let logger = Logger::new();
252        logger.flush();
253
254        let mut guarded_params = logger.inner.lock().unwrap();
255        let buffer = guarded_params.retrieve_log_buffer();
256
257        if let Some(buffer) = buffer {
258            stream
259                .write_all(buffer.as_slice())
260                .upstream_with_context(&format!(
261                    "Failed to write buffers to file: '{}'",
262                    log_file.display()
263                ))?;
264            stream.flush().upstream_with_context(&format!(
265                "Failed to flush buffers to file: '{}'",
266                log_file.display()
267            ))?;
268        }
269
270        guarded_params.set_log_dest(&dest, Some(stream))
271    }
272
273    /// Retrieve the current log destination
274    pub fn get_log_dest() -> LogDestination {
275        let logger = Logger::new();
276        let guarded_params = logger.inner.lock().unwrap();
277        guarded_params.get_log_dest().clone()
278    }
279
280    /// Set the log configuration.
281    #[cfg(feature = "config")]
282    pub fn set_log_config(log_config: &LogConfig) -> Result<()> {
283        Logger::new().int_set_log_config(log_config)
284    }
285
286    /// Enable / disable colored output
287    pub fn set_color(color: bool) {
288        let logger = Logger::new();
289        let mut guarded_params = logger.inner.lock().unwrap();
290        guarded_params.set_color(color)
291    }
292
293    /// Enable / disable timestamp in messages
294    pub fn set_timestamp(val: bool) {
295        let logger = Logger::new();
296        let mut guarded_params = logger.inner.lock().unwrap();
297        guarded_params.set_timestamp(val)
298    }
299
300    /// Enable / disable timestamp in messages
301    pub fn set_millis(val: bool) {
302        let logger = Logger::new();
303        let mut guarded_params = logger.inner.lock().unwrap();
304        guarded_params.set_millis(val)
305    }
306
307    /// Enable / disable brief info messages
308    pub fn set_brief_info(val: bool) {
309        let logger = Logger::new();
310        let mut guarded_params = logger.inner.lock().unwrap();
311        guarded_params.set_brief_info(val)
312    }
313
314    #[cfg(feature = "config")]
315    fn int_set_log_config(&self, log_config: &LogConfig) -> Result<()> {
316        let mut guarded_params = self.inner.lock().unwrap();
317        let last_max_level = *guarded_params.max_level();
318
319        guarded_params.set_default_level(log_config.get_default_level());
320
321        let max_level = guarded_params.set_mod_config(log_config.get_mod_level());
322        if max_level != &last_max_level {
323            log::set_max_level(max_level.to_level_filter());
324        }
325
326        let log_dest = guarded_params.get_log_dest();
327        let cfg_log_dest = log_config.get_log_dest();
328        let stream_log = cfg_log_dest.is_stream_dest();
329
330        if cfg_log_dest != log_dest || stream_log {
331            if stream_log {
332                if let Some(log_stream) = log_config.get_log_stream() {
333                    guarded_params.set_log_dest(
334                        cfg_log_dest,
335                        Some(
336                            OpenOptions::new()
337                                .append(true)
338                                .create(true)
339                                .open(log_stream)
340                                .upstream_with_context(&format!(
341                                    "Failed to open log file: '{}'",
342                                    log_stream.display()
343                                ))?,
344                        ),
345                    )?;
346                } else {
347                    return Err(Error::with_context(
348                        ErrorKind::InvParam,
349                        &format!(
350                            "Missing parameter log_stream for destination {:?}",
351                            cfg_log_dest
352                        ),
353                    ));
354                }
355            } else {
356                guarded_params.set_log_dest(cfg_log_dest, NO_STREAM)?;
357            }
358        }
359
360        guarded_params.set_color(log_config.is_color());
361        guarded_params.set_brief_info(log_config.is_brief_info());
362
363        Ok(())
364    }
365}
366
367impl Log for Logger {
368    fn enabled(&self, _metadata: &Metadata) -> bool {
369        true
370    }
371
372    fn log(&self, record: &Record) {
373        let (mod_name, mod_tag) = if let Some(mod_path) = record.module_path() {
374            if let Some(ref exe_name) = self.exe_name {
375                if let Some(ref captures) = self.module_re.captures(mod_path) {
376                    if captures.get(1).unwrap().as_str() == exe_name {
377                        (
378                            mod_path.to_owned(),
379                            captures.get(2).unwrap().as_str().to_owned(),
380                        )
381                    } else {
382                        (mod_path.to_owned(), mod_path.to_owned())
383                    }
384                } else if mod_path == exe_name {
385                    (mod_path.to_owned(), String::from("main"))
386                } else {
387                    (mod_path.to_owned(), mod_path.to_owned())
388                }
389            } else {
390                (mod_path.to_owned(), mod_path.to_owned())
391            }
392        } else {
393            (String::from("undefined"), String::from("undefined"))
394        };
395
396        let curr_level = record.metadata().level();
397
398        let mut guarded_params = self.inner.lock().unwrap();
399        let mut level = guarded_params.get_default_level();
400        if let Some(mod_level) = guarded_params.get_mod_level(&mod_tag) {
401            level = mod_level;
402        }
403
404        if curr_level <= level {
405            let timestamp = if guarded_params.timestamp() {
406                let now = Local::now();
407                if guarded_params.millis() {
408                    let ts_millis = now.timestamp_millis() % 1000;
409                    format!("{}.{:03} ", now.format("%Y-%m-%d %H:%M:%S"), ts_millis)
410                } else {
411                    format!("{} ", now.format("%Y-%m-%d %H:%M:%S"))
412                }
413            } else {
414                "".to_owned()
415            };
416
417            let mut output = if guarded_params.brief_info() && (curr_level == Level::Info) {
418                format!(
419                    "{}{:<5} {}\n",
420                    timestamp,
421                    record.level().to_string(),
422                    record.args()
423                )
424            } else {
425                format!(
426                    "{}{:<5} [{}] {}\n",
427                    timestamp,
428                    record.level().to_string(),
429                    &mod_name,
430                    record.args()
431                )
432            };
433
434            if guarded_params.color() {
435                output = match curr_level {
436                    Level::Error => format!("{}", output.red()),
437                    Level::Warn => format!("{}", output.yellow()),
438                    Level::Info => format!("{}", output.green()),
439                    Level::Debug => format!("{}", output.cyan()),
440                    Level::Trace => format!("{}", output.blue()),
441                };
442            }
443
444            let _res = match guarded_params.get_log_dest() {
445                LogDestination::Stderr => stderr().write(output.as_bytes()),
446                LogDestination::Stdout => stdout().write(output.as_bytes()),
447                LogDestination::Stream => {
448                    if let Some(ref mut stream) = guarded_params.log_stream() {
449                        stream.write(output.as_bytes())
450                    } else {
451                        stderr().write(output.as_bytes())
452                    }
453                }
454                LogDestination::StreamStdout => {
455                    if let Some(ref mut stream) = guarded_params.log_stream() {
456                        let _wres = stream.write(output.as_bytes());
457                    }
458                    stdout().write(output.as_bytes())
459                }
460                LogDestination::StreamStderr => {
461                    if let Some(ref mut stream) = guarded_params.log_stream() {
462                        let _wres = stream.write(output.as_bytes());
463                    }
464                    stderr().write(output.as_bytes())
465                }
466                LogDestination::Buffer => {
467                    if let Some(ref mut buffer) = guarded_params.log_buffer() {
468                        buffer.write(output.as_bytes())
469                    } else {
470                        stderr().write(output.as_bytes())
471                    }
472                }
473                LogDestination::BufferStdout => {
474                    if let Some(ref mut buffer) = guarded_params.log_buffer() {
475                        let _wres = buffer.write(output.as_bytes());
476                    }
477                    stdout().write(output.as_bytes())
478                }
479                LogDestination::BufferStderr => {
480                    if let Some(ref mut buffer) = guarded_params.log_buffer() {
481                        let _wres = buffer.write(output.as_bytes());
482                    }
483                    stderr().write(output.as_bytes())
484                }
485            };
486        }
487    }
488
489    fn flush(&self) {
490        let mut guarded_params = self.inner.lock().unwrap();
491        guarded_params.flush();
492    }
493}
494
495/*
496#[cfg(test)]
497mod test {
498    use log::{info};
499    use crate::{Logger, LogDestination};
500    #[test]
501    fn log_to_mem() {
502        Logger::initialise(Some("debug")).unwrap();
503        let buffer: Vec<u8> = vec![];
504
505        Logger::set_log_dest(&LogDestination::STREAM, Some(buffer)).unwrap();
506
507        info!("logging to memory buffer");
508
509        assert!(!buffer.is_empty());
510    }
511}
512*/