use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Instant;
pub use tracing::Level;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use tracing_indicatif::IndicatifLayer;
use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
use tracing_appender::rolling::{RollingFileAppender, Rotation};
use console::Emoji;
use indicatif::ProgressStyle;
pub static TRACE_EMOJI: Emoji<'_, '_> = Emoji("🔍 ", "");
pub static DEBUG_EMOJI: Emoji<'_, '_> = Emoji("🐛 ", "");
pub static INFO_EMOJI: Emoji<'_, '_> = Emoji("ℹ️ ", "");
pub static WARN_EMOJI: Emoji<'_, '_> = Emoji("⚠️ ", "");
pub static ERROR_EMOJI: Emoji<'_, '_> = Emoji("❌ ", "");
pub static FATAL_EMOJI: Emoji<'_, '_> = Emoji("💥 ", "");
pub static TIMER_EMOJI: Emoji<'_, '_> = Emoji("⏱️ ", "");
pub static SUCCESS_EMOJI: Emoji<'_, '_> = Emoji("✅ ", "");
static INITIALIZED: AtomicBool = AtomicBool::new(false);
pub struct Timer {
start: Instant,
}
impl Timer {
pub fn new() -> Self {
Self {
start: Instant::now(),
}
}
pub fn elapsed(&self) -> std::time::Duration {
self.start.elapsed()
}
pub fn elapsed_str(&self) -> String {
let elapsed = self.elapsed();
format!(
"{}.{:03}s",
elapsed.as_secs(),
elapsed.subsec_millis()
)
}
pub fn reset(&mut self) {
self.start = Instant::now();
}
}
pub struct Loggur;
impl Loggur {
pub fn init(level: Level) -> Option<WorkerGuard> {
if INITIALIZED.swap(true, Ordering::SeqCst) {
return None;
}
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(level.as_str()));
let fmt_layer = tracing_subscriber::fmt::layer()
.with_target(true)
.with_ansi(true)
.with_level(true)
.with_file(true)
.with_line_number(true);
tracing_subscriber::registry()
.with(env_filter)
.with(fmt_layer)
.init();
None
}
pub fn init_with_file(level: Level, file_path: &str) -> Option<WorkerGuard> {
if INITIALIZED.swap(true, Ordering::SeqCst) {
return None;
}
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(level.as_str()));
let fmt_layer = tracing_subscriber::fmt::layer()
.with_target(true)
.with_ansi(true)
.with_level(true)
.with_file(true)
.with_line_number(true);
let file_appender = RollingFileAppender::new(
Rotation::NEVER,
std::path::Path::new(file_path).parent().unwrap_or_else(|| std::path::Path::new(".")),
std::path::Path::new(file_path).file_name().unwrap_or_default(),
);
let (non_blocking, guard) = NonBlocking::new(file_appender);
let file_layer = tracing_subscriber::fmt::layer()
.with_writer(non_blocking)
.with_ansi(false)
.with_target(true)
.with_level(true)
.with_file(true)
.with_line_number(true);
tracing_subscriber::registry()
.with(env_filter)
.with(fmt_layer)
.with(file_layer)
.init();
Some(guard)
}
pub fn init_with_progress(level: Level) -> Option<WorkerGuard> {
if INITIALIZED.swap(true, Ordering::SeqCst) {
return None;
}
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(level.as_str()));
let fmt_layer = tracing_subscriber::fmt::layer()
.with_target(true)
.with_ansi(true)
.with_level(true)
.with_file(true)
.with_line_number(true);
let progress_style = ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
.unwrap()
.progress_chars("#>-");
let indicatif_layer = IndicatifLayer::new()
.with_progress_style(progress_style);
tracing_subscriber::registry()
.with(env_filter)
.with(indicatif_layer)
.with(fmt_layer)
.init();
None
}
pub fn init_full(level: Level, file_path: Option<&str>) -> Option<WorkerGuard> {
if INITIALIZED.swap(true, Ordering::SeqCst) {
return None;
}
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(level.as_str()));
let progress_style = ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
.unwrap()
.progress_chars("#>-");
let indicatif_layer = IndicatifLayer::new()
.with_progress_style(progress_style);
let fmt_layer = tracing_subscriber::fmt::layer()
.with_target(true)
.with_ansi(true)
.with_level(true)
.with_file(true)
.with_line_number(true);
if let Some(path) = file_path {
let file_appender = RollingFileAppender::new(
Rotation::NEVER,
std::path::Path::new(path).parent().unwrap_or_else(|| std::path::Path::new(".")),
std::path::Path::new(path).file_name().unwrap_or_default(),
);
let (non_blocking, guard) = NonBlocking::new(file_appender);
let file_layer = tracing_subscriber::fmt::layer()
.with_writer(non_blocking)
.with_ansi(false)
.with_target(true)
.with_level(true)
.with_file(true)
.with_line_number(true);
tracing_subscriber::registry()
.with(env_filter)
.with(indicatif_layer)
.with(fmt_layer)
.with(file_layer)
.init();
return Some(guard);
} else {
tracing_subscriber::registry()
.with(env_filter)
.with(indicatif_layer)
.with(fmt_layer)
.init();
return None;
}
}
pub fn spinner() -> indicatif::ProgressBar {
let pb = indicatif::ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} {wide_msg}")
.unwrap()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "),
);
pb
}
pub fn timer() -> Timer {
Timer::new()
}
}
#[macro_export]
macro_rules! trace {
($($arg:tt)*) => {
tracing::trace!($($arg)*)
};
}
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => {
tracing::debug!($($arg)*)
};
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
tracing::info!($($arg)*)
};
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
tracing::warn!($($arg)*)
};
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
tracing::error!($($arg)*)
};
}
#[macro_export]
macro_rules! time_it {
($name:expr, $block:block) => {
{
let timer = $crate::Loggur::timer();
let result = $block;
$crate::info!("{} {} 완료: {}", $crate::TIMER_EMOJI, $name, timer.elapsed_str());
result
}
};
}
#[macro_export]
macro_rules! progress_span {
($name:expr, $total:expr) => {
tracing::info_span!($name, progress_total = $total)
};
}
#[macro_export]
macro_rules! progress_inc {
($amount:expr) => {
tracing::Span::current().record("progress_step", $amount);
};
}