use ansi_term::{Color, Style};
use anyhow::Result;
use flexi_logger::writers::LogWriter;
use flexi_logger::{Age, Duplicate, FlexiLoggerError, LogSpecification};
use flexi_logger::{Cleanup, Criterion, FileSpec, Naming};
use flexi_logger::{
DeferredNow, FormatFunction, LevelFilter, LogSpecBuilder, Logger, LoggerHandle, Record,
WriteMode,
};
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use std::thread;
const TS_DASHES_BLANK_COLONS_DOT_BLANK: &str = "%m-%d %H:%M:%S%.3f";
const HOUR_MINUTER_SECONDE: &str = "%H:%M:%S%.3f";
#[allow(dead_code)]
fn with_thread(
w: &mut dyn std::io::Write,
now: &mut DeferredNow,
record: &Record,
) -> Result<(), std::io::Error> {
let level = record.level();
write!(
w,
"[{}][{}][{:5}][{}:{}] {}",
now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK),
thread::current().name().unwrap_or("<unnamed>"),
level.to_string(),
record.target(),
record.line().unwrap_or(0),
&record.args()
)
}
#[allow(dead_code)]
pub fn colored_with_thread(
w: &mut dyn std::io::Write,
now: &mut DeferredNow,
record: &Record,
) -> Result<(), std::io::Error> {
let level = record.level();
write!(
w,
"{}",
format_args!(
"[{}][{}][{:5}][{}:{}] {}",
now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK),
thread::current().name().unwrap_or("<unnamed>"),
style(level).paint(format_args!("{:6}", level.to_string()).to_string()),
record.target(),
record.line().unwrap_or(0),
&record.args()
)
)
}
#[allow(dead_code)]
pub fn colored_with_thread_target(
w: &mut dyn std::io::Write,
now: &mut DeferredNow,
record: &Record,
) -> Result<(), std::io::Error> {
let level = record.level();
write!(
w,
"{}",
format_args!(
"[{}][{}][{:5}][{}:{}] {}",
now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK),
thread::current().name().unwrap_or("<unnamed>"),
style(level).paint(format_args!("{:6}", level.to_string()).to_string()),
record.target(),
record.line().unwrap_or(0),
&record.args()
)
)
}
#[allow(dead_code)]
pub fn simple_colored_with_thread_target(
w: &mut dyn std::io::Write,
now: &mut DeferredNow,
record: &Record,
) -> Result<(), std::io::Error> {
let level = record.level();
write!(
w,
"{}",
format_args!(
"[{}][{}][{:5}][{}:{}] {}",
now.format(HOUR_MINUTER_SECONDE),
thread::current().name().unwrap_or("<unnamed>"),
style(level).paint(format_args!("{:6}", level.to_string()).to_string()),
record.target(),
record.line().unwrap_or(0),
&record.args()
)
)
}
pub struct LoggerBuilder {
display_target: bool,
log_spec_builder: LogSpecBuilder,
}
impl LoggerBuilder {
pub fn default(level: LevelFilter) -> Self {
let mut log_spec_builder = LogSpecBuilder::new();
log_spec_builder.default(level);
Self {
log_spec_builder,
display_target: false,
}
}
pub fn module<M: AsRef<str>>(mut self, module_name: M, lf: LevelFilter) -> Self {
self.log_spec_builder.module(module_name, lf);
self
}
pub fn build_default(self) -> LoggerBuilder2 {
LoggerBuilder2 {
logger: Logger::with(self.log_spec_builder.build())
.format(if self.display_target {
colored_with_thread_target
} else {
colored_with_thread
})
.write_mode(WriteMode::Direct),
}
}
pub fn log_to_stdout(self) {
Logger::with(self.log_spec_builder.build())
.format(if self.display_target {
colored_with_thread_target
} else {
colored_with_thread
})
.write_mode(WriteMode::Direct)
.log_to_stdout()
.start()
.unwrap();
}
pub fn build_with(self, format: FormatFunction, write_mode: WriteMode) -> LoggerBuilder2 {
LoggerBuilder2 {
logger: Logger::with(self.log_spec_builder.build())
.format(format)
.write_mode(write_mode),
}
}
}
pub struct LoggerBuilder2 {
logger: Logger,
}
pub struct LoggerBuilder3 {
logger: Logger,
}
impl LoggerBuilder3 {
#[must_use]
pub fn start(self) -> LoggerHandle {
self.logger.start().unwrap()
}
pub fn _start(self) -> Result<LoggerHandle> {
Ok(self.logger.start()?)
}
#[must_use]
pub fn start_with_specfile(self, p: impl AsRef<Path>) -> LoggerHandle {
self.logger.start_with_specfile(p).unwrap()
}
#[must_use]
pub fn start_with_specfile_default(self, app: &str) -> LoggerHandle {
let path = PathBuf::from_str("/var/local/etc/")
.unwrap()
.join(app)
.join("logspecification.toml");
self.logger.start_with_specfile(path).unwrap()
}
}
impl LoggerBuilder2 {
pub fn log_to_stdout(self) -> LoggerBuilder3 {
LoggerBuilder3 {
logger: self.logger.log_to_stdout(),
}
}
pub fn log_to_file_default(self, app: &str) -> LoggerBuilder3 {
let fs_path = PathBuf::from_str("/var/local/log").unwrap().join(app);
let fs = FileSpec::default()
.directory(fs_path)
.basename(app)
.suffix("log");
self.log_to_file(
fs,
Criterion::AgeOrSize(Age::Day, 10_000_000),
Naming::Numbers,
Cleanup::KeepLogFiles(10),
true,
)
}
pub fn log_to_writer(self, w: Box<dyn LogWriter>) -> LoggerBuilder3 {
LoggerBuilder3 {
logger: self.logger.log_to_writer(w),
}
}
pub fn log_to_file(
self,
fs: FileSpec,
criterion: Criterion,
naming: Naming,
cleanup: Cleanup,
append: bool,
) -> LoggerBuilder3 {
LoggerBuilder3 {
logger: self
.logger
.log_to_file(fs)
.o_append(append)
.rotate(criterion, naming, cleanup),
}
}
}
#[allow(dead_code)]
pub struct LoggerFeatureBuilder {
_app: String,
_debug_level: DebugLevel,
_prod_level: LevelFilter,
fs: FileSpec,
criterion: Criterion,
naming: Naming,
cleanup: Cleanup,
append: bool,
modules: Vec<(String, LevelFilter)>,
writer: Option<Box<dyn LogWriter>>,
log_etc_path: PathBuf,
}
impl LoggerFeatureBuilder {
pub fn default(
app: &str,
_debug_level: DebugLevel,
prod_level: LevelFilter,
log_etc_path: PathBuf,
log_path: PathBuf,
) -> Self {
let fs = FileSpec::default()
.directory(log_path)
.basename(app)
.suffix("log");
let criterion = Criterion::AgeOrSize(Age::Day, 10_000_000);
let naming = Naming::Numbers;
let cleanup = Cleanup::KeepLogFiles(10);
let append = true;
Self {
_app: app.to_string(),
_debug_level,
_prod_level: prod_level,
fs,
criterion,
naming,
cleanup,
append,
modules: Vec::new(),
writer: None,
log_etc_path,
}
}
pub fn module<M: AsRef<str>>(mut self, module_name: M, lf: LevelFilter) -> Self {
self.modules.push((module_name.as_ref().to_owned(), lf));
self
}
pub fn log_to_write(mut self, w: Box<dyn LogWriter>) -> Self {
self.writer = Some(w);
self
}
pub fn config(
mut self,
fs: FileSpec,
criterion: Criterion,
naming: Naming,
cleanup: Cleanup,
append: bool,
) -> Self {
self.fs = fs;
self.criterion = criterion;
self.naming = naming;
self.cleanup = cleanup;
self.append = append;
self
}
#[cfg(feature = "prod")]
#[must_use]
pub fn build(self) -> LoggerHandle {
let mut log_spec_builder = LogSpecBuilder::new();
log_spec_builder.default(self._prod_level);
for (module, level) in self.modules {
log_spec_builder.module(module, level);
}
let path = self.log_etc_path.join(format!("{}.toml", self._app));
if let Some(w) = self.writer {
Logger::with(log_spec_builder.build())
.format(with_thread)
.write_mode(WriteMode::Direct)
.log_to_file_and_writer(self.fs, w)
.o_append(self.append)
.rotate(self.criterion, self.naming, self.cleanup)
.start_with_specfile(path)
.unwrap()
} else {
Logger::with(log_spec_builder.build())
.format(with_thread)
.write_mode(WriteMode::Direct)
.log_to_file(self.fs)
.o_append(self.append)
.rotate(self.criterion, self.naming, self.cleanup)
.start_with_specfile(path)
.unwrap()
}
}
#[cfg(not(feature = "prod"))]
#[must_use]
pub fn build(self) -> LoggerHandle {
let specification = match self._debug_level {
DebugLevel::Filter(debug_level) => {
let mut log_spec_builder = LogSpecBuilder::new();
log_spec_builder.default(debug_level);
for (module, level) in self.modules {
log_spec_builder.module(module, level);
}
log_spec_builder.build()
}
DebugLevel::Env(default) => {
if let Some(env_val) =
std::env::vars().find_map(|x| if x.0 == self._app { Some(x.1) } else { None })
{
match LogSpecification::parse(env_val) {
Ok(rs) => rs,
Err(e) => {
println!("{:?}", e);
LogSpecification::env_or_parse(default).unwrap()
}
}
} else {
LogSpecification::parse(default).unwrap()
}
}
};
if let Some(w) = self.writer {
LoggerBuilder2 {
logger: Logger::with(specification)
.format(colored_with_thread)
.write_mode(WriteMode::Direct)
.duplicate_to_stdout(Duplicate::All),
}
.log_to_writer(w)
.start()
} else {
LoggerBuilder2 {
logger: Logger::with(specification)
.format(colored_with_thread)
.write_mode(WriteMode::Direct),
}
.log_to_stdout()
.start()
}
}
}
lazy_static::lazy_static! {
static ref MY_PALETTE: std::sync::RwLock<Palette> = std::sync::RwLock::new(Palette::default());
}
pub fn style(level: log::Level) -> Style {
let palette = &*(MY_PALETTE.read().unwrap());
match level {
log::Level::Error => palette.error,
log::Level::Warn => palette.warn,
log::Level::Info => palette.info,
log::Level::Debug => palette.debug,
log::Level::Trace => palette.trace,
}
}
#[derive(Debug)]
struct Palette {
pub error: Style,
pub warn: Style,
pub info: Style,
pub debug: Style,
pub trace: Style,
}
impl Palette {
fn default() -> Palette {
Palette {
error: Style::default().fg(Color::Red).bold(),
warn: Style::default().fg(Color::Yellow).bold(),
info: Style::default(),
debug: Style::default().fg(Color::Fixed(28)),
trace: Style::default().fg(Color::Fixed(8)),
}
}
}
pub enum DebugLevel {
Filter(LevelFilter),
Env(String),
}
impl From<LevelFilter> for DebugLevel {
fn from(value: LevelFilter) -> Self {
Self::Filter(value)
}
}
impl From<&str> for DebugLevel {
fn from(value: &str) -> Self {
Self::Env(value.to_string())
}
}