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-10-10 16:52:03 Functions: 28.9 % 38 11

            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            8 :     fn from_str(input: &str) -> Option<Self> {
      16            8 :         match input.to_ascii_lowercase().as_str() {
      17            8 :             "error" => Some(Self::Error),
      18            7 :             "warn" | "warning" => Some(Self::Warn),
      19            6 :             "info" => Some(Self::Info),
      20            5 :             "debug" => Some(Self::Debug),
      21            4 :             "trace" => Some(Self::Trace),
      22            2 :             _ => None,
      23              :         }
      24            8 :     }
      25              : 
      26            5 :     fn as_str(self) -> &'static str {
      27            5 :         match self {
      28            1 :             Self::Error => "ERROR",
      29            1 :             Self::Warn => "WARN",
      30            1 :             Self::Info => "INFO",
      31            1 :             Self::Debug => "DEBUG",
      32            1 :             Self::Trace => "TRACE",
      33              :         }
      34            5 :     }
      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            4 : fn read_config() -> LoggerConfig {
      49            4 :     let enabled = std::env::var("HUNKY_LOG")
      50            4 :         .map(|v| matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on"))
      51            4 :         .unwrap_or(false);
      52              : 
      53            4 :     let level = std::env::var("HUNKY_LOG_LEVEL")
      54            4 :         .ok()
      55            4 :         .as_deref()
      56            4 :         .and_then(LogLevel::from_str)
      57            4 :         .unwrap_or(LogLevel::Info);
      58              : 
      59            4 :     let file_path = std::env::var("HUNKY_LOG_FILE").unwrap_or_else(|_| "hunky.log".to_string());
      60              : 
      61            4 :     let filtered_events_enabled = std::env::var("HUNKY_LOG_FILTERED_EVENTS")
      62            4 :         .map(|v| matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on"))
      63            4 :         .unwrap_or(false);
      64              : 
      65            4 :     LoggerConfig {
      66            4 :         enabled,
      67            4 :         level,
      68            4 :         file_path,
      69            4 :         filtered_events_enabled,
      70            4 :     }
      71            4 : }
      72              : 
      73          253 : fn config() -> &'static LoggerConfig {
      74          253 :     LOGGER_CONFIG.get_or_init(read_config)
      75          253 : }
      76              : 
      77            0 : pub fn init() {
      78            0 :     let _ = config();
      79            0 : }
      80              : 
      81          140 : pub fn enabled(level: LogLevel) -> bool {
      82          140 :     let cfg = config();
      83          140 :     cfg.enabled && level <= cfg.level
      84          140 : }
      85              : 
      86          113 : pub fn filtered_events_enabled() -> bool {
      87          113 :     let cfg = config();
      88          113 :     cfg.enabled && cfg.filtered_events_enabled
      89          113 : }
      90              : 
      91          140 : pub fn log(level: LogLevel, msg: impl AsRef<str>) {
      92          140 :     if !enabled(level) {
      93          140 :         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          140 : }
     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          140 : pub fn debug(msg: impl AsRef<str>) {
     128          140 :     log(LogLevel::Debug, msg);
     129          140 : }
     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