use std::fs::{File, OpenOptions};
use std::io::Write;
use std::str::FromStr;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex};
use humphrey::http::date::DateTime;
use humphrey::monitor::event::{Event, EventType, ToEventMask};
use crate::config::Config;
use crate::AppState;
pub const INTERNAL_MASK_ERROR: u32 = EventType::ThreadPoolPanic as u32;
pub const INTERNAL_MASK_WARN: u32 = INTERNAL_MASK_ERROR
| EventType::RequestServedError as u32
| EventType::RequestTimeout as u32
| EventType::StreamDisconnectedWhileWaiting as u32
| EventType::ThreadPoolOverload as u32
| EventType::ThreadRestarted as u32;
pub const INTERNAL_MASK_INFO: u32 = INTERNAL_MASK_WARN | EventType::HTTPSRedirect as u32;
pub const INTERNAL_MASK_DEBUG: u32 = INTERNAL_MASK_INFO
| EventType::KeepAliveRespected as u32
| EventType::ThreadPoolProcessStarted as u32
| EventType::ConnectionSuccess as u32
| EventType::ConnectionClosed as u32;
pub struct Logger {
level: LogLevel,
console: bool,
file: Option<Mutex<File>>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
}
impl Default for Logger {
fn default() -> Self {
Self {
level: LogLevel::Warn,
console: true,
file: None,
}
}
}
impl From<&Config> for Logger {
fn from(config: &Config) -> Self {
let file = config.logging.file.as_ref().map(|path| {
Mutex::new(
OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(path)
.unwrap(),
)
});
Self {
level: config.logging.level.clone(),
console: config.logging.console,
file,
}
}
}
impl Logger {
pub fn error(&self, message: impl AsRef<str>) {
let string = format!("{} [ERROR] {}", Logger::time_format(), message.as_ref());
self.log_to_console(&string);
self.log_to_file(&string);
}
pub fn warn(&self, message: impl AsRef<str>) {
if self.level >= LogLevel::Warn {
let string = format!("{} [WARN] {}", Logger::time_format(), message.as_ref());
self.log_to_console(&string);
self.log_to_file(&string);
}
}
pub fn info(&self, message: impl AsRef<str>) {
if self.level >= LogLevel::Info {
let string = format!("{} [INFO] {}", Logger::time_format(), message.as_ref());
self.log_to_console(&string);
self.log_to_file(&string);
}
}
pub fn debug(&self, message: impl AsRef<str>) {
if self.level == LogLevel::Debug {
let string = format!("{} [DEBUG] {}", Logger::time_format(), message.as_ref());
self.log_to_console(&string);
self.log_to_file(&string);
}
}
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
)
}
fn log_to_console(&self, string: &str) {
if self.console {
println!("{}", string);
}
}
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();
}
}
}
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"),
}
}
}
impl ToEventMask for LogLevel {
fn to_event_mask(&self) -> u32 {
match self {
Self::Error => INTERNAL_MASK_ERROR,
Self::Warn => INTERNAL_MASK_WARN,
Self::Info => INTERNAL_MASK_INFO,
Self::Debug => INTERNAL_MASK_DEBUG,
}
}
}
pub fn monitor_thread(rx: Receiver<Event>, state: Arc<AppState>) {
for e in rx {
if e.kind == EventType::RequestServedError
&& !e
.info
.as_ref()
.map(|i| i.starts_with("400"))
.unwrap_or(false)
{
continue;
}
let message = if let Some(info) = e.info {
if e.kind == EventType::RequestServedError || e.kind == EventType::RequestTimeout {
format!(
"{}{}",
e.peer
.map(|p| p.ip().to_string() + ": ")
.unwrap_or_else(|| "".into()),
info
)
} else {
format!(
"{}{}: {}",
e.peer
.map(|p| p.ip().to_string() + ": ")
.unwrap_or_else(|| "".into()),
e.kind.to_string(),
info
)
}
} else {
format!(
"{}{}",
e.peer
.map(|p| p.ip().to_string() + ": ")
.unwrap_or_else(|| "".into()),
e.kind.to_string()
)
};
if e.kind.to_event_mask() & INTERNAL_MASK_ERROR != 0 {
state.logger.error(message);
} else if e.kind.to_event_mask() & INTERNAL_MASK_WARN != 0 {
state.logger.warn(message);
} else if e.kind.to_event_mask() & INTERNAL_MASK_INFO != 0 {
state.logger.info(message);
} else {
state.logger.debug(message);
}
}
}