1use parking_lot::Mutex;
19use std::fmt;
20use std::fs::OpenOptions;
21use std::io::Write;
22use std::sync::OnceLock;
23use std::time::{SystemTime, UNIX_EPOCH};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
27pub enum DebugLevel {
28 Off = 0,
29 Error = 1,
30 Info = 2,
31 Debug = 3,
32 Trace = 4,
33}
34
35impl DebugLevel {
36 fn from_env() -> Self {
37 match std::env::var("DEBUG_LEVEL") {
38 Ok(val) => match val.trim().parse::<u8>() {
39 Ok(0) => DebugLevel::Off,
40 Ok(1) => DebugLevel::Error,
41 Ok(2) => DebugLevel::Info,
42 Ok(3) => DebugLevel::Debug,
43 Ok(4) => DebugLevel::Trace,
44 _ => DebugLevel::Off,
45 },
46 Err(_) => DebugLevel::Off,
47 }
48 }
49}
50
51struct DebugLogger {
53 level: DebugLevel,
55 file: Option<std::fs::File>,
57}
58
59impl DebugLogger {
60 fn new() -> Self {
61 let level = DebugLevel::from_env();
62
63 #[cfg(unix)]
64 let log_path = std::path::PathBuf::from("/tmp/par_term_debug.log");
65 #[cfg(windows)]
66 let log_path = std::env::temp_dir().join("par_term_debug.log");
67
68 let file = OpenOptions::new()
69 .write(true)
70 .truncate(true)
71 .create(true)
72 .open(&log_path)
73 .ok();
74
75 let mut logger = DebugLogger { level, file };
76 logger.write_raw(&format!(
77 "\n{}\npar-term log session started at {} (debug_level={:?}, rust_log={})\n{}\n",
78 "=".repeat(80),
79 get_timestamp(),
80 level,
81 std::env::var("RUST_LOG").unwrap_or_else(|_| "unset".to_string()),
82 "=".repeat(80)
83 ));
84 logger
85 }
86
87 fn write_raw(&mut self, msg: &str) {
88 if let Some(ref mut file) = self.file {
89 let _ = file.write_all(msg.as_bytes());
90 let _ = file.flush();
91 }
92 }
93
94 fn log(&mut self, level: DebugLevel, category: &str, msg: &str) {
96 if level <= self.level {
97 let timestamp = get_timestamp();
98 let level_str = match level {
99 DebugLevel::Error => "ERROR",
100 DebugLevel::Info => "INFO ",
101 DebugLevel::Debug => "DEBUG",
102 DebugLevel::Trace => "TRACE",
103 DebugLevel::Off => return,
104 };
105 self.write_raw(&format!(
106 "[{}] [{}] [{}] {}\n",
107 timestamp, level_str, category, msg
108 ));
109 }
110 }
111
112 fn log_record(&mut self, record: &log::Record) {
114 let timestamp = get_timestamp();
115 let level_str = match record.level() {
116 log::Level::Error => "ERROR",
117 log::Level::Warn => "WARN ",
118 log::Level::Info => "INFO ",
119 log::Level::Debug => "DEBUG",
120 log::Level::Trace => "TRACE",
121 };
122 self.write_raw(&format!(
123 "[{}] [{}] [{}] {}\n",
124 timestamp,
125 level_str,
126 record.target(),
127 record.args()
128 ));
129 }
130}
131
132static LOGGER: OnceLock<Mutex<DebugLogger>> = OnceLock::new();
133
134fn get_logger() -> &'static Mutex<DebugLogger> {
135 LOGGER.get_or_init(|| Mutex::new(DebugLogger::new()))
136}
137
138fn get_timestamp() -> String {
139 let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
140 format!("{}.{:06}", now.as_secs(), now.subsec_micros())
141}
142
143pub fn log_path() -> std::path::PathBuf {
145 #[cfg(unix)]
146 {
147 std::path::PathBuf::from("/tmp/par_term_debug.log")
148 }
149 #[cfg(windows)]
150 {
151 std::env::temp_dir().join("par_term_debug.log")
152 }
153}
154
155pub fn is_enabled(level: DebugLevel) -> bool {
157 let logger = get_logger().lock();
158 level <= logger.level
159}
160
161pub fn log(level: DebugLevel, category: &str, msg: &str) {
163 let mut logger = get_logger().lock();
164 logger.log(level, category, msg);
165}
166
167pub fn logf(level: DebugLevel, category: &str, args: fmt::Arguments) {
169 if is_enabled(level) {
170 log(level, category, &format!("{}", args));
171 }
172}
173
174struct LogCrateBridge {
182 max_level: log::LevelFilter,
184 mirror_stderr: bool,
186 module_filters: Vec<(&'static str, log::LevelFilter)>,
188}
189
190impl LogCrateBridge {
191 fn new() -> Self {
192 let rust_log_set = std::env::var("RUST_LOG").is_ok();
193 let max_level = if rust_log_set {
194 match std::env::var("RUST_LOG")
196 .unwrap_or_default()
197 .to_lowercase()
198 .as_str()
199 {
200 "trace" => log::LevelFilter::Trace,
201 "debug" => log::LevelFilter::Debug,
202 "info" => log::LevelFilter::Info,
203 "warn" => log::LevelFilter::Warn,
204 "error" => log::LevelFilter::Error,
205 "off" => log::LevelFilter::Off,
206 _ => log::LevelFilter::Info, }
208 } else {
209 log::LevelFilter::Info
211 };
212
213 LogCrateBridge {
214 max_level,
215 mirror_stderr: rust_log_set,
216 module_filters: vec![
217 ("wgpu_core", log::LevelFilter::Warn),
218 ("wgpu_hal", log::LevelFilter::Warn),
219 ("naga", log::LevelFilter::Warn),
220 ("rodio", log::LevelFilter::Error),
221 ("cpal", log::LevelFilter::Error),
222 ],
223 }
224 }
225
226 fn level_for_module(&self, target: &str) -> log::LevelFilter {
227 for (prefix, filter) in &self.module_filters {
228 if target.starts_with(prefix) {
229 return *filter;
230 }
231 }
232 self.max_level
233 }
234}
235
236impl log::Log for LogCrateBridge {
237 fn enabled(&self, metadata: &log::Metadata) -> bool {
238 metadata.level() <= self.level_for_module(metadata.target())
239 }
240
241 fn log(&self, record: &log::Record) {
242 if !self.enabled(record.metadata()) {
243 return;
244 }
245
246 let mut logger = get_logger().lock();
248 logger.log_record(record);
249 drop(logger);
250
251 if self.mirror_stderr {
253 eprintln!(
254 "[{}] {}: {}",
255 record.level(),
256 record.target(),
257 record.args()
258 );
259 }
260 }
261
262 fn flush(&self) {}
263}
264
265pub fn init_log_bridge(level_override: Option<log::LevelFilter>) {
272 let _ = get_logger();
274
275 let bridge = LogCrateBridge::new();
276 let max_level = level_override.unwrap_or(bridge.max_level);
278
279 if log::set_boxed_logger(Box::new(bridge)).is_ok() {
281 log::set_max_level(max_level);
282 }
283}
284
285pub fn set_log_level(level: log::LevelFilter) {
289 log::set_max_level(level);
290}
291
292#[macro_export]
297macro_rules! debug_error {
298 ($category:expr, $($arg:tt)*) => {
299 $crate::debug::logf($crate::debug::DebugLevel::Error, $category, format_args!($($arg)*))
300 };
301}
302
303#[macro_export]
304macro_rules! debug_info {
305 ($category:expr, $($arg:tt)*) => {
306 $crate::debug::logf($crate::debug::DebugLevel::Info, $category, format_args!($($arg)*))
307 };
308}
309
310#[macro_export]
311macro_rules! debug_log {
312 ($category:expr, $($arg:tt)*) => {
313 $crate::debug::logf($crate::debug::DebugLevel::Debug, $category, format_args!($($arg)*))
314 };
315}
316
317#[macro_export]
318macro_rules! debug_trace {
319 ($category:expr, $($arg:tt)*) => {
320 $crate::debug::logf($crate::debug::DebugLevel::Trace, $category, format_args!($($arg)*))
321 };
322}