bt_logger/
lib.rs

1/// A simple and lightweight logger for Rust with various features such as logging to different output destinations (stdout, stderr), formatting log messages, and level checking.
2/// 
3/// LogLevel
4/// Represents the different levels of log severity. The values are ordered from lowest to highest severity.
5/// NONE = No logging,
6/// FATAL = Fatal error,
7/// ERROR = Error message,
8/// WARN = Warning message,
9/// INFO = Informational message,
10/// DEBUG = Debugging message,
11/// TRACE = Trace message,
12/// VERBOSE = Very verbose message
13/// 
14/// LogTarget
15/// Represents the different output destinations for log messages.
16/// STD_ERROR: Logs to stderr
17/// STD_OUT: Logs to stdout
18/// STD_BOTH: Logs to both stdout and stderr
19/// 
20/// Macros
21/// bt_logger provides several macros for logging different levels of messages. Each macro:
22/// - Checks if the log level is sufficient to log the message.
23/// - If sufficient, formats the message using get_formatted_msg.
24/// - Calls log_msg with the formatted message.
25/// 
26/// log_fatal!(function name, message, message arguments)
27/// log_error!(function name, message, message arguments)
28/// log_warning!(function name, message, message arguments)
29/// log_info!(function name, message, message arguments)
30/// log_debug!(function name, message, message arguments)
31/// log_trace!(function name, message, message arguments)
32/// log_verbose!(function name, message, message arguments)
33/// get_fatal!(function name, message, message arguments)
34/// get_error!(function name, message, message arguments)
35/// 
36/// Usage
37/// To use the bt_logger module, you would create a logger instance with the desired configuration and then use the macros to log messages at different levels. For example:
38/// build_logger("BACHUETECH", "My Application", LogLevel::INFO, LogTarget::StdOut);
39/// .......
40/// log_info!("function_name","Hello, {}", "Bachuetech User");
41///
42/// This code builds a logger instance with the tag BACHUETECH, application name My Application, and log level INFO. It then uses the log_info! macro to log a message to stdout.
43/// If the logger is not create the application will stop with the first call of a loggin macro
44/// build the logger as early as possible to avoid issue using the build_logger
45/// 
46/// The log output is:
47/// TAG APPLICATION CURRENT_UTC_TIME(YYYY-mm-ddTHH:MM:SS.3fz) level(one capital letter) source(module path::function)|>|message
48
49    pub(crate) mod io_utils;
50
51    use std::path::PathBuf;
52    use std::str::FromStr;
53    use std::{env, fmt, fs};
54    use chrono::prelude::*;
55    use once_cell::sync::{Lazy, OnceCell};
56    use tokio::runtime::Runtime;
57
58    use crate::io_utils::log_to_file;
59
60    #[macro_use]
61    pub mod macros;
62
63
64const LOG_ENV_VAR_LOG_LEVEL: &str = "btlogger_log_level";
65const LOG_ENV_VAR_LOG_OUTPUT: &str = "btlogger_log_output";
66
67/// Create a static runtime
68static ASYNC_RUNTIME: Lazy<Runtime> = Lazy::new(|| {
69    Runtime::new().expect("Failed to create Tokio runtime")
70});
71
72static LOGGER: OnceCell<Logger> = OnceCell::new();
73
74/// LogLevel
75/// Represents the different levels of log severity. The values are ordered from lowest to highest severity.
76/// NONE = No logging,
77/// FATAL = Fatal error,
78/// ERROR = Error message,
79/// WARN = Warning message,
80/// INFO = Informational message,
81/// DEBUG = Debugging message,
82/// TRACE = Trace message,
83/// VERBOSE = Very verbose message
84    #[repr(u8)]
85    #[derive(Clone)]
86    pub enum LogLevel{
87        NONE = 100,
88        FATAL = 60,
89        ERROR = 50,
90        WARN = 40,
91        INFO = 30,
92        DEBUG = 20,
93        TRACE = 10,
94        VERBOSE = 0,
95    }
96    
97    impl fmt::Display for LogLevel{
98        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99            match *self{
100                LogLevel::NONE    => write!(f, "N"),
101                LogLevel::FATAL   => write!(f, "F"),
102                LogLevel::ERROR   => write!(f, "E"),
103                LogLevel::WARN    => write!(f, "W"),
104                LogLevel::INFO    => write!(f, "I"),
105                LogLevel::DEBUG   => write!(f, "D"),
106                LogLevel::TRACE   => write!(f, "T"),
107                LogLevel::VERBOSE => write!(f, "V"),
108            }
109        }
110    }
111
112    impl LogLevel {
113        fn from_str(log_level: &str) -> LogLevel{
114            match log_level.to_uppercase().as_str(){
115                "N" | "NONE" => LogLevel::NONE,
116                "F" | "FATAL" => LogLevel::FATAL,
117                "E" | "ERR" | "ERROR" => LogLevel::ERROR,
118                "W" | "WARN" | "WARNING"=> LogLevel::WARN,
119                "I" | "INFO" => LogLevel::INFO,
120                "D" | "DEBUG" => LogLevel::DEBUG,
121                "T" | "TRACE" => LogLevel::TRACE,
122                "V" | "VERB" | "VERBOSE"=> LogLevel::VERBOSE,
123                _ => LogLevel::NONE,
124            }
125        }
126    
127    }
128/// LogTarget
129/// Represents the different output destinations for log messages.
130/// STD_ERROR: Logs to stderr
131/// STD_OUT: Logs to stdout
132/// STD_BOTH: Logs to both stdout and stderr
133/// NONE: Does not send log to any of the STD.
134    #[allow(non_camel_case_types)]
135    #[derive(Debug, Clone)]
136    pub enum LogTarget{
137        STD_ERROR,
138        STD_OUT,
139        STD_BOTH,
140        NONE,
141    }
142    
143    impl LogTarget {
144        pub fn from_str(log_destination: &str) -> LogTarget{
145            match log_destination.to_uppercase().as_str(){
146                "ERR" | "ERROR" | "STDERR" | "E" => LogTarget::STD_ERROR,
147                "STANDARD" | "STD" | "STDOUT" | "S" | "O" => LogTarget::STD_OUT,
148                "NONE" | "N" => LogTarget::NONE,
149                _ => LogTarget::STD_ERROR
150            }
151        }
152    }
153/// Logger
154/// Represents a logger instance with its configuration.
155/// log_tag: The tag used in log messages
156/// log_app: The application name used in log messages
157/// current_log_level_value: The current log level value (0-100)
158/// output_destination: The output destination for log messages (StdError, StdOut, or StdBoth)
159    #[derive(Clone)]
160    pub struct Logger{ //ToDo, check if this can be private
161        log_tag: String,
162        log_app: String,
163        current_log_level_value: u8,
164        output_destination: LogTarget,
165        destination_file: Option<String>,
166    }
167    
168    impl Logger{
169        ///Creates a new logger instance with the given configuration.
170        ///If file cannot be open then it will be ignored as None.
171        fn new(tag: &str, application: &str, level: LogLevel, output:LogTarget, destination_file: Option<String>) -> Self{
172            let dest = if destination_file.is_some() {
173                match PathBuf::from_str(&destination_file.unwrap()){
174                    Ok(file_path) => {
175                                //if let Some(fp) = file_path.to_str(){
176                                if let Some(parent) = file_path.parent() {
177                                    if fs::create_dir_all(parent).is_err(){ None } else { let p: String = file_path.to_string_lossy().into(); Some(p)}
178                                }else{ None }
179                    },
180                    Err(_) => None,
181                }
182            }else {None};
183
184            Logger { log_tag: tag.to_owned(), log_app: application.to_owned(),
185                     current_log_level_value: level as u8, output_destination: output,
186                     destination_file: dest.into() }
187        }
188    
189        ///Returns the current time as a string in the format "YYYY-MM-DDTHH:MM:SS.SSSZ".
190        pub(crate) fn get_current_time() -> String{
191            let current_time: DateTime<Utc> = Utc::now();
192            // Format the current time as a string
193            current_time.format("%Y-%m-%dT%H:%M:%S%.3f%z").to_string()
194        }
195    
196        ///Formats a log message with the given level, source, and message.
197        fn get_formatted_msg(&self, level: LogLevel, source: &str, msg: &String) -> String{
198            let current_time = Logger::get_current_time();
199            format!("{} {} {} {} {}|>|{}", self.log_tag, self.log_app, current_time, level, source, msg)
200        }
201   
202        ///Logs a message with the given level, message, and module and function name.
203        pub fn log_msg(&self, msg: &String, level: LogLevel, module: &str, function: &str){ //source: &str){
204           let log_msg = self.get_formatted_msg(level, &format!("{}::{}",module, function), msg);
205           let output_dest = self.output_destination.clone();
206           let dest_file = self.destination_file.clone();
207
208            ASYNC_RUNTIME.spawn(async move{
209                match output_dest {
210                    LogTarget::STD_ERROR =>{log_stderr(&log_msg);},
211                    LogTarget::STD_OUT =>  {log_stdout(&log_msg);},
212                    LogTarget::STD_BOTH => {log_stderr(&log_msg);
213                                            log_stdout(&log_msg); },
214                    LogTarget::NONE => (),
215                }
216                if let Some(df) = dest_file {
217                    log_to_file(df.as_str(),log_msg.as_str());
218                }
219            });
220        }
221    
222        ///Get formatted message with the given level, message, and module and function name.
223        pub fn get_msg(&self, msg: &String, level: LogLevel, module: &str, function: &str) -> String { //source: &str){
224            let log_msg = self.get_formatted_msg(level, &format!("{}::{}",module, function), msg);
225            log_msg
226        }
227
228        //Check if a particular log_level has to be logged based on the currect configuration
229        pub fn log_this(&self, log_level: LogLevel) -> bool{
230            if log_level as u8 >= self.current_log_level_value {
231                return true
232            }
233            false
234        }
235    }
236    
237    /*lazy_static! {
238        static ref LOGGER: Mutex<Option<Logger>> = Mutex::new(None);
239    }*/
240
241    ///Logs a message to stdout.
242    fn log_stdout(message: &String ){
243        println!("{}", message);
244    }
245
246    ///Logs a message to stderr.
247    fn log_stderr(message: &String){
248        eprintln!("{}", message);
249    }    
250
251    ///Build the logger. Run this function first
252/// Configures a global logger instance based on the provided environment variables and options.
253///
254/// This function takes in a tag, an application name, a log level, and a log target (output), and optional destination file.
255///
256/// # Arguments
257///
258/// * `tag`: The tag or identifier for the logger.
259/// * `application`: The application name associated with the logger.
260/// * `level`:  An enum log level to use for this logger instance.
261/// * `output`:  An enum target to output logs to (e.g. standard error, file, etc.).
262/// * `path_file`: Optional Absolute path to the file to log as String. If path is invalid or file cannot be open then is ignored
263    pub fn build_logger(tag: &str, application: &str, level: LogLevel, output:LogTarget, path_file: Option<String>){
264        let _ = LOGGER.set(Logger::new(tag, application, level, output, path_file));
265    }
266
267    ///Build the logger using environment variables and default to parameters. Run this function first
268/// Configures a global logger instance based on the provided environment variables and options.
269///
270/// This function takes in a tag, an application name, a log level, and a log target,
271/// and uses the `LOG_ENV_VAR_LOG_LEVEL` (btlogger_log_level) and `LOG_ENV_VAR_LOG_OUTPUT` (btlogger_log_output) environment variables
272/// to override or set default values for the logger configuration.
273///
274/// If no environment variables are set, it will use the provided options to configure the logger.
275///
276/// # Arguments
277///
278/// * `tag`: The tag or identifier for the logger.
279/// * `application`: The application name associated with the logger.
280/// * `level`:  An enum default log level to use for this logger instance.
281/// * `output`:  An enum default target to output logs to (e.g. standard error, file, etc.).
282/// * `path_file`: Optional Absolute path to the file to log as String. If path is invalid or file cannot be open then is ignored
283    pub fn build_logger_env(tag: &str, application: &str, level: LogLevel, output:LogTarget, path_file: Option<String>){
284        let int_level: LogLevel;
285        let int_dest: LogTarget;
286        match env::var(LOG_ENV_VAR_LOG_LEVEL){
287            Ok(levll) => int_level = LogLevel::from_str(&levll),
288            Err(_) => int_level = level,
289        }
290
291        match env::var(LOG_ENV_VAR_LOG_OUTPUT){
292            Ok(levlo) => int_dest = LogTarget::from_str(&levlo),
293            Err(_) => int_dest = output,
294        }
295
296
297        build_logger(tag, application, int_level, int_dest, path_file);
298    }
299   
300    ///Build the logger. Run this function first
301/// Builds a logger configuration based on the provided arguments.
302///
303/// This function takes in a tag and an application name as required parameters,
304/// and uses the provided vector of strings to override or set default values for
305/// the log level and target.
306///
307/// If no arguments are provided, it will build a logger with the error log level
308/// and standard error target. Otherwise, it will use the specified argument values
309/// to configure the logger.
310///
311/// # Arguments
312///
313/// * `tag`: The tag or identifier for the logger.
314/// * `application`: The application name associated with the logger.
315/// * `args`: A vector of strings containing key-value pairs for configuring the logger. The keys are "LOGLVL" or "LOGDST". They set: 
316///           LOGLVL: the log level (level)
317///           LOGDST: output target (out_target)
318///           LOGFILE: Absolute path to the file to log as String
319    pub fn build_logger_args(tag: &str, application: &str, args: &Vec<String>){
320        if args.len() < 1{
321            //build_logger(tag, application, LogLevel::ERROR, LogTarget::STD_ERROR, None );
322            #[cfg(debug_assertions)]
323                build_logger(tag, application, LogLevel::VERBOSE, LogTarget::STD_OUT, None);
324            #[cfg(not(debug_assertions))]
325                build_logger(tag, application, LogLevel::WARN, LogTarget::STD_ERROR, None);            
326        }else{
327            #[cfg(debug_assertions)]
328            let mut level = LogLevel::VERBOSE;
329            #[cfg(not(debug_assertions))]
330            let mut level = LogLevel::WARN;
331            #[cfg(debug_assertions)]
332            let mut out_target = LogTarget::STD_OUT;
333            #[cfg(not(debug_assertions))]
334            let mut out_target = LogTarget::STD_ERROR;
335            let mut dest_file = None;
336            for param in args{
337                match param.split_once("="){
338                    Some(t) => {
339                        match t.0.to_uppercase().as_str() {
340                            "LOGLVL" => level = LogLevel::from_str(t.1),
341                            "LOGDST" => out_target = LogTarget::from_str(t.1),
342                            "LOGFILE" => dest_file = Some(t.1.to_owned()),
343                            _ => (),
344                        }
345                    }
346                    None => () ,
347                }
348
349            }
350            build_logger_env(tag, application, level, out_target, dest_file);
351        }
352    }
353
354    pub fn get_logger() -> Option<Logger>{
355        /*let _logger = LOGGER.lock().unwrap();
356        _logger.clone().unwrap()*/
357        if LOGGER.get().is_some() {
358            return Some(LOGGER.get().unwrap().clone())
359        }else{
360            let l_msg = format!("{} {} {} {} {}|>|{}", "BACHUETECH", "bt_logger", Logger::get_current_time(), LogLevel::WARN, "get_logger", "BT Logger is not initialized");
361            println!("{}", l_msg);
362            None
363        }
364    }