1use alloc::string::String;
35use alloc::vec::Vec;
36
37#[cfg(feature = "std")]
38use std::sync::{Arc, Mutex};
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
42#[repr(C)]
43pub enum DebugLevel {
44 Trace,
46 Debug,
48 Info,
50 Warn,
52 Error,
54}
55
56impl Default for DebugLevel {
57 fn default() -> Self {
58 DebugLevel::Debug
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
64#[repr(C)]
65pub enum DebugCategory {
66 General,
68 Window,
70 EventLoop,
72 Input,
74 Layout,
76 Text,
78 DisplayList,
80 SceneBuilding,
82 Rendering,
84 Resources,
86 Callbacks,
88 Timer,
90 DebugServer,
92 Platform,
94 Icon,
96}
97
98impl Default for DebugCategory {
99 fn default() -> Self {
100 DebugCategory::General
101 }
102}
103
104#[derive(Debug, Clone, PartialEq)]
106#[repr(C)]
107pub struct LogMessage {
108 pub level: DebugLevel,
110 pub category: DebugCategory,
112 pub message: String,
114 pub location: String,
116 pub elapsed_us: u64,
118 pub window_id: Option<String>,
120}
121
122#[cfg(feature = "std")]
128pub struct DebugLogger {
129 messages: Vec<LogMessage>,
130 start_time: std::time::Instant,
131 min_level: DebugLevel,
133 category_filter: Option<Vec<DebugCategory>>,
135 current_window_id: Option<String>,
137}
138
139#[cfg(feature = "std")]
140impl DebugLogger {
141 pub fn new() -> Self {
143 Self {
144 messages: Vec::new(),
145 start_time: std::time::Instant::now(),
146 min_level: DebugLevel::Trace,
147 category_filter: None,
148 current_window_id: None,
149 }
150 }
151
152 pub fn with_min_level(min_level: DebugLevel) -> Self {
154 Self {
155 messages: Vec::new(),
156 start_time: std::time::Instant::now(),
157 min_level,
158 category_filter: None,
159 current_window_id: None,
160 }
161 }
162
163 pub fn with_categories(categories: Vec<DebugCategory>) -> Self {
165 Self {
166 messages: Vec::new(),
167 start_time: std::time::Instant::now(),
168 min_level: DebugLevel::Trace,
169 category_filter: Some(categories),
170 current_window_id: None,
171 }
172 }
173
174 pub fn set_window_context(&mut self, window_id: Option<String>) {
176 self.current_window_id = window_id;
177 }
178
179 #[track_caller]
181 pub fn log(&mut self, level: DebugLevel, category: DebugCategory, message: impl Into<String>) {
182 if level < self.min_level {
184 return;
185 }
186
187 if let Some(ref allowed) = self.category_filter {
189 if !allowed.contains(&category) {
190 return;
191 }
192 }
193
194 let location = core::panic::Location::caller();
195 self.messages.push(LogMessage {
196 level,
197 category,
198 message: message.into(),
199 location: format!("{}:{}", location.file(), location.line()),
200 elapsed_us: self.start_time.elapsed().as_micros() as u64,
201 window_id: self.current_window_id.clone(),
202 });
203 }
204
205 #[track_caller]
207 pub fn trace(&mut self, category: DebugCategory, message: impl Into<String>) {
208 self.log(DebugLevel::Trace, category, message);
209 }
210
211 #[track_caller]
213 pub fn debug(&mut self, category: DebugCategory, message: impl Into<String>) {
214 self.log(DebugLevel::Debug, category, message);
215 }
216
217 #[track_caller]
219 pub fn info(&mut self, category: DebugCategory, message: impl Into<String>) {
220 self.log(DebugLevel::Info, category, message);
221 }
222
223 #[track_caller]
225 pub fn warn(&mut self, category: DebugCategory, message: impl Into<String>) {
226 self.log(DebugLevel::Warn, category, message);
227 }
228
229 #[track_caller]
231 pub fn error(&mut self, category: DebugCategory, message: impl Into<String>) {
232 self.log(DebugLevel::Error, category, message);
233 }
234
235 pub fn take_messages(&mut self) -> Vec<LogMessage> {
237 core::mem::take(&mut self.messages)
238 }
239
240 pub fn messages(&self) -> &[LogMessage] {
242 &self.messages
243 }
244
245 pub fn len(&self) -> usize {
247 self.messages.len()
248 }
249
250 pub fn is_empty(&self) -> bool {
252 self.messages.is_empty()
253 }
254
255 pub fn elapsed(&self) -> std::time::Duration {
257 self.start_time.elapsed()
258 }
259}
260
261#[cfg(feature = "std")]
262impl Default for DebugLogger {
263 fn default() -> Self {
264 Self::new()
265 }
266}
267
268#[macro_export]
276macro_rules! log_trace {
277 ($logger:expr, $category:ident, $($arg:tt)*) => {
278 if let Some(ref mut logger) = $logger {
279 logger.trace($crate::debug::DebugCategory::$category, format!($($arg)*));
280 }
281 };
282}
283
284#[macro_export]
285macro_rules! log_debug {
286 ($logger:expr, $category:ident, $($arg:tt)*) => {
287 if let Some(ref mut logger) = $logger {
288 logger.debug($crate::debug::DebugCategory::$category, format!($($arg)*));
289 }
290 };
291}
292
293#[macro_export]
294macro_rules! log_info {
295 ($logger:expr, $category:ident, $($arg:tt)*) => {
296 if let Some(ref mut logger) = $logger {
297 logger.info($crate::debug::DebugCategory::$category, format!($($arg)*));
298 }
299 };
300}
301
302#[macro_export]
303macro_rules! log_warn {
304 ($logger:expr, $category:ident, $($arg:tt)*) => {
305 if let Some(ref mut logger) = $logger {
306 logger.warn($crate::debug::DebugCategory::$category, format!($($arg)*));
307 }
308 };
309}
310
311#[macro_export]
312macro_rules! log_error {
313 ($logger:expr, $category:ident, $($arg:tt)*) => {
314 if let Some(ref mut logger) = $logger {
315 logger.error($crate::debug::DebugCategory::$category, format!($($arg)*));
316 }
317 };
318}
319
320#[cfg(feature = "std")]
322pub type DebugLog = Option<DebugLogger>;
323
324#[cfg(feature = "std")]
326#[track_caller]
327pub fn debug_log(
328 logger: &mut Option<DebugLogger>,
329 level: DebugLevel,
330 category: DebugCategory,
331 message: impl Into<String>,
332) {
333 if let Some(ref mut l) = logger {
334 l.log(level, category, message);
335 }
336}
337
338#[cfg(feature = "std")]
340pub fn is_debug_enabled() -> bool {
341 std::env::var("AZUL_DEBUG").is_ok()
342}
343
344#[cfg(feature = "std")]
346pub fn get_debug_port() -> Option<u16> {
347 std::env::var("AZUL_DEBUG")
348 .ok()
349 .and_then(|s| s.parse().ok())
350}