use crate::config::{BooleanKey, StringKey};
use crate::metadata::now_unix_timestamp;
use env_logger::Target::{Stderr, Stdout};
use env_logger::DEFAULT_FILTER_ENV;
use log::LevelFilter::{Debug, Info, Trace};
use log::{Level, LevelFilter};
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::env;
use std::fmt::Display;
use std::str::FromStr;
pub const DRIVER_PATH: &str = "Driver path: ";
pub const BROWSER_PATH: &str = "Browser path: ";
#[derive(Default, PartialEq)]
enum OutputType {
#[default]
Logger,
Json,
Shell,
Mixed,
}
#[derive(Default)]
pub struct Logger {
debug: bool,
trace: bool,
output: OutputType,
json: RefCell<JsonOutput>,
minimal_json: RefCell<MinimalJson>,
}
#[derive(Default, Serialize, Deserialize)]
pub struct Logs {
pub level: String,
pub timestamp: u64,
pub message: String,
}
#[derive(Default, Serialize, Deserialize)]
pub struct Result {
pub code: i32,
pub message: String,
pub driver_path: String,
pub browser_path: String,
}
#[derive(Default, Serialize, Deserialize)]
pub struct JsonOutput {
pub logs: Vec<Logs>,
pub result: Result,
}
#[derive(Default, Serialize, Deserialize)]
pub struct MinimalJson {
pub driver_path: String,
pub browser_path: String,
pub error: String,
}
impl Logger {
pub fn new() -> Self {
let debug = BooleanKey("debug", false).get_value();
let trace = BooleanKey("trace", false).get_value();
let log_level = StringKey(vec!["log-level"], "").get_value();
Logger::create("", debug, trace, &log_level)
}
pub fn create(output: &str, debug: bool, trace: bool, log_level: &str) -> Self {
let output_type;
if output.eq_ignore_ascii_case("json") {
output_type = OutputType::Json;
} else if output.eq_ignore_ascii_case("shell") {
output_type = OutputType::Shell;
} else if output.eq_ignore_ascii_case("mixed") {
output_type = OutputType::Mixed;
} else {
output_type = OutputType::Logger;
}
match output_type {
OutputType::Logger | OutputType::Mixed => {
if env::var(DEFAULT_FILTER_ENV).unwrap_or_default().is_empty() {
let mut filter = if !log_level.is_empty() {
LevelFilter::from_str(log_level).unwrap_or(Info)
} else {
let mut filter = match debug {
true => Debug,
false => Info,
};
if trace {
filter = Trace;
}
filter
};
if trace {
filter = Trace
}
let target = if output_type == OutputType::Logger {
Stdout
} else {
Stderr
};
env_logger::Builder::new()
.filter_module(env!("CARGO_CRATE_NAME"), filter)
.target(target)
.format_target(false)
.format_timestamp_millis()
.try_init()
.unwrap_or_default();
} else {
env_logger::try_init().unwrap_or_default();
}
}
_ => {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
.try_init()
.unwrap_or_default();
}
}
Logger {
debug,
trace,
output: output_type,
json: RefCell::new(JsonOutput {
logs: Vec::new(),
result: Result {
code: 0,
message: "".to_string(),
driver_path: "".to_string(),
browser_path: "".to_string(),
},
}),
minimal_json: RefCell::new(Default::default()),
}
}
pub fn error<T: Display>(&self, message: T) {
self.logger(message.to_string(), Level::Error);
}
pub fn warn<T: Display>(&self, message: T) {
self.logger(message.to_string(), Level::Warn);
}
pub fn info<T: Display>(&self, message: T) {
self.logger(message.to_string(), Level::Info);
}
pub fn debug<T: Display>(&self, message: T) {
self.logger(message.to_string(), Level::Debug);
}
pub fn debug_or_warn<T: Display>(&self, message: T, is_debug: bool) {
let level = if is_debug { Level::Debug } else { Level::Warn };
self.logger(message.to_string(), level);
}
pub fn trace<T: Display>(&self, message: T) {
self.logger(message.to_string(), Level::Trace);
}
fn logger(&self, message: String, level: Level) {
match self.output {
OutputType::Json => {
let trace = level <= Level::Trace && self.trace;
let debug = level <= Level::Debug && self.debug;
let other = level <= Level::Info;
if trace || debug || other {
self.json
.borrow_mut()
.logs
.push(self.create_json_log(message.to_string(), level));
}
if level == Level::Info || level <= Level::Error {
if message.starts_with(DRIVER_PATH) {
self.json.borrow_mut().result.driver_path =
self.clean_driver_path(&message);
} else if message.starts_with(BROWSER_PATH) {
self.json.borrow_mut().result.browser_path =
self.clean_browser_path(&message);
} else {
self.json.borrow_mut().result.message = message;
}
}
}
OutputType::Shell => {
if level == Level::Info {
println!("{}", message);
} else if level == Level::Error {
eprintln!("{}", message);
}
}
_ => {
if self.output == OutputType::Mixed && level == Level::Info || level <= Level::Error
{
if message.starts_with(DRIVER_PATH) {
self.minimal_json.borrow_mut().driver_path =
self.clean_driver_path(&message);
} else if message.starts_with(BROWSER_PATH) {
self.minimal_json.borrow_mut().browser_path =
self.clean_browser_path(&message);
} else {
self.minimal_json.borrow_mut().error = message.clone();
}
}
log::log!(level, "{}", message);
}
}
}
fn clean_driver_path(&self, message: &str) -> String {
message.replace(DRIVER_PATH, "")
}
fn clean_browser_path(&self, message: &str) -> String {
message.replace(BROWSER_PATH, "")
}
fn create_json_log(&self, message: String, level: Level) -> Logs {
Logs {
level: level.to_string().to_uppercase(),
timestamp: now_unix_timestamp(),
message,
}
}
fn get_json_blog<T>(&self, json_output: &T) -> String
where
T: Serialize,
{
serde_json::to_string_pretty(json_output).unwrap()
}
pub fn set_code(&self, code: i32) {
self.json.borrow_mut().result.code = code;
}
pub fn flush(&self) {
if self.output == OutputType::Json {
print!("{}", self.get_json_blog(&self.json));
} else if self.output == OutputType::Mixed {
print!("{}", self.get_json_blog(&self.minimal_json));
}
}
pub fn is_debug_enabled(&self) -> bool {
self.debug || self.trace
}
}