MogensR commited on
Commit
e67da5f
Β·
1 Parent(s): d6467ce

Create utils/logger.py

Browse files
Files changed (1) hide show
  1. utils/logger.py +409 -0
utils/logger.py ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Logging Management Module
3
+ ========================
4
+
5
+ Professional logging system that integrates with existing LOGS/ folder
6
+ and provides structured, comprehensive logging for BackgroundFX Pro.
7
+
8
+ Features:
9
+ - Integration with existing LOGS/ folder
10
+ - Structured logging with different levels
11
+ - Performance tracking and metrics
12
+ - Error tracking and debugging
13
+ - Rotating log files
14
+ - Console and file output
15
+
16
+ Author: BackgroundFX Pro Team
17
+ License: MIT
18
+ """
19
+
20
+ import os
21
+ import sys
22
+ import logging
23
+ import logging.handlers
24
+ from typing import Optional, Dict, Any
25
+ from datetime import datetime
26
+ from pathlib import Path
27
+ import json
28
+ import traceback
29
+ from functools import wraps
30
+ import time
31
+
32
+ class ColoredFormatter(logging.Formatter):
33
+ """Custom formatter with colors for console output"""
34
+
35
+ # Color codes
36
+ COLORS = {
37
+ 'DEBUG': '\033[36m', # Cyan
38
+ 'INFO': '\033[32m', # Green
39
+ 'WARNING': '\033[33m', # Yellow
40
+ 'ERROR': '\033[31m', # Red
41
+ 'CRITICAL': '\033[35m', # Magenta
42
+ 'RESET': '\033[0m' # Reset
43
+ }
44
+
45
+ def format(self, record):
46
+ # Add color to levelname
47
+ if record.levelname in self.COLORS:
48
+ record.levelname = f"{self.COLORS[record.levelname]}{record.levelname}{self.COLORS['RESET']}"
49
+
50
+ return super().format(record)
51
+
52
+ class BackgroundFXLogger:
53
+ """Main logger class for BackgroundFX Pro"""
54
+
55
+ def __init__(self,
56
+ name: str = "BackgroundFX",
57
+ logs_dir: str = "LOGS",
58
+ level: int = logging.INFO,
59
+ console_output: bool = True,
60
+ file_output: bool = True):
61
+
62
+ self.name = name
63
+ self.logs_dir = Path(logs_dir)
64
+ self.level = level
65
+ self.console_output = console_output
66
+ self.file_output = file_output
67
+
68
+ # Create logs directory if it doesn't exist
69
+ self.logs_dir.mkdir(exist_ok=True)
70
+
71
+ # Initialize logger
72
+ self.logger = logging.getLogger(name)
73
+ self.logger.setLevel(level)
74
+
75
+ # Clear existing handlers to avoid duplicates
76
+ self.logger.handlers.clear()
77
+
78
+ # Setup handlers
79
+ self._setup_handlers()
80
+
81
+ # Performance tracking
82
+ self.performance_data = {}
83
+ self.start_times = {}
84
+
85
+ def _setup_handlers(self):
86
+ """Setup logging handlers for console and file output"""
87
+
88
+ # Console handler
89
+ if self.console_output:
90
+ console_handler = logging.StreamHandler(sys.stdout)
91
+ console_handler.setLevel(self.level)
92
+
93
+ # Colored formatter for console
94
+ console_formatter = ColoredFormatter(
95
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
96
+ datefmt='%Y-%m-%d %H:%M:%S'
97
+ )
98
+ console_handler.setFormatter(console_formatter)
99
+ self.logger.addHandler(console_handler)
100
+
101
+ # File handlers
102
+ if self.file_output:
103
+ # Main log file (rotating)
104
+ main_log_file = self.logs_dir / "backgroundfx.log"
105
+ file_handler = logging.handlers.RotatingFileHandler(
106
+ main_log_file,
107
+ maxBytes=10*1024*1024, # 10MB
108
+ backupCount=5
109
+ )
110
+ file_handler.setLevel(self.level)
111
+
112
+ # Plain formatter for file
113
+ file_formatter = logging.Formatter(
114
+ '%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s',
115
+ datefmt='%Y-%m-%d %H:%M:%S'
116
+ )
117
+ file_handler.setFormatter(file_formatter)
118
+ self.logger.addHandler(file_handler)
119
+
120
+ # Error-only log file
121
+ error_log_file = self.logs_dir / "errors.log"
122
+ error_handler = logging.handlers.RotatingFileHandler(
123
+ error_log_file,
124
+ maxBytes=5*1024*1024, # 5MB
125
+ backupCount=3
126
+ )
127
+ error_handler.setLevel(logging.ERROR)
128
+ error_handler.setFormatter(file_formatter)
129
+ self.logger.addHandler(error_handler)
130
+
131
+ # Performance log file (JSON format)
132
+ self.performance_log_file = self.logs_dir / "performance.json"
133
+
134
+ def debug(self, message: str, **kwargs):
135
+ """Log debug message"""
136
+ self.logger.debug(message, extra=kwargs)
137
+
138
+ def info(self, message: str, **kwargs):
139
+ """Log info message"""
140
+ self.logger.info(message, extra=kwargs)
141
+
142
+ def warning(self, message: str, **kwargs):
143
+ """Log warning message"""
144
+ self.logger.warning(message, extra=kwargs)
145
+
146
+ def error(self, message: str, exception: Optional[Exception] = None, **kwargs):
147
+ """Log error message with optional exception details"""
148
+ if exception:
149
+ message = f"{message} | Exception: {str(exception)}"
150
+ # Log full traceback to file
151
+ self.logger.error(f"{message}\n{traceback.format_exc()}", extra=kwargs)
152
+ else:
153
+ self.logger.error(message, extra=kwargs)
154
+
155
+ def critical(self, message: str, exception: Optional[Exception] = None, **kwargs):
156
+ """Log critical message"""
157
+ if exception:
158
+ message = f"{message} | Exception: {str(exception)}"
159
+ self.logger.critical(f"{message}\n{traceback.format_exc()}", extra=kwargs)
160
+ else:
161
+ self.logger.critical(message, extra=kwargs)
162
+
163
+ def log_processing_step(self, step_name: str, details: Dict[str, Any] = None):
164
+ """Log a processing step with details"""
165
+ details = details or {}
166
+ self.info(f"πŸ”„ Processing: {step_name}", **details)
167
+
168
+ def log_performance_metric(self, metric_name: str, value: float, unit: str = "", details: Dict = None):
169
+ """Log performance metric"""
170
+ details = details or {}
171
+ message = f"πŸ“Š {metric_name}: {value:.3f}{unit}"
172
+ self.info(message, **details)
173
+
174
+ # Store for performance analysis
175
+ timestamp = datetime.now().isoformat()
176
+ self.performance_data[timestamp] = {
177
+ 'metric': metric_name,
178
+ 'value': value,
179
+ 'unit': unit,
180
+ 'details': details
181
+ }
182
+
183
+ # Save to performance log
184
+ self._save_performance_data()
185
+
186
+ def log_model_status(self, model_name: str, status: str, details: Dict = None):
187
+ """Log model initialization/status"""
188
+ details = details or {}
189
+ if status == "initialized":
190
+ self.info(f"βœ… {model_name} initialized successfully", **details)
191
+ elif status == "failed":
192
+ self.error(f"❌ {model_name} initialization failed", **details)
193
+ elif status == "loading":
194
+ self.info(f"πŸ”„ Loading {model_name}...", **details)
195
+ else:
196
+ self.info(f"πŸ”§ {model_name}: {status}", **details)
197
+
198
+ def log_quality_metrics(self, frame_id: int, metrics: Dict[str, float]):
199
+ """Log quality assessment metrics"""
200
+ metric_str = " | ".join([f"{k}: {v:.3f}" for k, v in metrics.items()])
201
+ self.info(f"πŸ“Š Frame {frame_id} Quality: {metric_str}")
202
+
203
+ # Store detailed metrics
204
+ timestamp = datetime.now().isoformat()
205
+ self.performance_data[f"{timestamp}_quality_{frame_id}"] = {
206
+ 'type': 'quality_metrics',
207
+ 'frame_id': frame_id,
208
+ 'metrics': metrics
209
+ }
210
+
211
+ def log_video_processing(self, input_path: str, output_path: str,
212
+ frame_count: int, processing_time: float):
213
+ """Log video processing completion"""
214
+ fps = frame_count / max(processing_time, 0.001)
215
+ self.info(
216
+ f"🎬 Video processed: {frame_count} frames in {processing_time:.1f}s ({fps:.1f} FPS)",
217
+ input_path=input_path,
218
+ output_path=output_path,
219
+ frame_count=frame_count,
220
+ processing_time=processing_time,
221
+ fps=fps
222
+ )
223
+
224
+ def start_timer(self, operation_name: str):
225
+ """Start timing an operation"""
226
+ self.start_times[operation_name] = time.time()
227
+ self.debug(f"⏱️ Started timing: {operation_name}")
228
+
229
+ def end_timer(self, operation_name: str, log_result: bool = True) -> float:
230
+ """End timing an operation and optionally log result"""
231
+ if operation_name not in self.start_times:
232
+ self.warning(f"Timer '{operation_name}' was not started")
233
+ return 0.0
234
+
235
+ elapsed = time.time() - self.start_times[operation_name]
236
+ del self.start_times[operation_name]
237
+
238
+ if log_result:
239
+ self.log_performance_metric(f"{operation_name}_time", elapsed, "s")
240
+
241
+ return elapsed
242
+
243
+ def _save_performance_data(self):
244
+ """Save performance data to JSON file"""
245
+ try:
246
+ # Load existing data
247
+ existing_data = {}
248
+ if self.performance_log_file.exists():
249
+ with open(self.performance_log_file, 'r') as f:
250
+ existing_data = json.load(f)
251
+
252
+ # Merge with new data
253
+ existing_data.update(self.performance_data)
254
+
255
+ # Keep only last 1000 entries to prevent file from growing too large
256
+ if len(existing_data) > 1000:
257
+ sorted_keys = sorted(existing_data.keys())
258
+ keep_keys = sorted_keys[-1000:]
259
+ existing_data = {k: existing_data[k] for k in keep_keys}
260
+
261
+ # Save updated data
262
+ with open(self.performance_log_file, 'w') as f:
263
+ json.dump(existing_data, f, indent=2)
264
+
265
+ except Exception as e:
266
+ self.warning(f"Failed to save performance data: {e}")
267
+
268
+ def get_log_files(self) -> Dict[str, str]:
269
+ """Get paths to all log files"""
270
+ return {
271
+ 'main_log': str(self.logs_dir / "backgroundfx.log"),
272
+ 'error_log': str(self.logs_dir / "errors.log"),
273
+ 'performance_log': str(self.logs_dir / "performance.json"),
274
+ 'logs_directory': str(self.logs_dir)
275
+ }
276
+
277
+ def get_recent_logs(self, lines: int = 50) -> Dict[str, List[str]]:
278
+ """Get recent log entries"""
279
+ logs = {}
280
+
281
+ try:
282
+ # Main log
283
+ main_log_file = self.logs_dir / "backgroundfx.log"
284
+ if main_log_file.exists():
285
+ with open(main_log_file, 'r') as f:
286
+ logs['main'] = f.readlines()[-lines:]
287
+
288
+ # Error log
289
+ error_log_file = self.logs_dir / "errors.log"
290
+ if error_log_file.exists():
291
+ with open(error_log_file, 'r') as f:
292
+ logs['errors'] = f.readlines()[-lines:]
293
+
294
+ except Exception as e:
295
+ self.warning(f"Failed to read recent logs: {e}")
296
+
297
+ return logs
298
+
299
+ # Global logger instance
300
+ _global_logger: Optional[BackgroundFXLogger] = None
301
+
302
+ def setup_logging(logs_dir: str = "LOGS",
303
+ level: int = logging.INFO,
304
+ console_output: bool = True,
305
+ file_output: bool = True) -> BackgroundFXLogger:
306
+ """Setup global logging configuration"""
307
+ global _global_logger
308
+
309
+ if _global_logger is None:
310
+ _global_logger = BackgroundFXLogger(
311
+ name="BackgroundFX",
312
+ logs_dir=logs_dir,
313
+ level=level,
314
+ console_output=console_output,
315
+ file_output=file_output
316
+ )
317
+
318
+ return _global_logger
319
+
320
+ def get_logger(name: str = None) -> BackgroundFXLogger:
321
+ """Get logger instance"""
322
+ if _global_logger is None:
323
+ setup_logging()
324
+
325
+ if name and name != "BackgroundFX":
326
+ # Create module-specific logger that inherits from main logger
327
+ module_logger = BackgroundFXLogger(
328
+ name=name,
329
+ logs_dir=_global_logger.logs_dir,
330
+ level=_global_logger.level,
331
+ console_output=False, # Use main logger for console
332
+ file_output=True
333
+ )
334
+ return module_logger
335
+
336
+ return _global_logger
337
+
338
+ def log_function_call(logger: BackgroundFXLogger = None):
339
+ """Decorator to log function calls with timing"""
340
+ if logger is None:
341
+ logger = get_logger()
342
+
343
+ def decorator(func):
344
+ @wraps(func)
345
+ def wrapper(*args, **kwargs):
346
+ func_name = f"{func.__module__}.{func.__name__}"
347
+ logger.debug(f"πŸ”§ Calling: {func_name}")
348
+ logger.start_timer(func_name)
349
+
350
+ try:
351
+ result = func(*args, **kwargs)
352
+ elapsed = logger.end_timer(func_name, log_result=False)
353
+ logger.debug(f"βœ… Completed: {func_name} ({elapsed:.3f}s)")
354
+ return result
355
+
356
+ except Exception as e:
357
+ elapsed = logger.end_timer(func_name, log_result=False)
358
+ logger.error(f"❌ Failed: {func_name} ({elapsed:.3f}s)", exception=e)
359
+ raise
360
+
361
+ return wrapper
362
+ return decorator
363
+
364
+ def log_processing_pipeline():
365
+ """Decorator for logging processing pipeline steps"""
366
+ logger = get_logger()
367
+
368
+ def decorator(func):
369
+ @wraps(func)
370
+ def wrapper(*args, **kwargs):
371
+ step_name = func.__name__.replace('_', ' ').title()
372
+ logger.log_processing_step(step_name)
373
+
374
+ try:
375
+ result = func(*args, **kwargs)
376
+ logger.info(f"βœ… {step_name} completed successfully")
377
+ return result
378
+
379
+ except Exception as e:
380
+ logger.error(f"❌ {step_name} failed", exception=e)
381
+ raise
382
+
383
+ return wrapper
384
+ return decorator
385
+
386
+ # Convenience functions
387
+ def log_info(message: str, **kwargs):
388
+ """Quick info logging"""
389
+ get_logger().info(message, **kwargs)
390
+
391
+ def log_error(message: str, exception: Exception = None, **kwargs):
392
+ """Quick error logging"""
393
+ get_logger().error(message, exception=exception, **kwargs)
394
+
395
+ def log_warning(message: str, **kwargs):
396
+ """Quick warning logging"""
397
+ get_logger().warning(message, **kwargs)
398
+
399
+ def log_debug(message: str, **kwargs):
400
+ """Quick debug logging"""
401
+ get_logger().debug(message, **kwargs)
402
+
403
+ # Initialize logging on module import
404
+ if _global_logger is None:
405
+ try:
406
+ setup_logging()
407
+ log_info("βœ… Logging system initialized")
408
+ except Exception as e:
409
+ print(f"⚠️ Failed to initialize logging: {e}")