use alloc::string::String;
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum DebugLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
impl Default for DebugLevel {
fn default() -> Self {
DebugLevel::Debug
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum DebugCategory {
General,
Window,
EventLoop,
Input,
Layout,
Text,
DisplayList,
SceneBuilding,
Rendering,
Resources,
Callbacks,
Timer,
DebugServer,
Platform,
Icon,
}
impl Default for DebugCategory {
fn default() -> Self {
DebugCategory::General
}
}
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct LogMessage {
pub level: DebugLevel,
pub category: DebugCategory,
pub message: String,
pub location: String,
pub elapsed_us: u64,
pub window_id: Option<String>,
}
#[cfg(feature = "std")]
pub struct DebugLogger {
messages: Vec<LogMessage>,
start_time: std::time::Instant,
min_level: DebugLevel,
category_filter: Option<Vec<DebugCategory>>,
current_window_id: Option<String>,
}
#[cfg(feature = "std")]
impl DebugLogger {
pub fn new() -> Self {
Self {
messages: Vec::new(),
start_time: std::time::Instant::now(),
min_level: DebugLevel::Trace,
category_filter: None,
current_window_id: None,
}
}
pub fn with_min_level(min_level: DebugLevel) -> Self {
Self {
messages: Vec::new(),
start_time: std::time::Instant::now(),
min_level,
category_filter: None,
current_window_id: None,
}
}
pub fn with_categories(categories: Vec<DebugCategory>) -> Self {
Self {
messages: Vec::new(),
start_time: std::time::Instant::now(),
min_level: DebugLevel::Trace,
category_filter: Some(categories),
current_window_id: None,
}
}
pub fn set_window_context(&mut self, window_id: Option<String>) {
self.current_window_id = window_id;
}
#[track_caller]
pub fn log(&mut self, level: DebugLevel, category: DebugCategory, message: impl Into<String>) {
if level < self.min_level {
return;
}
if let Some(ref allowed) = self.category_filter {
if !allowed.contains(&category) {
return;
}
}
let location = core::panic::Location::caller();
self.messages.push(LogMessage {
level,
category,
message: message.into(),
location: format!("{}:{}", location.file(), location.line()),
elapsed_us: self.start_time.elapsed().as_micros() as u64,
window_id: self.current_window_id.clone(),
});
}
#[track_caller]
pub fn trace(&mut self, category: DebugCategory, message: impl Into<String>) {
self.log(DebugLevel::Trace, category, message);
}
#[track_caller]
pub fn debug(&mut self, category: DebugCategory, message: impl Into<String>) {
self.log(DebugLevel::Debug, category, message);
}
#[track_caller]
pub fn info(&mut self, category: DebugCategory, message: impl Into<String>) {
self.log(DebugLevel::Info, category, message);
}
#[track_caller]
pub fn warn(&mut self, category: DebugCategory, message: impl Into<String>) {
self.log(DebugLevel::Warn, category, message);
}
#[track_caller]
pub fn error(&mut self, category: DebugCategory, message: impl Into<String>) {
self.log(DebugLevel::Error, category, message);
}
pub fn take_messages(&mut self) -> Vec<LogMessage> {
core::mem::take(&mut self.messages)
}
pub fn messages(&self) -> &[LogMessage] {
&self.messages
}
pub fn len(&self) -> usize {
self.messages.len()
}
pub fn is_empty(&self) -> bool {
self.messages.is_empty()
}
pub fn elapsed(&self) -> std::time::Duration {
self.start_time.elapsed()
}
}
#[cfg(feature = "std")]
impl Default for DebugLogger {
fn default() -> Self {
Self::new()
}
}
#[macro_export]
macro_rules! log_trace {
($logger:expr, $category:ident, $($arg:tt)*) => {
if let Some(ref mut logger) = $logger {
logger.trace($crate::debug::DebugCategory::$category, format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! log_debug {
($logger:expr, $category:ident, $($arg:tt)*) => {
if let Some(ref mut logger) = $logger {
logger.debug($crate::debug::DebugCategory::$category, format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! log_info {
($logger:expr, $category:ident, $($arg:tt)*) => {
if let Some(ref mut logger) = $logger {
logger.info($crate::debug::DebugCategory::$category, format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! log_warn {
($logger:expr, $category:ident, $($arg:tt)*) => {
if let Some(ref mut logger) = $logger {
logger.warn($crate::debug::DebugCategory::$category, format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! log_error {
($logger:expr, $category:ident, $($arg:tt)*) => {
if let Some(ref mut logger) = $logger {
logger.error($crate::debug::DebugCategory::$category, format!($($arg)*));
}
};
}
#[cfg(feature = "std")]
pub type DebugLog = Option<DebugLogger>;
#[cfg(feature = "std")]
#[track_caller]
pub fn debug_log(
logger: &mut Option<DebugLogger>,
level: DebugLevel,
category: DebugCategory,
message: impl Into<String>,
) {
if let Some(ref mut l) = logger {
l.log(level, category, message);
}
}
#[cfg(feature = "std")]
pub fn is_debug_enabled() -> bool {
std::env::var("AZUL_DEBUG").is_ok()
}
#[cfg(feature = "std")]
pub fn get_debug_port() -> Option<u16> {
std::env::var("AZUL_DEBUG")
.ok()
.and_then(|s| s.parse().ok())
}