mdlogger 0.1.2

Multi-device logging system library crate
Documentation
use serde::Serialize;
use serde_json::{json, Map, Value};
use std::str::FromStr;
use std::fmt::Display;
use time::format_description;
use crate::{constants::{CRITICAL_ENABLED_KEY, CRITICAL_TEXT_KEY, DEBUG_ENABLED_KEY, DEBUG_TEXT_KEY, ENABLED_KEY, FATAL_ENABLED_KEY, FATAL_TEXT_KEY, INFO_ENABLED_KEY, INFO_TEXT_KEY, LOG_MESSAGE_FORMAT_KEY, PATTERN_KEY, TIMESTAMP_FORMAT_KEY, WARNING_ENABLED_KEY, WARNING_TEXT_KEY}, utils::{check_message_pattern, remove_quotes}};



pub (crate) const PLAIN_TEXT_FORMAT: &str = "plain_text";
pub (crate) const JSON_FORMAT: &str = "json";
pub (crate) const JSON_PRETTY_FORMAT: &str = "json_pretty";


pub (crate) const VALID_LOG_MESSAGE_FORMATS: [&str; 3] = [
    PLAIN_TEXT_FORMAT,
    JSON_FORMAT,
    JSON_PRETTY_FORMAT
];


const CONFIGURABLE_KEYS: [&str; 13] = [
    ENABLED_KEY,
    TIMESTAMP_FORMAT_KEY,
    DEBUG_ENABLED_KEY,
    INFO_ENABLED_KEY,
    WARNING_ENABLED_KEY,
    CRITICAL_ENABLED_KEY,
    DEBUG_TEXT_KEY,
    INFO_TEXT_KEY,
    WARNING_TEXT_KEY,
    CRITICAL_TEXT_KEY,
    FATAL_TEXT_KEY,
    LOG_MESSAGE_FORMAT_KEY,
    PATTERN_KEY,
];

/// Log message type enumeration
#[derive(Serialize, Clone, Copy)]
pub enum LogMsgType {
    DebugMsgType,
    InfoMsgType,
    WarningMsgType,
    CriticalMsgType,
    FatalMsgType,
}

/// PartialEq trait implementation for log message type enumration
impl PartialEq for LogMsgType {
    fn eq(&self, other: &Self) -> bool {
        *self as i32 == *other as i32
    }
}

/// Maximum number of log mssage types
pub const LOG_MSG_TYPE_NUM: usize = LogMsgType::FatalMsgType as usize + 1usize;

// Log message format types enumeration
pub (crate) enum LogMessageFormat {
    PlainText,
    Json,
    JsonPretty
}

// Log message format error object
pub (crate) struct LogMessageFormatErr {
    message: String
}

// Display trait implementation for log message format error object
impl Display for LogMessageFormatErr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.message)        
    }
}

// FromStr trait implementation for log message format enumeration
impl FromStr for LogMessageFormat {
    type Err = LogMessageFormatErr;
    
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s_lowecase = s.to_lowercase();
        if s_lowecase == PLAIN_TEXT_FORMAT {
            Ok(LogMessageFormat::PlainText)
        } else if s_lowecase == JSON_FORMAT {
            Ok(LogMessageFormat::Json)
        } else if s_lowecase == JSON_PRETTY_FORMAT {
            Ok(LogMessageFormat::JsonPretty)
        } else {
            Err(LogMessageFormatErr {message: format!("<{}> is not a valid log message format, valid are ({})",
                                    s_lowecase, VALID_LOG_MESSAGE_FORMATS.join(", "))})
        }
    }
}

/// Log handler base object
pub struct LogHandlerBase {
    name: String,
    enabled: bool,
    timestamp_format: String,
    msg_types_enabled: [bool; LOG_MSG_TYPE_NUM],
    msg_types_text: [String; LOG_MSG_TYPE_NUM],
    message_format: String, 
    pattern: String,
    appname: String,
    appver: String
}

/// Log handler base object implementation
impl LogHandlerBase {
    /// Create a new Log handler base object
    /// see its use in ConsoleLogHandler::new function as an example
    /// * `name` Log handler name
    /// * `enabled` Log handler enabling flag
    /// * `timestamp_format` log messag time stamo format
    /// * `msg_types_enabled` log message type enabling flag
    /// * `msg_types_text` log message type text
    /// * `message_format` log message output text type (plain, json, json_pretty)
    /// * `pattern` log message format pattern
    /// * `appname` application name
    /// * `appversion` application version
    pub fn new (name: String, 
                enabled: bool, 
                timestamp_format: String,
                msg_types_enabled: [bool; LOG_MSG_TYPE_NUM], 
                msg_types_text: [String; LOG_MSG_TYPE_NUM],
                message_format: String,
                pattern: String, 
                appname: String, 
                appver: String) -> Self {
        Self {
            name,
            enabled,
            timestamp_format, 
            msg_types_enabled,
            msg_types_text,
            message_format,
            pattern,
            appname,
            appver    
        }
    }

    /// Return log handler name
    pub fn get_name(&self) ->&String {
        &self.name
    }

    /// Return log handler enabling flag status
    pub fn is_enabled(&self) ->bool {
        self.enabled.clone()
    }

    /// Return log handler log message type enabling flag status
    /// * `msg_type` log message type
    pub fn is_msg_type_enabled(&self, msg_type: &LogMsgType) -> bool {
        let idx = *msg_type as usize;
        let mut result = false;
        if idx < self.msg_types_enabled.len() {
            result = self.msg_types_enabled[idx].clone();
        } 

        result
    }

    /// Return time stamp format
    pub fn get_timestamp_format(&self) ->&String {
        &self.timestamp_format
    } 

    /// Return log message format pattern
    pub fn get_pattern(&self) ->&String {
        &self.pattern
    } 

    /// Return application name
    pub fn get_appname(&self) ->&String {
        &self.appname
    }

    /// Return application version
    pub fn get_appver(&self) ->&String {
        &self.appver
    }

    /// Return log message type text
    pub fn get_msg_types_text(&self) -> &[String; LOG_MSG_TYPE_NUM] {
        &self.msg_types_text
    }

    /// Return log message format (plain, json, json_pretty)
    pub fn get_message_format(&self) -> &String {
        &self.message_format
    }

    /// Return if key argument is a log handler base configuration key
    pub fn is_abaseconfig(&self, key: &str) -> bool {
        CONFIGURABLE_KEYS.contains(&key)
    }

    /// Set log handler configuration key with a new value
    /// * `key` configuration key name
    /// * `value` new configuration key value
    pub fn set_config(&mut self, key: &str, value: &Value) -> Result<Option<String>, String> {
        let mut error = String::new();
        if ENABLED_KEY == key {
            match value.as_bool() {
                Some(val) => {
                    self.enabled = val;
                },
                None => {
                    error = format!("{} needs a boolean value", key);
                }
            }
        } else if TIMESTAMP_FORMAT_KEY == key {
            match value.as_str() {
                Some(val) => {
                    let no_quotes_value = remove_quotes(val);
                    if  let Err(parse_error) = format_description::parse(&no_quotes_value) {
                        error = format!("{}", parse_error);
                    } else {
                        self.timestamp_format = no_quotes_value;                        
                    }    
                },
                None => {
                    error = format!("{} needs a string value", key);
                }
            }
        } else if DEBUG_ENABLED_KEY == key {
            match value.as_bool() {
                Some(val) => {
                    self.msg_types_enabled[LogMsgType::DebugMsgType as usize] = val;
                },
                None => {
                    error = format!("{} needs a boolean value", key);
                }
            }
        } else if INFO_ENABLED_KEY == key {
            match value.as_bool() {
                Some(val) => {
                    self.msg_types_enabled[LogMsgType::InfoMsgType as usize] = val;
                },
                None => {
                    error = format!("{} needs a boolean value", key);
                }
            }
        } else if WARNING_ENABLED_KEY == key {
            match value.as_bool() {
                Some(val) => {
                    self.msg_types_enabled[LogMsgType::WarningMsgType as usize] = val;
                },
                None => {
                    error = format!("{} needs a boolean value", key);
                }
            }
        } else if CRITICAL_ENABLED_KEY == key {
            match value.as_bool() {
                Some(val) => {
                    self.msg_types_enabled[LogMsgType::CriticalMsgType as usize] = val;
                },
                None => {
                    error = format!("{} needs a boolean value", key);
                }
            }
        } else if DEBUG_TEXT_KEY == key {
            match value.as_str() {
                Some(val) => {
                    self.msg_types_text[LogMsgType::DebugMsgType as usize] = String::from(val);
                },
                None => {
                    error = format!("{} needs a string value", key);
                }
            }
        } else if INFO_TEXT_KEY == key{
            match value.as_str() {
                Some(val) => {
                    self.msg_types_text[LogMsgType::InfoMsgType as usize] = String::from(val);
                },
                None => {
                    error = format!("{} needs a string value", key);
                }
            }
        } else if WARNING_TEXT_KEY == key{
            match value.as_str() {
                Some(val) => {
                    self.msg_types_text[LogMsgType::WarningMsgType as usize] = String::from(val);
                },
                None => {
                    error = format!("{} needs a string value", key);
                }
            }        
        } else if CRITICAL_TEXT_KEY == key{
            match value.as_str() {
                Some(val) => {
                    self.msg_types_text[LogMsgType::CriticalMsgType as usize] = String::from(val);
                },
                None => {
                    error = format!("{} needs a string value", key);
                }
            }
        } else if FATAL_TEXT_KEY == key {
            match value.as_str() {
                Some(val) => {
                    self.msg_types_text[LogMsgType::FatalMsgType as usize] = String::from(val);
                },
                None => {
                    error = format!("{} needs a string value", key);
                }
            }
        } else if LOG_MESSAGE_FORMAT_KEY == key {
            match value.as_str() {
                Some(val) => {
                    match val.parse::<LogMessageFormat>() {
                        Ok(_) => {
                            self.message_format = String::from(val);
                        },
                        Err(parse_error) => {
                            error = format!("{}", parse_error);
                        }
                    }
                },
                None => {
                    error = format!("{} needs a string value", key);
                }
            }
        } else if PATTERN_KEY == key {
            match value.as_str() {
                Some(val) => {
                    if let Err(check_error) = check_message_pattern(&val.to_string()) {
                        error = check_error;
                    } else {
                        self.pattern = String::from(val);
                    }
                },
                None => {
                    error = format!("{} needs a string value", key);
                }
            }
        
        } else {
            error = format!("{} is not a valid configuration key", key);
        }

        if error.len() > 0 {
            Err(error)
        } else {
            Ok(None)
        }
    } 

    /// Return log handler base object configurationas a Map json value
    pub fn get_config(&self) -> Map<String, Value> {
        let mut config = Map::new();
        config.insert(String::from("name"), json!(self.name));
        config.insert(String::from(ENABLED_KEY), json!(self.enabled));
        config.insert(String::from(TIMESTAMP_FORMAT_KEY), json!(format!("\"{}\"", self.timestamp_format)));
        config.insert(String::from(DEBUG_ENABLED_KEY), json!(self.msg_types_enabled[LogMsgType::DebugMsgType as usize]));
        config.insert(String::from(INFO_ENABLED_KEY), json!(self.msg_types_enabled[LogMsgType::InfoMsgType as usize]));
        config.insert(String::from(WARNING_ENABLED_KEY), json!(self.msg_types_enabled[LogMsgType::WarningMsgType as usize]));
        config.insert(String::from(CRITICAL_ENABLED_KEY), json!(self.msg_types_enabled[LogMsgType::CriticalMsgType as usize]));
        config.insert(String::from(FATAL_ENABLED_KEY), json!(self.msg_types_enabled[LogMsgType::FatalMsgType as usize]));

        config.insert(String::from(DEBUG_TEXT_KEY), json!(self.msg_types_text[LogMsgType::DebugMsgType as usize]));
        config.insert(String::from(INFO_TEXT_KEY), json!(self.msg_types_text[LogMsgType::InfoMsgType as usize]));
        config.insert(String::from(WARNING_TEXT_KEY), json!(self.msg_types_text[LogMsgType::WarningMsgType as usize]));
        config.insert(String::from(CRITICAL_TEXT_KEY), json!(self.msg_types_text[LogMsgType::CriticalMsgType as usize]));
        config.insert(String::from(FATAL_TEXT_KEY), json!(self.msg_types_text[LogMsgType::FatalMsgType as usize]));
        
        config.insert(String::from(PATTERN_KEY), json!(self.pattern));
        config.insert(String::from(LOG_MESSAGE_FORMAT_KEY), json!(self.get_message_format()));
        config.insert(String::from("appname"), json!(self.appname));
        config.insert(String::from("appver"), json!(self.appver));

        config
    }
}