1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::str::FromStr;
use std::sync::Mutex;

use crate::config::Config;
use humphrey::http::date::DateTime;

/// Encapsulates logging methods and configuration.
pub struct Logger {
    level: LogLevel,
    console: bool,
    file: Option<Mutex<File>>,
}

impl From<&Config> for Logger {
    fn from(config: &Config) -> Self {
        let file = if let Some(path) = &config.log_file {
            // If the log file can be opened, wrap it in a `Mutex`
            Some(Mutex::new(
                OpenOptions::new()
                    .write(true)
                    .truncate(true)
                    .create(true)
                    .open(path)
                    .unwrap(),
            ))
        } else {
            // Otherwise don't log to a file
            None
        };

        Self {
            level: config.log_level.clone(),
            console: config.log_console,
            file,
        }
    }
}

impl Default for Logger {
    fn default() -> Self {
        Self {
            level: LogLevel::Warn,
            console: true,
            file: None,
        }
    }
}

impl Logger {
    /// Logs an error message.
    pub fn error(&self, message: &str) {
        let string = format!("{} [ERROR] {}", Logger::time_format(), message);
        self.log_to_console(&string);
        self.log_to_file(&string);
    }

    /// Logs a warning, provided that the log level allows this.
    pub fn warn(&self, message: &str) {
        if self.level >= LogLevel::Warn {
            let string = format!("{} [WARN]  {}", Logger::time_format(), message);
            self.log_to_console(&string);
            self.log_to_file(&string);
        }
    }

    /// Logs information, provided that the log level allows this.
    pub fn info(&self, message: &str) {
        if self.level >= LogLevel::Info {
            let string = format!("{} [INFO]  {}", Logger::time_format(), message);
            self.log_to_console(&string);
            self.log_to_file(&string);
        }
    }

    /// Logs debug information, provided that the log level allows this.
    pub fn debug(&self, message: &str) {
        if self.level == LogLevel::Debug {
            let string = format!("{} [DEBUG] {}", Logger::time_format(), message);
            self.log_to_console(&string);
            self.log_to_file(&string);
        }
    }

    /// Formats the current time into the format `YYYY-MM-DD HH:MM:SS`
    fn time_format() -> String {
        let time = DateTime::now();
        format!(
            "{}-{:02}-{:02} {:02}:{:02}:{:02}",
            time.year,
            time.month + 1,
            time.day,
            time.hour,
            time.minute,
            time.second
        )
    }

    /// Logs the string to the console, if the logging configuration allows it
    fn log_to_console(&self, string: &str) {
        if self.console {
            println!("{}", string);
        }
    }

    /// Logs the string to the log file, if the logging configuration allows it
    fn log_to_file(&self, string: &str) {
        if let Some(file_mutex) = &self.file {
            let mut file = file_mutex.lock().unwrap();
            file.write_all(string.as_bytes()).unwrap();
            file.write_all(b"\n").unwrap();
        }
    }
}

/// Represents a log level.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
    /// Only errors will be logged
    Error,
    /// Errors and warnings will be logged
    Warn,
    /// Errors, warnings and general information will be logged
    Info,
    /// Everything, including debug information, will be logged
    Debug,
}

impl FromStr for LogLevel {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_ascii_lowercase().as_str() {
            "error" => Ok(Self::Error),
            "warn" => Ok(Self::Warn),
            "info" => Ok(Self::Info),
            "debug" => Ok(Self::Debug),
            _ => Err("Log level was invalid"),
        }
    }
}