pub use log::LevelFilter;
use log::debug;
use serde::{Deserialize, Serialize};
use std::{env, io, path::PathBuf, str::FromStr};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub enum StdoutLog {
Off,
Plain,
Colored,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LoggerConfig {
pub stdout: StdoutLog,
pub level_filter: LevelFilter,
pub log_file: Option<PathBuf>,
pub allow_env_override: bool,
pub log_gfx_device_level: Option<LevelFilter>,
}
impl Default for LoggerConfig {
fn default() -> Self {
Self {
stdout: StdoutLog::Colored,
level_filter: LevelFilter::Info,
log_file: None,
allow_env_override: true,
log_gfx_device_level: Some(LevelFilter::Warn),
}
}
}
#[allow(missing_debug_implementations)]
pub struct Logger {
dispatch: fern::Dispatch,
}
impl Logger {
fn new() -> Self {
let dispatch = fern::Dispatch::new().format(|out, message, record| {
out.finish(format_args!(
"[{level}][{target}] {message}",
level = record.level(),
target = record.target(),
message = message,
))
});
Self { dispatch }
}
pub fn from_config(mut config: LoggerConfig) -> Self {
if config.allow_env_override {
env_var_override(&mut config);
}
let mut logger = Self::new();
logger.dispatch = logger.dispatch.level(config.level_filter);
match config.stdout {
StdoutLog::Plain => logger.dispatch = logger.dispatch.chain(io::stdout()),
StdoutLog::Colored => {
logger.dispatch = logger
.dispatch
.chain(colored_stdout(fern::colors::ColoredLevelConfig::new()))
}
StdoutLog::Off => {}
}
if let Some(log_gfx_device_level) = config.log_gfx_device_level {
logger.dispatch = logger
.dispatch
.level_for("gfx_device_gl", log_gfx_device_level);
}
if let Some(path) = config.log_file {
if let Ok(log_file) = fern::log_file(path) {
logger.dispatch = logger.dispatch.chain(log_file)
} else {
eprintln!("Unable to access the log file, as such it will not be used")
}
}
logger
}
pub fn level_for<T: Into<std::borrow::Cow<'static, str>>>(
mut self,
module: T,
level: LevelFilter,
) -> Self {
self.dispatch = self.dispatch.level_for(module, level);
self
}
pub fn start(self) {
self.dispatch.apply().unwrap_or_else(|_| {
debug!("Global logger already set, default sardonyx logger will not be used")
});
}
}
pub fn start_logger(config: LoggerConfig) {
Logger::from_config(config).start();
}
fn env_var_override(config: &mut LoggerConfig) {
if let Ok(var) = env::var("sardonyx_LOG_STDOUT") {
match var.to_lowercase().as_ref() {
"off" | "no" | "0" => config.stdout = StdoutLog::Off,
"plain" | "yes" | "1" => config.stdout = StdoutLog::Plain,
"colored" | "2" => config.stdout = StdoutLog::Colored,
_ => {}
}
}
if let Ok(var) = env::var("sardonyx_LOG_LEVEL_FILTER") {
if let Ok(lf) = LevelFilter::from_str(&var) {
config.level_filter = lf;
}
}
if let Ok(path) = env::var("sardonyx_LOG_FILE_PATH") {
config.log_file = Some(PathBuf::from(path));
}
}
fn colored_stdout(color_config: fern::colors::ColoredLevelConfig) -> fern::Dispatch {
fern::Dispatch::new()
.chain(io::stdout())
.format(move |out, message, record| {
let color = color_config.get_color(&record.level());
out.finish(format_args!(
"{color}{message}{color_reset}",
color = format!("\x1B[{}m", color.to_fg_str()),
message = message,
color_reset = "\x1B[0m",
))
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
#[test]
fn check_stdout_override() {
let mut config = LoggerConfig::default();
assert_eq!(config.stdout, StdoutLog::Colored);
env::set_var("sardonyx_LOG_STDOUT", "pLaIn");
env_var_override(&mut config);
env::remove_var("sardonyx_LOG_STDOUT");
assert_eq!(config.stdout, StdoutLog::Plain);
}
}