Skip to main content

hydra_sync/
log.rs

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