LCOV - code coverage report
Current view: top level - src - logger.rs (source / functions) Coverage Total Hit
Test: Hunky Coverage Lines: 65.5 % 84 55
Test Date: 2026-02-25 04:31:59 Functions: 55.3 % 38 21

            Line data    Source code
       1              : use std::fs::OpenOptions;
       2              : use std::io::Write;
       3              : use std::sync::{Mutex, OnceLock};
       4              : 
       5              : #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
       6              : pub enum LogLevel {
       7              :     Error = 1,
       8              :     Warn = 2,
       9              :     Info = 3,
      10              :     Debug = 4,
      11              :     Trace = 5,
      12              : }
      13              : 
      14              : impl LogLevel {
      15           16 :     fn from_str(input: &str) -> Option<Self> {
      16           16 :         match input.to_ascii_lowercase().as_str() {
      17           16 :             "error" => Some(Self::Error),
      18           14 :             "warn" | "warning" => Some(Self::Warn),
      19           12 :             "info" => Some(Self::Info),
      20           10 :             "debug" => Some(Self::Debug),
      21            8 :             "trace" => Some(Self::Trace),
      22            4 :             _ => None,
      23              :         }
      24           16 :     }
      25              : 
      26           10 :     fn as_str(self) -> &'static str {
      27           10 :         match self {
      28            2 :             Self::Error => "ERROR",
      29            2 :             Self::Warn => "WARN",
      30            2 :             Self::Info => "INFO",
      31            2 :             Self::Debug => "DEBUG",
      32            2 :             Self::Trace => "TRACE",
      33              :         }
      34           10 :     }
      35              : }
      36              : 
      37              : #[derive(Clone, Debug)]
      38              : struct LoggerConfig {
      39              :     enabled: bool,
      40              :     level: LogLevel,
      41              :     file_path: String,
      42              :     filtered_events_enabled: bool,
      43              : }
      44              : 
      45              : static LOGGER_CONFIG: OnceLock<LoggerConfig> = OnceLock::new();
      46              : static LOG_WRITE_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
      47              : 
      48            8 : fn read_config() -> LoggerConfig {
      49            8 :     let enabled = std::env::var("HUNKY_LOG")
      50            8 :         .map(|v| matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on"))
      51            8 :         .unwrap_or(false);
      52              : 
      53            8 :     let level = std::env::var("HUNKY_LOG_LEVEL")
      54            8 :         .ok()
      55            8 :         .as_deref()
      56            8 :         .and_then(LogLevel::from_str)
      57            8 :         .unwrap_or(LogLevel::Info);
      58              : 
      59            8 :     let file_path = std::env::var("HUNKY_LOG_FILE").unwrap_or_else(|_| "hunky.log".to_string());
      60              : 
      61            8 :     let filtered_events_enabled = std::env::var("HUNKY_LOG_FILTERED_EVENTS")
      62            8 :         .map(|v| matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on"))
      63            8 :         .unwrap_or(false);
      64              : 
      65            8 :     LoggerConfig {
      66            8 :         enabled,
      67            8 :         level,
      68            8 :         file_path,
      69            8 :         filtered_events_enabled,
      70            8 :     }
      71            8 : }
      72              : 
      73           61 : fn config() -> &'static LoggerConfig {
      74           61 :     LOGGER_CONFIG.get_or_init(read_config)
      75           61 : }
      76              : 
      77            0 : pub fn init() {
      78            0 :     let _ = config();
      79            0 : }
      80              : 
      81           60 : pub fn enabled(level: LogLevel) -> bool {
      82           60 :     let cfg = config();
      83           60 :     cfg.enabled && level <= cfg.level
      84           60 : }
      85              : 
      86            1 : pub fn filtered_events_enabled() -> bool {
      87            1 :     let cfg = config();
      88            1 :     cfg.enabled && cfg.filtered_events_enabled
      89            1 : }
      90              : 
      91           60 : pub fn log(level: LogLevel, msg: impl AsRef<str>) {
      92           60 :     if !enabled(level) {
      93           60 :         return;
      94            0 :     }
      95              : 
      96            0 :     let cfg = config();
      97            0 :     let write_lock = LOG_WRITE_LOCK.get_or_init(|| Mutex::new(()));
      98            0 :     let _guard = write_lock.lock().unwrap_or_else(|e| e.into_inner());
      99              : 
     100            0 :     if let Ok(mut file) = OpenOptions::new()
     101            0 :         .create(true)
     102            0 :         .append(true)
     103            0 :         .open(&cfg.file_path)
     104              :     {
     105            0 :         let ts = std::time::SystemTime::now()
     106            0 :             .duration_since(std::time::UNIX_EPOCH)
     107            0 :             .map(|d| d.as_secs())
     108            0 :             .unwrap_or(0);
     109            0 :         let _ = writeln!(file, "[{}] [{}] {}", ts, level.as_str(), msg.as_ref());
     110            0 :     }
     111           60 : }
     112              : 
     113              : #[allow(dead_code)]
     114            0 : pub fn error(msg: impl AsRef<str>) {
     115            0 :     log(LogLevel::Error, msg);
     116            0 : }
     117              : 
     118            0 : pub fn warn(msg: impl AsRef<str>) {
     119            0 :     log(LogLevel::Warn, msg);
     120            0 : }
     121              : 
     122              : #[allow(dead_code)]
     123            0 : pub fn info(msg: impl AsRef<str>) {
     124            0 :     log(LogLevel::Info, msg);
     125            0 : }
     126              : 
     127           60 : pub fn debug(msg: impl AsRef<str>) {
     128           60 :     log(LogLevel::Debug, msg);
     129           60 : }
     130              : 
     131            0 : pub fn trace(msg: impl AsRef<str>) {
     132            0 :     log(LogLevel::Trace, msg);
     133            0 : }
     134              : 
     135              : #[cfg(test)]
     136              : #[path = "../tests/logger.rs"]
     137              : mod tests;
        

Generated by: LCOV version 2.0-1