1use parking_lot::Mutex;
2use std::fmt;
15use std::fs::OpenOptions;
16use std::io::Write;
17use std::sync::OnceLock;
18use std::time::{SystemTime, UNIX_EPOCH};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
22pub enum DebugLevel {
23 Off = 0,
24 Error = 1,
25 Info = 2,
26 Debug = 3,
27 Trace = 4,
28}
29
30impl DebugLevel {
31 fn from_env() -> Self {
32 match std::env::var("DEBUG_LEVEL") {
33 Ok(val) => match val.trim().parse::<u8>() {
34 Ok(0) => DebugLevel::Off,
35 Ok(1) => DebugLevel::Error,
36 Ok(2) => DebugLevel::Info,
37 Ok(3) => DebugLevel::Debug,
38 Ok(4) => DebugLevel::Trace,
39 _ => DebugLevel::Off,
40 },
41 Err(_) => DebugLevel::Off,
42 }
43 }
44}
45
46struct DebugLogger {
48 level: DebugLevel,
49 file: Option<std::fs::File>,
50}
51
52impl DebugLogger {
53 fn new() -> Self {
54 let level = DebugLevel::from_env();
55
56 let file = if level != DebugLevel::Off {
57 #[cfg(unix)]
58 let log_path = std::path::PathBuf::from("/tmp/par_term_debug.log");
59 #[cfg(windows)]
60 let log_path = std::env::temp_dir().join("par_term_debug.log");
61
62 match OpenOptions::new()
63 .write(true)
64 .truncate(true)
65 .create(true)
66 .open(&log_path)
67 {
68 Ok(f) => {
69 let mut logger = DebugLogger {
71 level,
72 file: Some(f),
73 };
74 logger.write_raw(&format!(
75 "\n{}\npar-term debug session started at {} (level={:?})\n{}\n",
76 "=".repeat(80),
77 get_timestamp(),
78 level,
79 "=".repeat(80)
80 ));
81 return logger;
82 }
83 Err(_e) => {
84 None
87 }
88 }
89 } else {
90 None
91 };
92
93 DebugLogger { level, file }
94 }
95
96 fn write_raw(&mut self, msg: &str) {
97 if let Some(ref mut file) = self.file {
98 let _ = file.write_all(msg.as_bytes());
99 let _ = file.flush();
100 }
101 }
102
103 fn log(&mut self, level: DebugLevel, category: &str, msg: &str) {
104 if level <= self.level {
105 let timestamp = get_timestamp();
106 let level_str = match level {
107 DebugLevel::Error => "ERROR",
108 DebugLevel::Info => "INFO ",
109 DebugLevel::Debug => "DEBUG",
110 DebugLevel::Trace => "TRACE",
111 DebugLevel::Off => return,
112 };
113 self.write_raw(&format!(
114 "[{}] [{}] [{}] {}\n",
115 timestamp, level_str, category, msg
116 ));
117 }
118 }
119}
120
121static LOGGER: OnceLock<Mutex<DebugLogger>> = OnceLock::new();
122
123fn get_logger() -> &'static Mutex<DebugLogger> {
124 LOGGER.get_or_init(|| Mutex::new(DebugLogger::new()))
125}
126
127fn get_timestamp() -> String {
128 let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
129 format!("{}.{:06}", now.as_secs(), now.subsec_micros())
130}
131
132pub fn is_enabled(level: DebugLevel) -> bool {
134 let logger = get_logger().lock();
135 level <= logger.level
136}
137
138pub fn log(level: DebugLevel, category: &str, msg: &str) {
140 let mut logger = get_logger().lock();
141 logger.log(level, category, msg);
142}
143
144pub fn logf(level: DebugLevel, category: &str, args: fmt::Arguments) {
146 if is_enabled(level) {
147 log(level, category, &format!("{}", args));
148 }
149}
150
151#[macro_export]
153macro_rules! debug_error {
154 ($category:expr, $($arg:tt)*) => {
155 $crate::debug::logf($crate::debug::DebugLevel::Error, $category, format_args!($($arg)*))
156 };
157}
158
159#[macro_export]
160macro_rules! debug_info {
161 ($category:expr, $($arg:tt)*) => {
162 $crate::debug::logf($crate::debug::DebugLevel::Info, $category, format_args!($($arg)*))
163 };
164}
165
166#[macro_export]
167macro_rules! debug_log {
168 ($category:expr, $($arg:tt)*) => {
169 $crate::debug::logf($crate::debug::DebugLevel::Debug, $category, format_args!($($arg)*))
170 };
171}
172
173#[macro_export]
174macro_rules! debug_trace {
175 ($category:expr, $($arg:tt)*) => {
176 $crate::debug::logf($crate::debug::DebugLevel::Trace, $category, format_args!($($arg)*))
177 };
178}