loggur 0.1.0

로깅 크레이트
Documentation
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()
    }
}

// 매크로 재정의 (tracing 매크로를 사용하도록)
#[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
        }
    };
}

// span 생성 및 진행 상태 표시 매크로
#[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);
    };
}