Skip to main content

hydra_sync/
log.rs

1use colored::{ColoredString, Colorize};
2use std::env;
3use std::fs::{File, OpenOptions};
4use std::io::{BufWriter, Write};
5use std::sync::{LazyLock, Mutex};
6
7static LOG_LEVEL: LazyLock<Level> = LazyLock::new(Level::from_env);
8static LOG_FILE: LazyLock<Mutex<Option<BufWriter<File>>>> = LazyLock::new(|| Mutex::new(None));
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
11pub enum Level {
12    Trace = 0,
13    Debug = 1,
14    Info = 2,
15    Warn = 3,
16    Error = 4,
17}
18
19impl Level {
20    fn from_env() -> Self {
21        dotenv::dotenv().ok();
22
23        if let Ok(path) = env::var("LOG_FILE") {
24            init(&path).ok();
25        }
26
27        env::var("LOG_LEVEL")
28            .ok()
29            .and_then(|s| match s.to_lowercase().as_str() {
30                "trace" => Some(Level::Trace),
31                "debug" => Some(Level::Debug),
32                "info" => Some(Level::Info),
33                "warn" => Some(Level::Warn),
34                "error" => Some(Level::Error),
35                _ => None,
36            })
37            .unwrap_or(Level::Info)
38    }
39}
40
41#[inline]
42fn should_log(level: Level) -> bool {
43    level >= *LOG_LEVEL
44}
45
46pub fn init(path: &str) -> Result<(), std::io::Error> {
47    let stem = path.strip_suffix(".log").unwrap_or(path);
48    let timestamped = format!(
49        "{}_{}.log",
50        stem,
51        chrono::Local::now().format("%Y%m%d_%H%M%S")
52    );
53    let file = OpenOptions::new()
54        .create(true)
55        .append(true)
56        .open(&timestamped)?;
57    *LOG_FILE.lock().unwrap() = Some(BufWriter::new(file));
58    Ok(())
59}
60
61#[inline]
62pub fn log(level: Level, msg: ColoredString) {
63    if !should_log(level) {
64        return;
65    }
66
67    let now = chrono::Local::now();
68
69    let level_str = match level {
70        Level::Trace => "TRACE".dimmed(),
71        Level::Debug => "DEBUG".blue().bold(),
72        Level::Info => "INFO".green().bold(),
73        Level::Warn => "WARN".yellow().bold(),
74        Level::Error => "ERROR".red().bold(),
75    };
76
77    println!("[{}][{}] {}", now.format("%H:%M:%S"), level_str, msg);
78
79    if let Ok(mut guard) = LOG_FILE.lock()
80        && let Some(ref mut file) = *guard
81    {
82        let level_plain = match level {
83            Level::Trace => "TRACE",
84            Level::Debug => "DEBUG",
85            Level::Info => "INFO",
86            Level::Warn => "WARN",
87            Level::Error => "ERROR",
88        };
89        let _ = writeln!(
90            file,
91            "[{}][{}] {}",
92            now.format("%Y-%m-%d %H:%M:%S"),
93            level_plain,
94            &*msg
95        );
96        let _ = file.flush();
97    }
98}
99
100#[macro_export]
101macro_rules! trace {
102    ($($arg:tt)*) => {
103        {
104            use colored::Colorize;
105            $crate::log::log($crate::log::Level::Trace, format!($($arg)*).dimmed())
106        }
107    };
108}
109
110#[macro_export]
111macro_rules! debug {
112    ($($arg:tt)*) => {
113        {
114            use colored::Colorize;
115            $crate::log::log($crate::log::Level::Debug, format!($($arg)*).blue())
116        }
117    };
118}
119
120#[macro_export]
121macro_rules! info {
122    ($($arg:tt)*) => {
123        {
124            use colored::Colorize;
125            $crate::log::log($crate::log::Level::Info, format!($($arg)*).green())
126        }
127    };
128}
129
130#[macro_export]
131macro_rules! warn {
132    ($($arg:tt)*) => {
133        {
134            use colored::Colorize;
135            $crate::log::log($crate::log::Level::Warn, format!($($arg)*).yellow())
136        }
137    };
138}
139
140#[macro_export]
141macro_rules! error {
142    ($($arg:tt)*) => {
143        {
144            use colored::Colorize;
145            $crate::log::log($crate::log::Level::Error, format!($($arg)*).red())
146        }
147    };
148}