#![allow(dead_code)]
#![allow(clippy::needless_doctest_main)]
#[cfg(test)]
mod tests;
#[macro_use]
pub mod logging_macros;
pub mod color;
#[cfg(feature = "log_files")]
pub mod existing_log_handler;
#[cfg(feature = "time")]
pub mod time;
#[cfg(feature = "config")]
mod config_file;
#[cfg(feature = "env")]
pub mod environment;
mod level; mod log_message;
#[cfg(feature = "config")]
pub use config_file::ConfigFileLoadError;
pub use level::Level;
#[cfg(feature = "log_files")]
use crate::existing_log_handler::ExistingLogHandler;
use std::error::Error;
use std::fmt::{Display, Formatter};
#[cfg(feature = "log_files")]
use std::fs::File;
#[cfg(feature = "log_files")]
use std::io::{prelude::*, BufWriter};
#[cfg(feature = "log_files")]
use std::path::PathBuf;
#[cfg(feature = "config")]
use config_file::ConfigFile;
use color::TermColor;
use lazy_static::lazy_static;
use log_message::LogMessage;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
lazy_static!(
pub static ref LOGGER: Logger = {
let mut logger = Logger::new();
#[cfg(feature = "env")]
environment::configure(&mut logger);
logger
};
);
#[derive(Debug)]
pub struct Logger {
level: Mutex<Level>,
color: AtomicBool,
#[cfg(feature = "log_files")]
log_file_color: AtomicBool,
#[cfg(feature = "time")]
show_time: AtomicBool,
#[cfg(feature = "log_files")]
log_path: Mutex<Option<PathBuf>>,
#[cfg(feature = "log_files")]
log_writer: Mutex<Option<BufWriter<File>>>,
#[cfg(feature = "log_files")]
existing_log_handler: Mutex<ExistingLogHandler>,
#[cfg(feature = "time")]
timestamp_format: Mutex<Option<String>>
}
impl Logger {
pub fn new() -> Logger {
Logger {
level: Mutex::new(Level::Debug),
color: AtomicBool::new(true),
#[cfg(feature = "log_files")]
log_file_color: AtomicBool::new(false),
#[cfg(feature = "time")]
show_time: AtomicBool::new(true),
#[cfg(feature = "log_files")]
log_path: Mutex::new(None),
#[cfg(feature = "log_files")]
log_writer: Mutex::new(None),
#[cfg(feature = "log_files")]
existing_log_handler: Mutex::new(ExistingLogHandler::Overwrite),
#[cfg(feature = "time")]
timestamp_format: Mutex::new(None)
}
}
#[cfg(feature = "env")]
pub fn load_env_vars(&self) {
environment::configure(self);
}
pub fn set_level(&self, level: Level) {
*self.level.lock().unwrap() = level;
}
pub fn get_level(&self) -> Level {
*self.level.lock().unwrap()
}
pub fn set_color(&self, color: bool) {
self.color.store(color, Ordering::Relaxed);
}
pub fn get_color(&self) -> bool {
self.color.load(Ordering::Relaxed)
}
#[cfg(feature = "log_files")]
pub fn set_log_file_color(&self, color: bool) {
self.log_file_color.store(color, Ordering::Relaxed);
}
#[cfg(feature = "log_files")]
pub fn get_log_file_color(&self) -> bool {
self.log_file_color.load(Ordering::Relaxed)
}
#[cfg(feature = "log_files")]
pub fn set_existing_log_handler(&self, handler: ExistingLogHandler) {
*self.existing_log_handler.lock().unwrap() = handler;
}
#[cfg(feature = "log_files")]
pub fn get_existing_log_handler(&self) -> ExistingLogHandler {
*self.existing_log_handler.lock().unwrap()
}
#[cfg(feature = "time")]
pub fn set_should_show_time(&self, show_time: bool) {
self.show_time.store(show_time, Ordering::Relaxed);
}
#[cfg(feature = "time")]
pub fn should_show_time(&self) -> bool {
self.show_time.load(Ordering::Relaxed)
}
#[cfg(feature = "log_files")]
#[must_use]
pub fn set_log_path(&self, path: &str) -> Result<(), SetLogPathError> {
let path_buf = PathBuf::from(path);
self.remove_log_writer();
if !path_buf.exists() && File::create(path).is_err() {
return Err(SetLogPathError::CouldNotCreateLogFile);
}
if !path_buf.is_file() {
return Err(SetLogPathError::PathIsNotAFile);
}
*self.log_path.lock().unwrap() = Some(path_buf);
Ok(())
}
#[cfg(feature = "log_files")]
pub fn remove_log_path(&self) {
*self.log_path.lock().unwrap() = None;
self.remove_log_writer();
}
#[cfg(feature = "log_files")]
pub fn get_log_path(&self) -> Option<PathBuf> {
(*self.log_path.lock().unwrap()).as_ref().cloned()
}
#[cfg(feature = "time")]
pub fn get_timestamp_format(&self) -> Option<String> {
let res = match self.timestamp_format.lock() {
Ok(ref mut inner) => inner.clone(),
Err(_) => return None
};
res
}
#[cfg(feature = "time")]
pub fn set_timestamp_format(&self, value: Option<String>) {
match self.timestamp_format.lock() {
Ok(mut inner) => *inner = value,
Err(_) => error!("Could not set timestamp format.")
}
}
#[cfg(feature = "log_files")]
fn set_log_writer(&self, buf_writer: BufWriter<File>) {
*self.log_writer.lock().unwrap() = Some(buf_writer);
}
#[cfg(feature = "log_files")]
fn remove_log_writer(&self) {
*self.log_writer.lock().unwrap() = None;
}
#[cfg(feature = "log_files")]
fn has_log_writer(&self) -> bool {
if let Ok(lw) = self.log_writer.lock() {
return lw.is_some();
}
false
}
#[cfg(feature = "log_files")]
fn set_log_writer_if_not_set(&self) {
if !self.has_log_writer() {
if let Some(path) = self.get_log_path() {
let file = match self.get_existing_log_handler().open_file(&path) {
Ok(f) => f,
Err(e) => {
error!("Could not open log file: {:?}", e);
self.remove_log_path();
return;
}
};
let buf_writer = BufWriter::new(file);
self.set_log_writer(buf_writer);
}
}
}
#[cfg(feature = "log_files")]
fn log_message_to_file(&self, log_message: &mut LogMessage) {
self.set_log_writer_if_not_set();
if let Ok(ref mut log_writer) = self.log_writer.lock() {
if log_writer.is_some() {
let formatted_message = log_message.formatted(self.get_log_file_color());
if let Err(e) = log_writer
.as_mut()
.unwrap()
.write(formatted_message.as_bytes())
{
self.remove_log_writer();
self.remove_log_path();
self.error(&format!("Log file could not be written to: {e:?}"));
}
}
}
}
#[cfg(not(feature = "log_files"))]
fn log_message_to_file(&self, _msg: &mut LogMessage) {
}
fn log_message(&self, level: Level, message: &str) {
let mut log_message = LogMessage::new(&self.prefix(), message, level);
print!("{}", log_message.formatted(self.get_color()));
#[cfg(feature = "log_files")]
self.log_message_to_file(&mut log_message);
}
pub fn debug(&self, message: &str) {
if self.get_level() <= Level::Debug {
self.log_message(Level::Debug, message);
}
}
pub fn info(&self, message: &str) {
if self.get_level() <= Level::Info {
self.log_message(Level::Info, message);
}
}
pub fn warn(&self, message: &str) {
if self.get_level() <= Level::Warn {
self.log_message(Level::Warn, message);
}
}
pub fn error(&self, message: &str) {
if self.get_level() <= Level::Error {
self.log_message(Level::Error, message);
}
}
#[cfg(feature = "time")]
fn prefix(&self) -> String {
if self.should_show_time() {
time::current_time_box(self.get_timestamp_format())
} else {
"".to_string()
}
}
#[cfg(not(feature = "time"))]
fn prefix(&self) -> String {
"".to_string()
}
pub fn flush(&self) -> std::io::Result<()> {
#[cfg(feature = "log_files")]
if let Ok(ref mut log_writer) = self.log_writer.lock() {
if log_writer.is_some() {
log_writer.as_mut().unwrap().flush()
} else {
Ok(())
}
} else {
Ok(())
}
#[cfg(not(feature = "log_files"))]
Ok(())
}
#[cfg(feature = "config")]
pub fn load_config_file(&self, path: &str) -> Result<(), ConfigFileLoadError> {
let config_file = ConfigFile::load(path)?;
self.set_level(config_file.level);
self.set_color(config_file.color);
#[cfg(feature = "time")]
self.set_should_show_time(config_file.time_stamp);
#[cfg(feature = "log_files")]
self.set_log_file_color(config_file.file_color);
#[cfg(feature = "log_files")]
self.set_existing_log_handler(config_file.existing_log_handler);
#[cfg(feature = "log_files")]
if let Some(ref log_path) = config_file.log_file_path {
self.set_log_path(log_path);
} else {
self.remove_log_path();
}
debug!("Config file loaded: {:?}", config_file);
Ok(())
}
}
impl Default for Logger {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SetLogPathError {
CouldNotCreateLogFile,
PathIsNotAFile
}
impl Display for SetLogPathError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SetLogPathError::CouldNotCreateLogFile => write!(f, "Could not create log file"),
SetLogPathError::PathIsNotAFile => write!(f, "Log file path is not a file")
}
}
}
impl Error for SetLogPathError {}