1use js_sys::Date;
4use serde::{Deserialize, Serialize};
5use std::string::{String, ToString};
6use std::sync::Mutex;
7use std::vec::Vec;
8use wasm_bindgen::prelude::*;
9
10#[wasm_bindgen]
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub enum LogLevel {
14 Error = 0,
15 Warn = 1,
16 Info = 2,
17 Debug = 3,
18 Trace = 4,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct PerformanceMetrics {
24 pub operation: String,
25 pub start_time: f64,
26 pub end_time: f64,
27 pub duration_ms: f64,
28 pub memory_before: usize,
29 pub memory_after: usize,
30 pub memory_delta: i64,
31 pub gpu_memory_used: usize,
32}
33
34#[wasm_bindgen]
36#[derive(Debug, Clone)]
37pub struct DebugConfig {
38 enabled: bool,
39 log_level: LogLevel,
40 console_output: bool,
41 performance_tracking: bool,
42 memory_tracking: bool,
43 gpu_profiling: bool,
44 max_log_entries: usize,
45}
46
47impl Default for DebugConfig {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53#[wasm_bindgen]
54impl DebugConfig {
55 #[wasm_bindgen(constructor)]
57 pub fn new() -> Self {
58 Self {
59 enabled: false,
60 log_level: LogLevel::Info,
61 console_output: true,
62 performance_tracking: false,
63 memory_tracking: false,
64 gpu_profiling: false,
65 max_log_entries: 1000,
66 }
67 }
68
69 pub fn enable(mut self) -> Self {
71 self.enabled = true;
72 self
73 }
74
75 pub fn disable(mut self) -> Self {
77 self.enabled = false;
78 self
79 }
80
81 pub fn set_log_level(mut self, level: LogLevel) -> Self {
83 self.log_level = level;
84 self
85 }
86
87 pub fn set_console_output(mut self, enabled: bool) -> Self {
89 self.console_output = enabled;
90 self
91 }
92
93 pub fn set_performance_tracking(mut self, enabled: bool) -> Self {
95 self.performance_tracking = enabled;
96 self
97 }
98
99 pub fn set_memory_tracking(mut self, enabled: bool) -> Self {
101 self.memory_tracking = enabled;
102 self
103 }
104
105 pub fn set_gpu_profiling(mut self, enabled: bool) -> Self {
107 self.gpu_profiling = enabled;
108 self
109 }
110
111 pub fn set_max_log_entries(mut self, max: usize) -> Self {
113 self.max_log_entries = max;
114 self
115 }
116
117 pub fn development() -> Self {
119 Self {
120 enabled: true,
121 log_level: LogLevel::Debug,
122 console_output: true,
123 performance_tracking: true,
124 memory_tracking: true,
125 gpu_profiling: true,
126 max_log_entries: 2000,
127 }
128 }
129
130 pub fn production() -> Self {
132 Self {
133 enabled: true,
134 log_level: LogLevel::Warn,
135 console_output: false,
136 performance_tracking: false,
137 memory_tracking: false,
138 gpu_profiling: false,
139 max_log_entries: 100,
140 }
141 }
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct LogEntry {
147 pub timestamp: f64,
148 pub level: LogLevel,
149 pub message: String,
150 pub category: String,
151 pub source_file: Option<String>,
152 pub source_line: Option<u32>,
153 pub metadata: Option<serde_json::Value>,
154}
155
156#[wasm_bindgen]
158#[derive(Clone)]
159pub struct DebugLogger {
160 config: DebugConfig,
161 log_entries: Vec<LogEntry>,
162 performance_metrics: Vec<PerformanceMetrics>,
163 active_timers: Vec<(String, f64)>,
164}
165
166#[wasm_bindgen]
167impl DebugLogger {
168 #[wasm_bindgen(constructor)]
170 pub fn new(config: DebugConfig) -> Self {
171 Self {
172 config,
173 log_entries: Vec::new(),
174 performance_metrics: Vec::new(),
175 active_timers: Vec::new(),
176 }
177 }
178
179 pub fn error(&mut self, message: &str, category: &str) {
181 self.log(LogLevel::Error, message, category, None, None, None);
182 }
183
184 pub fn warn(&mut self, message: &str, category: &str) {
186 self.log(LogLevel::Warn, message, category, None, None, None);
187 }
188
189 pub fn info(&mut self, message: &str, category: &str) {
191 self.log(LogLevel::Info, message, category, None, None, None);
192 }
193
194 pub fn debug(&mut self, message: &str, category: &str) {
196 self.log(LogLevel::Debug, message, category, None, None, None);
197 }
198
199 pub fn trace(&mut self, message: &str, category: &str) {
201 self.log(LogLevel::Trace, message, category, None, None, None);
202 }
203
204 pub fn start_timer(&mut self, operation: &str) {
206 if !self.config.enabled || !self.config.performance_tracking {
207 return;
208 }
209
210 let start_time = Date::now();
211 self.active_timers.push((operation.to_string(), start_time));
212
213 if self.config.console_output {
214 web_sys::console::time_with_label(&format!("โฑ๏ธ {}", operation));
215 }
216 }
217
218 pub fn end_timer(&mut self, operation: &str) -> Option<f64> {
220 if !self.config.enabled || !self.config.performance_tracking {
221 return None;
222 }
223
224 let end_time = Date::now();
225
226 if let Some(pos) = self.active_timers.iter().position(|(op, _)| op == operation) {
228 let (_, start_time) = self.active_timers.remove(pos);
229 let duration_ms = end_time - start_time;
230
231 let memory_before = 0; let memory_after = crate::get_wasm_memory_usage();
233 let memory_delta = memory_after as i64 - memory_before as i64;
234
235 let metrics = PerformanceMetrics {
236 operation: operation.to_string(),
237 start_time,
238 end_time,
239 duration_ms,
240 memory_before,
241 memory_after,
242 memory_delta,
243 gpu_memory_used: 0, };
245
246 self.performance_metrics.push(metrics);
247
248 if self.config.console_output {
249 web_sys::console::time_end_with_label(&format!("โฑ๏ธ {}", operation));
250 web_sys::console::log_1(
251 &format!("๐ {} completed in {:.2}ms", operation, duration_ms).into(),
252 );
253 }
254
255 self.trim_metrics();
256 Some(duration_ms)
257 } else {
258 if self.config.console_output {
259 web_sys::console::warn_1(
260 &format!("โ ๏ธ Timer '{}' was not started", operation).into(),
261 );
262 }
263 None
264 }
265 }
266
267 pub fn log_model_loading(&mut self, model_name: &str, size_bytes: usize, source: &str) {
269 let message = format!(
270 "Loading model '{}' ({:.2} MB) from {}",
271 model_name,
272 size_bytes as f64 / 1_048_576.0,
273 source
274 );
275 self.info(&message, "model_loading");
276 }
277
278 pub fn log_inference(&mut self, model_name: &str, input_shape: &[usize], device: &str) {
280 let message = format!(
281 "Running inference on model '{}' with input shape {:?} on {}",
282 model_name, input_shape, device
283 );
284 self.debug(&message, "inference");
285 }
286
287 pub fn log_memory_usage(&mut self, context: &str) {
289 if !self.config.enabled || !self.config.memory_tracking {
290 return;
291 }
292
293 let wasm_memory = crate::get_wasm_memory_usage();
294 let message = format!(
295 "{}: WASM memory usage: {:.2} MB",
296 context,
297 wasm_memory as f64 / 1_048_576.0
298 );
299 self.debug(&message, "memory");
300 }
301
302 pub fn log_gpu_operation(&mut self, operation: &str, buffer_size: usize) {
304 if !self.config.enabled || !self.config.gpu_profiling {
305 return;
306 }
307
308 let message = format!(
309 "GPU operation '{}' with buffer size: {:.2} MB",
310 operation,
311 buffer_size as f64 / 1_048_576.0
312 );
313 self.debug(&message, "gpu");
314 }
315
316 pub fn get_performance_summary(&self) -> String {
318 if self.performance_metrics.is_empty() {
319 return "No performance metrics available".to_string();
320 }
321
322 let total_operations = self.performance_metrics.len();
323 let total_time: f64 = self.performance_metrics.iter().map(|m| m.duration_ms).sum();
324 let avg_time = total_time / total_operations as f64;
325
326 let slowest = self
327 .performance_metrics
328 .iter()
329 .max_by(|a, b| {
330 a.duration_ms.partial_cmp(&b.duration_ms).unwrap_or(std::cmp::Ordering::Equal)
331 })
332 .expect("performance_metrics is not empty after length check");
333
334 format!(
335 "Performance Summary:\n\
336 - Total operations: {}\n\
337 - Total time: {:.2}ms\n\
338 - Average time: {:.2}ms\n\
339 - Slowest operation: {} ({:.2}ms)",
340 total_operations, total_time, avg_time, slowest.operation, slowest.duration_ms
341 )
342 }
343
344 pub fn export_logs(&self) -> String {
346 let mut output = String::new();
347 output.push_str("=== TrustformersWasm Debug Export ===\n\n");
348
349 output.push_str(&format!(
350 "Config: enabled={}, level={:?}, perf_tracking={}, mem_tracking={}, gpu_profiling={}\n\n",
351 self.config.enabled,
352 self.config.log_level,
353 self.config.performance_tracking,
354 self.config.memory_tracking,
355 self.config.gpu_profiling
356 ));
357
358 output.push_str("=== Log Entries ===\n");
359 for entry in &self.log_entries {
360 output.push_str(&format!(
361 "[{:.3}] {:?} [{}] {}\n",
362 entry.timestamp, entry.level, entry.category, entry.message
363 ));
364 }
365
366 output.push_str("\n=== Performance Metrics ===\n");
367 for metric in &self.performance_metrics {
368 output.push_str(&format!(
369 "{}: {:.2}ms (mem: {}โ{}, ฮ{})\n",
370 metric.operation,
371 metric.duration_ms,
372 metric.memory_before,
373 metric.memory_after,
374 metric.memory_delta
375 ));
376 }
377
378 output.push_str("\n=== Summary ===\n");
379 output.push_str(&self.get_performance_summary());
380
381 output
382 }
383
384 pub fn clear(&mut self) {
386 self.log_entries.clear();
387 self.performance_metrics.clear();
388 self.active_timers.clear();
389
390 if self.config.console_output {
391 web_sys::console::log_1(&"๐งน Debug logs cleared".into());
392 }
393 }
394
395 #[wasm_bindgen(getter)]
397 pub fn log_count(&self) -> usize {
398 self.log_entries.len()
399 }
400
401 #[wasm_bindgen(getter)]
403 pub fn metrics_count(&self) -> usize {
404 self.performance_metrics.len()
405 }
406
407 fn log(
410 &mut self,
411 level: LogLevel,
412 message: &str,
413 category: &str,
414 source_file: Option<String>,
415 source_line: Option<u32>,
416 metadata: Option<serde_json::Value>,
417 ) {
418 if !self.config.enabled || level as u8 > self.config.log_level as u8 {
419 return;
420 }
421
422 let entry = LogEntry {
423 timestamp: Date::now(),
424 level,
425 message: message.to_string(),
426 category: category.to_string(),
427 source_file,
428 source_line,
429 metadata,
430 };
431
432 self.log_entries.push(entry);
433
434 if self.config.console_output {
435 let level_prefix = match level {
436 LogLevel::Error => "โ",
437 LogLevel::Warn => "โ ๏ธ",
438 LogLevel::Info => "โน๏ธ",
439 LogLevel::Debug => "๐",
440 LogLevel::Trace => "๐",
441 };
442
443 let formatted_message = format!("{} [{}] {}", level_prefix, category, message);
444
445 match level {
446 LogLevel::Error => web_sys::console::error_1(&formatted_message.into()),
447 LogLevel::Warn => web_sys::console::warn_1(&formatted_message.into()),
448 LogLevel::Info => web_sys::console::info_1(&formatted_message.into()),
449 LogLevel::Debug | LogLevel::Trace => {
450 web_sys::console::log_1(&formatted_message.into())
451 },
452 }
453 }
454
455 self.trim_logs();
456 }
457
458 fn trim_logs(&mut self) {
459 if self.log_entries.len() > self.config.max_log_entries {
460 let excess = self.log_entries.len() - self.config.max_log_entries;
461 self.log_entries.drain(0..excess);
462 }
463 }
464
465 fn trim_metrics(&mut self) {
466 if self.performance_metrics.len() > self.config.max_log_entries {
467 let excess = self.performance_metrics.len() - self.config.max_log_entries;
468 self.performance_metrics.drain(0..excess);
469 }
470 }
471}
472
473static GLOBAL_LOGGER: Mutex<Option<DebugLogger>> = Mutex::new(None);
475
476#[wasm_bindgen]
478pub fn init_debug_logger(config: DebugConfig) {
479 if let Ok(mut logger) = GLOBAL_LOGGER.lock() {
480 *logger = Some(DebugLogger::new(config));
481 }
482}
483
484#[wasm_bindgen]
486pub fn get_debug_logger() -> Option<DebugLogger> {
487 GLOBAL_LOGGER.lock().ok().and_then(|logger| logger.clone())
488}
489
490#[macro_export]
492macro_rules! debug_log {
493 (error, $msg:expr) => {
494 if let Some(mut logger) = get_debug_logger() {
495 logger.error($msg, module_path!());
496 }
497 };
498 (warn, $msg:expr) => {
499 if let Some(mut logger) = get_debug_logger() {
500 logger.warn($msg, module_path!());
501 }
502 };
503 (info, $msg:expr) => {
504 if let Some(mut logger) = get_debug_logger() {
505 logger.info($msg, module_path!());
506 }
507 };
508 (debug, $msg:expr) => {
509 if let Some(mut logger) = get_debug_logger() {
510 logger.debug($msg, module_path!());
511 }
512 };
513 (trace, $msg:expr) => {
514 if let Some(mut logger) = get_debug_logger() {
515 logger.trace($msg, module_path!());
516 }
517 };
518}
519
520#[cfg(test)]
521mod tests {
522 use super::*;
523
524 #[test]
525 fn test_debug_config() {
526 let mut config = DebugConfig::new();
527 assert!(!config.enabled);
528
529 config = config.enable().set_log_level(LogLevel::Debug);
530 assert!(config.enabled);
531 assert_eq!(config.log_level as u8, LogLevel::Debug as u8);
532 }
533
534 #[test]
535 fn test_logger_creation() {
536 let config = DebugConfig::development();
537 let logger = DebugLogger::new(config);
538 assert_eq!(logger.log_count(), 0);
539 assert_eq!(logger.metrics_count(), 0);
540 }
541}