| """ |
| HuggingFace Spaces Debug Helper |
| ================================ |
| Adds real-time error logging and debug endpoints for easier debugging |
| without rebuilding Docker every time. |
| """ |
|
|
| import sys |
| import io |
| import traceback |
| import logging |
| from datetime import datetime |
| from pathlib import Path |
| from collections import deque |
| from threading import Lock |
|
|
| class DebugLogger: |
| """ |
| Captures all errors and prints them both to console and stores them |
| for viewing via web dashboard /debug endpoint. |
| """ |
| |
| def __init__(self, max_errors=100, log_file="/home/user/app/logs/debug.log"): |
| self.errors = deque(maxlen=max_errors) |
| self.lock = Lock() |
| self.log_file = Path(log_file) |
| self.log_file.parent.mkdir(parents=True, exist_ok=True) |
| |
| |
| self.file_handler = logging.FileHandler(self.log_file) |
| self.file_handler.setLevel(logging.ERROR) |
| formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') |
| self.file_handler.setFormatter(formatter) |
| |
| |
| logging.getLogger().addHandler(self.file_handler) |
| |
| def log_error(self, error_type, error_msg, tb_str=None): |
| """Log an error with timestamp and traceback""" |
| timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
| |
| error_entry = { |
| 'timestamp': timestamp, |
| 'type': error_type, |
| 'message': str(error_msg), |
| 'traceback': tb_str or '' |
| } |
| |
| with self.lock: |
| self.errors.append(error_entry) |
| |
| |
| print(f"\n{'='*80}") |
| print(f"🔴 ERROR CAPTURED: {timestamp}") |
| print(f"{'='*80}") |
| print(f"Type: {error_type}") |
| print(f"Message: {error_msg}") |
| if tb_str: |
| print(f"\nTraceback:") |
| print(tb_str) |
| print(f"{'='*80}\n") |
| |
| |
| with open(self.log_file, 'a') as f: |
| f.write(f"\n{'='*80}\n") |
| f.write(f"ERROR: {timestamp}\n") |
| f.write(f"Type: {error_type}\n") |
| f.write(f"Message: {error_msg}\n") |
| if tb_str: |
| f.write(f"Traceback:\n{tb_str}\n") |
| f.write(f"{'='*80}\n") |
| |
| def get_recent_errors(self, count=20): |
| """Get the most recent N errors""" |
| with self.lock: |
| return list(self.errors)[-count:] |
| |
| def get_all_errors(self): |
| """Get all stored errors""" |
| with self.lock: |
| return list(self.errors) |
| |
| def clear_errors(self): |
| """Clear all stored errors""" |
| with self.lock: |
| self.errors.clear() |
| print("✅ Debug error log cleared") |
| |
| def get_log_file_contents(self): |
| """Read the debug log file""" |
| try: |
| if self.log_file.exists(): |
| with open(self.log_file, 'r') as f: |
| return f.read() |
| return "No debug log file found" |
| except Exception as e: |
| return f"Error reading log file: {e}" |
|
|
| |
| debug_logger = DebugLogger() |
|
|
|
|
| def capture_exception(func): |
| """ |
| Decorator to capture and log exceptions from any function |
| """ |
| def wrapper(*args, **kwargs): |
| try: |
| return func(*args, **kwargs) |
| except Exception as e: |
| error_type = type(e).__name__ |
| error_msg = str(e) |
| tb_str = traceback.format_exc() |
| |
| debug_logger.log_error(error_type, error_msg, tb_str) |
| raise |
| |
| return wrapper |
|
|
|
|
| def add_debug_routes(app): |
| """ |
| Add debug routes to Flask app |
| |
| Usage: |
| from debug_helper import add_debug_routes |
| add_debug_routes(app) |
| """ |
| |
| @app.route('/debug') |
| def debug_page(): |
| """HTML page showing recent errors""" |
| errors = debug_logger.get_all_errors() |
| |
| html = """ |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>K1RL QUASAR Debug Dashboard</title> |
| <style> |
| body { |
| background: #0a0e27; |
| color: #00ff9d; |
| font-family: 'Courier New', monospace; |
| padding: 20px; |
| margin: 0; |
| } |
| .header { |
| text-align: center; |
| padding: 20px; |
| border-bottom: 2px solid #00ff9d; |
| margin-bottom: 20px; |
| } |
| .error-card { |
| background: #1a1f3a; |
| border: 1px solid #00ff9d; |
| border-radius: 5px; |
| padding: 15px; |
| margin-bottom: 15px; |
| box-shadow: 0 0 10px rgba(0, 255, 157, 0.2); |
| } |
| .error-header { |
| display: flex; |
| justify-content: space-between; |
| margin-bottom: 10px; |
| padding-bottom: 10px; |
| border-bottom: 1px solid #00ff9d; |
| } |
| .error-type { |
| color: #ff3366; |
| font-weight: bold; |
| } |
| .error-timestamp { |
| color: #888; |
| font-size: 0.9em; |
| } |
| .error-message { |
| color: #ffaa00; |
| margin: 10px 0; |
| } |
| .error-traceback { |
| background: #000; |
| padding: 10px; |
| border-radius: 3px; |
| overflow-x: auto; |
| font-size: 0.85em; |
| color: #00ff9d; |
| white-space: pre; |
| } |
| .controls { |
| text-align: center; |
| margin: 20px 0; |
| } |
| button { |
| background: #00ff9d; |
| color: #0a0e27; |
| border: none; |
| padding: 10px 20px; |
| border-radius: 5px; |
| cursor: pointer; |
| font-weight: bold; |
| margin: 0 5px; |
| } |
| button:hover { |
| background: #00cc7d; |
| } |
| .no-errors { |
| text-align: center; |
| padding: 40px; |
| color: #00ff9d; |
| font-size: 1.2em; |
| } |
| .stats { |
| text-align: center; |
| margin: 20px 0; |
| padding: 10px; |
| background: #1a1f3a; |
| border-radius: 5px; |
| } |
| </style> |
| <script> |
| function refresh() { |
| location.reload(); |
| } |
| function clearErrors() { |
| fetch('/debug/clear', {method: 'POST'}) |
| .then(() => location.reload()); |
| } |
| // Auto-refresh every 10 seconds |
| setTimeout(refresh, 10000); |
| </script> |
| </head> |
| <body> |
| <div class="header"> |
| <h1>🔴 K1RL QUASAR Debug Dashboard</h1> |
| <p>Real-time error monitoring for HuggingFace Spaces</p> |
| </div> |
| |
| <div class="stats"> |
| <strong>Total Errors Captured:</strong> """ + str(len(errors)) + """<br> |
| <small>Auto-refreshes every 10 seconds</small> |
| </div> |
| |
| <div class="controls"> |
| <button onclick="refresh()">🔄 Refresh Now</button> |
| <button onclick="clearErrors()">🗑️ Clear All Errors</button> |
| <button onclick="window.location.href='/debug/logs'">📄 View Full Log File</button> |
| <button onclick="window.location.href='/'">🏠 Back to Dashboard</button> |
| </div> |
| """ |
| |
| if not errors: |
| html += """ |
| <div class="no-errors"> |
| ✅ No errors captured yet<br> |
| <small>Errors will appear here when they occur</small> |
| </div> |
| """ |
| else: |
| |
| for error in reversed(errors): |
| html += f""" |
| <div class="error-card"> |
| <div class="error-header"> |
| <span class="error-type">{error['type']}</span> |
| <span class="error-timestamp">{error['timestamp']}</span> |
| </div> |
| <div class="error-message">{error['message']}</div> |
| """ |
| if error['traceback']: |
| html += f""" |
| <div class="error-traceback">{error['traceback']}</div> |
| """ |
| html += """ |
| </div> |
| """ |
| |
| html += """ |
| </body> |
| </html> |
| """ |
| return html |
| |
| @app.route('/debug/clear', methods=['POST']) |
| def clear_errors(): |
| """Clear all stored errors""" |
| debug_logger.clear_errors() |
| return {'status': 'success', 'message': 'Errors cleared'} |
| |
| @app.route('/debug/logs') |
| def view_log_file(): |
| """View the raw debug log file""" |
| log_contents = debug_logger.get_log_file_contents() |
| return f""" |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>Debug Log File</title> |
| <style> |
| body {{ |
| background: #0a0e27; |
| color: #00ff9d; |
| font-family: 'Courier New', monospace; |
| padding: 20px; |
| }} |
| pre {{ |
| background: #000; |
| padding: 20px; |
| border-radius: 5px; |
| overflow-x: auto; |
| }} |
| button {{ |
| background: #00ff9d; |
| color: #0a0e27; |
| border: none; |
| padding: 10px 20px; |
| border-radius: 5px; |
| cursor: pointer; |
| font-weight: bold; |
| margin: 10px 5px; |
| }} |
| </style> |
| </head> |
| <body> |
| <h1>📄 Debug Log File</h1> |
| <button onclick="window.location.href='/debug'">🔙 Back to Debug Dashboard</button> |
| <button onclick="location.reload()">🔄 Refresh</button> |
| <pre>{log_contents}</pre> |
| </body> |
| </html> |
| """ |
| |
| print("✅ Debug routes added: /debug, /debug/clear, /debug/logs") |
|
|
|
|
| def patch_quantum_meta_controller(): |
| """ |
| Monkey-patch the QuantumMetaController to use debug logging |
| Call this before creating the controller instance |
| """ |
| print("🔧 Patching QuantumMetaController for better error logging...") |
| |
| |
| import types |
| |
| def patched_run_with_cleanup(original_func): |
| def wrapper(*args, **kwargs): |
| try: |
| return original_func(*args, **kwargs) |
| except Exception as e: |
| error_type = type(e).__name__ |
| error_msg = str(e) |
| tb_str = traceback.format_exc() |
| |
| debug_logger.log_error(error_type, error_msg, tb_str) |
| |
| |
| if len(args) > 0 and isinstance(args[0], dict): |
| args[0]['error'] = e |
| |
| raise |
| return wrapper |
| |
| print("✅ QuantumMetaController patched for debug logging") |
|
|
|
|
| if __name__ == "__main__": |
| |
| print("Testing debug logger...") |
| |
| try: |
| raise ValueError("This is a test error") |
| except Exception as e: |
| debug_logger.log_error( |
| type(e).__name__, |
| str(e), |
| traceback.format_exc() |
| ) |
| |
| print(f"\nCaptured {len(debug_logger.get_all_errors())} errors") |
| print("\nRecent errors:") |
| for error in debug_logger.get_recent_errors(): |
| print(f" - {error['timestamp']}: {error['type']} - {error['message']}") |