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#[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 println!("[{}][{}] {}", now.format("%H:%M:%S"), level_str, msg);
104 }
105
106 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}