elara_log/
lib.rs

1/*!
2This crate provides basic logging capabilities for the
3[Project Elara](https://github.com/elaraproject/) suite of
4open-source software libraries. It can also be used as
5a general-purpose lightweight logging library. It has
6just five logging macros:
7
8```rust
9debug!(some_debug_message)
10error!(some_error_message)
11info!(some_info_message)
12success!(some_success_message)
13warn!(some_success_message)
14```
15
16The macros accept the same format strings as `println!` which
17allows using string substitutions:
18
19```rust
20info!("The Answer to {} is {}.", 
21	  "the Ultimate Question of Life, the Universe, and Everything", 
22	  "42");
23```
24
25To use it in your project, install it with cargo by running:
26
27```bash
28# this crate is listed under a different
29# name to avoid a naming clash with a previous
30# deprecated version of the same library
31cargo add elara-log-ng
32```
33
34Then import the crate and initialize the logger
35in your `main()` **before** calling any of the logging macros:
36
37```rust
38// required in all cases
39// (including if you're using
40// elara-log in a library)
41use elara_log::prelude::*;
42
43fn main() {
44	// for executables/applications only
45	// add this before calling any of
46	// the logging macros
47	Logger::new().init().unwrap();
48}
49```
50*/
51
52// Much thanks to https://github.com/takeshixx/python-tinylogs
53// and https://github.com/borntyping/rust-simple_logger
54// and https://github.com/rust-lang/log
55// of which elara-log takes a lot of its design from
56
57#![allow(non_camel_case_types)]
58use sys_time::DateTime;
59use std::fmt;
60use std::fs::OpenOptions;
61use std::io::Write;
62use std::path::{Path, PathBuf};
63
64// mod time;
65pub mod prelude {
66    pub use crate::{debug, error, info, success, warn, Logger};
67}
68
69const INFO_COLOR: &str = "\x1b[0;34m";
70const SUCCESS_COLOR: &str = "\x1b[0;32m";
71const WARNING_COLOR: &str = "\x1b[0;33m";
72const ERROR_COLOR: &str = "\x1b[0;31m";
73const DEBUG_COLOR: &str = "\x1b[0;35m";
74const ESCAPE: &str = "\x1b[0m";
75const DELIMITER: &str = "\x1b[1;1m";
76const INFO_TEXT: &str = "INFO";
77const SUCCESS_TEXT: &str = "SUCCESS";
78const WARNING_TEXT: &str = "WARNING";
79const ERROR_TEXT: &str = "ERROR";
80const DEBUG_TEXT: &str = "DEBUG";
81
82enum LogfileType {
83    Nofile,
84    FileHandler(PathBuf),
85}
86
87impl LogfileType {
88    fn get_path(&self) -> Option<PathBuf> {
89        match self {
90            LogfileType::FileHandler(p) => Some(p.to_path_buf()),
91            _ => None,
92        }
93    }
94}
95
96pub struct Logger {
97    debug: bool,
98    file: LogfileType,
99    multi: bool,
100}
101
102impl Logger {
103    fn _timestamp(&self) -> String {
104        // let now = SystemTime::now();
105        // let time = time::fmt("%Y-%m-%dT%H:%M:%S");
106        // note: UTC time not current time
107        let now = DateTime::now_utc();
108        format!(
109            "{}-{}-{} {}:{}:{}",
110            now.year(),
111            now.month(),
112            now.day(),
113            now.hour(),
114            now.minute(),
115            now.second()
116        )
117    }
118
119    fn _is_logfile(&self) -> bool {
120        match &self.file {
121            LogfileType::FileHandler(_) => true,
122            _ => false,
123        }
124    }
125
126    fn _is_stdout(&self) -> bool {
127        match &self.file {
128            LogfileType::FileHandler(_) => {
129                if self.multi {
130                    true
131                } else {
132                    false
133                }
134            }
135            LogfileType::Nofile => true,
136        }
137    }
138
139    #[must_use = "You must call init() afterwards to begin logging"]
140    pub fn new() -> Logger {
141        Logger {
142            debug: false,
143            file: LogfileType::Nofile,
144            multi: false,
145        }
146    }
147
148    pub fn init(self) -> Result<(), LoggerInitError> {
149        set_logger(Box::new(self))?;
150        Ok(())
151    }
152
153    pub fn set_debug(&mut self) {
154        self.debug = true;
155    }
156
157    pub fn set_multi(&mut self) {
158        self.multi = true;
159    }
160
161    pub fn set_logfile(&mut self, path_str: &str) {
162        let path = Path::new(&path_str).to_path_buf();
163        self.file = LogfileType::FileHandler(path);
164    }
165
166    pub fn get_path(self) -> Option<PathBuf> {
167        self.file.get_path()
168    }
169
170    fn print(&self, title: &str, color: &str, msg: String) {
171        if self._is_logfile() {
172            let path = &self.file.get_path().unwrap();
173            let mut logfile = OpenOptions::new()
174                .write(true)
175                .create_new(true)
176                .open(&path)
177                .unwrap();
178            let log_message = format!("[{}] {} {}\n", self._timestamp(), title, msg);
179            write!(&mut logfile, "{}", log_message).unwrap();
180        }
181
182        if self._is_stdout() {
183            println!(
184                "{} {}{}{} {}",
185                self._timestamp(),
186                DELIMITER,
187                format!("{}{}{}{}{}", ESCAPE, color, title, ESCAPE, DELIMITER),
188                ESCAPE,
189                msg
190            );
191        }
192    }
193}
194
195pub enum LogLevel {
196    Info,
197    Success,
198    Warn,
199    Debug,
200    Error,
201}
202
203impl Log for Logger {
204    fn log(&self, level: LogLevel, msg: String){
205        match level {
206            LogLevel::Info => self.print(INFO_TEXT, INFO_COLOR, msg),
207            LogLevel::Success => self.print(SUCCESS_TEXT, SUCCESS_COLOR, msg),
208            LogLevel::Warn => self.print(WARNING_TEXT, WARNING_COLOR, msg),
209            LogLevel::Debug => self.print(DEBUG_TEXT, DEBUG_COLOR, msg),
210            LogLevel::Error => self.print(ERROR_TEXT, ERROR_COLOR, msg)
211
212        }
213    }
214}
215
216pub trait Log {
217    fn log(&self, level: LogLevel, msg: String);
218}
219
220#[derive(Debug)]
221pub struct LoggerInitError;
222
223// Dummy logger for non-initialized logger
224struct NoLogger;
225
226impl Log for NoLogger {
227    fn log(&self, _level: LogLevel, _msg: String) {}
228}
229
230impl fmt::Display for LoggerInitError {
231    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
232        write!(f, "Logger was initialized more than once or couldn't initialize.")
233    }
234}
235
236static mut LOGGER: &dyn Log = &NoLogger;
237
238// Uses set_boxed_logger implementation in the `log` crate
239// as it is hard to guarantee 'static for the logger
240pub fn set_logger(logger: Box<dyn Log>) -> Result<(), LoggerInitError> {
241    _set_logger(|| Box::leak(logger))
242}
243
244fn _set_logger<F>(make_logger: F) -> Result<(), LoggerInitError>
245where
246    F: FnOnce() -> &'static dyn Log,
247{
248    unsafe {
249        LOGGER = make_logger();
250    }
251    Ok(())
252}
253
254pub fn logger() -> &'static dyn Log {
255    unsafe { LOGGER }
256}
257
258#[macro_export]
259macro_rules! info {
260    ($($arg:tt)+) => ($crate::logger().log($crate::LogLevel::Info, format!($($arg)+)))
261}
262
263#[macro_export]
264macro_rules! warn {
265    ($($arg:tt)+) => ($crate::logger().log($crate::LogLevel::Warn, format!($($arg)+)))
266}
267
268#[macro_export]
269macro_rules! debug {
270    ($($arg:tt)+) => ($crate::logger().log($crate::LogLevel::Debug, format!($($arg)+)))
271}
272
273#[macro_export]
274macro_rules! success {
275    ($($arg:tt)+) => ($crate::logger().log($crate::LogLevel::Success, format!($($arg)+)))
276}
277
278#[macro_export]
279macro_rules! error {
280    ($($arg:tt)+) => ($crate::logger().log($crate::LogLevel::Error, format!($($arg)+)))
281}