ribo 0.1.3

Ribo for universe, provide tons of util functions.
Documentation
//! Logging utilities using tracing
//!
//! This module re-exports tracing and provides utilities for initializing logging.

/// Re-export tracing so that projects using ribo don't need to depend on tracing directly
pub use tracing;

use tracing_subscriber::fmt::time::LocalTime;
use tracing_subscriber::{fmt, EnvFilter};

pub use tracing::{debug, error, info, warn};

#[derive(Debug, Clone, Copy)]
pub enum LogLevel {
    ERROR,
    WARN,
    INFO,
    DEBUG,
    TRACE,
}

impl From<LogLevel> for tracing::Level {
    fn from(level: LogLevel) -> Self {
        match level {
            LogLevel::ERROR => tracing::Level::ERROR,
            LogLevel::WARN => tracing::Level::WARN,
            LogLevel::INFO => tracing::Level::INFO,
            LogLevel::DEBUG => tracing::Level::DEBUG,
            LogLevel::TRACE => tracing::Level::TRACE,
        }
    }
}

/// Initialize the global tracing subscriber with a default configuration
///
/// This sets up a subscriber with:
/// - Environment filter using `RUST_LOG` environment variable
/// - Target information included in logs
/// - File and line number information
/// - RFC3339 formatted timestamps
/// - Colored output enabled

pub fn init_log(level: LogLevel) {
    let filter = EnvFilter::builder()
        .with_default_directive(tracing::Level::from(level).into())
        .from_env_lossy(); // allow RUST_LOG to override

    use time::format_description::parse;
    use time::UtcOffset;

    let format = parse("[year]-[month padding:zero]-[day padding:zero] [hour]:[minute]:[second]")
        .expect("Failed to parse time format");

    // Obtain the local time offset
    let offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);

    // Create a custom timer using the offset and format
    let timer = fmt::time::OffsetTime::new(offset, format);
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .compact()
        .with_timer(timer) // simplified timestamp
        .with_target(true)
        .with_file(true)
        .with_line_number(true)
        .with_ansi(true)
        .init();
}

/// Initialize the global tracing subscriber with custom configuration
///
/// # Arguments
///
/// * `env_filter` - A custom environment filter string (e.g., "info", "debug", "my_crate=trace")
/// * `include_ansi` - Whether to include ANSI color codes in output
pub fn init_with_filter(env_filter: &str, include_ansi: bool) {
    fmt()
        .with_env_filter(EnvFilter::new(env_filter))
        .with_target(true)
        .with_file(true)
        .with_line_number(true)
        .with_timer(fmt::time::LocalTime::rfc_3339())
        .with_ansi(include_ansi)
        .init()
}

/// Initialize the global tracing subscriber with a custom formatter
///
/// This allows for more fine-grained control over the logging setup
pub fn init_with_config<F>(config_fn: F)
where
    F: Fn(fmt::SubscriberBuilder) -> fmt::SubscriberBuilder,
{
    let subscriber = fmt::Subscriber::builder();
    let configured_subscriber = config_fn(subscriber);
    configured_subscriber.init()
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_tracing_reexport() {
        // Test that tracing is properly re-exported
        use crate::log::tracing;
        tracing::trace!("Test trace message"); // Just verify it compiles and can be used
        assert!(true);
    }

    #[test]
    fn test_init_function() {
        // Test that the init function can be called
        // Note: Since tracing is global, we can only test that it doesn't panic
        // in tests where the global subscriber hasn't been set yet
        let result = std::panic::catch_unwind(|| {
            crate::log::init();
        });

        // The init should succeed if it's the first call, or fail if called again
        // (which is expected in a test environment) - so we just check it doesn't panic unexpectedly
        assert!(result.is_ok() || result.is_err());
    }
}