""" Debug-Utilities für die MYP-Anwendung Hilft bei der Diagnose und Behebung von Problemen in der Anwendung """ import os import sys import time import json import traceback import inspect from datetime import datetime from functools import wraps import logging from typing import Any, Dict, List, Optional, Tuple, Union, Callable from utils.logging_config import get_logger # Logger für dieses Modul erstellen debug_logger = get_logger("app") # Konstanten für Formatierung DEBUG_SEPARATOR = "=" * 60 DEBUG_SUBSEPARATOR = "-" * 60 class DebugLevel: """Enum für Debug-Level""" MINIMAL = 0 # Nur kritische Fehler NORMAL = 1 # Standardfehler und wichtige Informationen VERBOSE = 2 # Ausführliche Informationen TRACE = 3 # Vollständige Trace-Informationen # Aktuelles Debug-Level (kann zur Laufzeit geändert werden) CURRENT_DEBUG_LEVEL = DebugLevel.NORMAL def set_debug_level(level: int): """Setzt das aktuelle Debug-Level für die Anwendung""" global CURRENT_DEBUG_LEVEL CURRENT_DEBUG_LEVEL = level debug_logger.info(f"🔧 Debug-Level gesetzt auf: {level}") def debug_print(message: str, level: int = DebugLevel.NORMAL): """ Gibt eine Debug-Nachricht aus, wenn das aktuelle Debug-Level mindestens dem angegebenen entspricht. Args: message: Die auszugebende Nachricht level: Das erforderliche Debug-Level """ if level <= CURRENT_DEBUG_LEVEL: # Aktuelle Funktion und Zeilennummer ermitteln frame = inspect.currentframe().f_back func_name = frame.f_code.co_name file_name = os.path.basename(frame.f_code.co_filename) line_no = frame.f_lineno # Debug-Ausgabe formatieren timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3] debug_prefix = f"[DEBUG {timestamp} {file_name}:{func_name}:{line_no}]" # Verschiedene Levels mit unterschiedlichen Emojis markieren level_emoji = "🐞" if level >= DebugLevel.VERBOSE else "🔍" # Ausgabe print(f"{level_emoji} {debug_prefix} {message}") def debug_dump(obj: Any, name: str = "Object", level: int = DebugLevel.VERBOSE): """ Gibt den Inhalt eines Objekts für Debug-Zwecke aus. Args: obj: Das zu untersuchende Objekt name: Name des Objekts für die Ausgabe level: Das erforderliche Debug-Level """ if level > CURRENT_DEBUG_LEVEL: return debug_print(f"📦 Debug-Dump von {name}:", level) try: # Für dict-ähnliche Objekte if hasattr(obj, 'items'): for k, v in obj.items(): debug_print(f" {k}: {v}", level) # Für list/tuple-ähnliche Objekte elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes)): for i, item in enumerate(obj): debug_print(f" [{i}]: {item}", level) # Für einfache Objekte else: # Versuche als JSON zu formatieren try: json_str = json.dumps(obj, indent=2, default=str) debug_print(f" {json_str}", level) except: # Fallback auf einfache String-Darstellung debug_print(f" {obj}", level) except Exception as e: debug_print(f" Fehler beim Dump: {e}", level) def debug_trace(message: str = "Execution trace"): """ Gibt einen vollständigen Stack-Trace für Debug-Zwecke aus. Args: message: Begleitende Nachricht für den Trace """ if CURRENT_DEBUG_LEVEL < DebugLevel.TRACE: return debug_print(f"🔬 TRACE: {message}", DebugLevel.TRACE) debug_print(DEBUG_SUBSEPARATOR, DebugLevel.TRACE) # Stack-Trace sammeln stack = traceback.extract_stack() # Letzten Frame (diese Funktion) entfernen stack = stack[:-1] for frame in stack: file_name = os.path.basename(frame.filename) debug_print(f" {file_name}:{frame.lineno} - {frame.name}", DebugLevel.TRACE) debug_print(DEBUG_SUBSEPARATOR, DebugLevel.TRACE) def debug_function(func=None, level: int = DebugLevel.NORMAL): """ Dekorator, der Eingang und Ausgang einer Funktion sowie die Ausführungszeit loggt. Args: func: Die zu dekorierende Funktion level: Das erforderliche Debug-Level Returns: Dekorierte Funktion """ def decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): if CURRENT_DEBUG_LEVEL < level: return fn(*args, **kwargs) # Funktionsaufruf loggen arg_str = ", ".join([ *[str(arg) for arg in args], *[f"{k}={v}" for k, v in kwargs.items()] ]) if len(arg_str) > 100: arg_str = arg_str[:97] + "..." debug_print(f"▶️ Starte {fn.__name__}({arg_str})", level) # Ausführungszeit messen start_time = time.time() try: # Funktion ausführen result = fn(*args, **kwargs) # Ausführungszeit und Ergebnis loggen end_time = time.time() duration = (end_time - start_time) * 1000 result_str = str(result) if len(result_str) > 100: result_str = result_str[:97] + "..." duration_emoji = "⏱️" if duration < 1000 else "⏳" debug_print(f"{duration_emoji} {fn.__name__} beendet in {duration:.2f} ms", level) debug_print(f"📤 Ergebnis: {result_str}", level) return result except Exception as e: # Fehler loggen end_time = time.time() duration = (end_time - start_time) * 1000 debug_print(f"❌ {fn.__name__} fehlgeschlagen nach {duration:.2f} ms: {str(e)}", level) # Stack-Trace nur bei hohem Debug-Level if CURRENT_DEBUG_LEVEL >= DebugLevel.VERBOSE: debug_print(f"🔬 Stack-Trace für {fn.__name__}:", DebugLevel.VERBOSE) traceback_str = traceback.format_exc() for line in traceback_str.split('\n'): debug_print(f" {line}", DebugLevel.VERBOSE) # Exception weiterleiten raise return wrapper if func: return decorator(func) return decorator def debug_timer(name: str = None, level: int = DebugLevel.NORMAL): """ Kontext-Manager, der die Ausführungszeit eines Code-Blocks misst. Args: name: Name des Code-Blocks für die Ausgabe level: Das erforderliche Debug-Level Beispiel: with debug_timer("Datenbankabfrage"): result = db.execute_query() """ class Timer: def __init__(self, block_name, debug_level): self.block_name = block_name self.debug_level = debug_level self.start_time = None def __enter__(self): if CURRENT_DEBUG_LEVEL >= self.debug_level: self.start_time = time.time() block_name = self.block_name or "Code-Block" debug_print(f"⏱️ Starte Timer für: {block_name}", self.debug_level) return self def __exit__(self, exc_type, exc_val, exc_tb): if CURRENT_DEBUG_LEVEL >= self.debug_level and self.start_time: end_time = time.time() duration = (end_time - self.start_time) * 1000 block_name = self.block_name or "Code-Block" if exc_type: debug_print(f"❌ {block_name} fehlgeschlagen nach {duration:.2f} ms: {exc_val}", self.debug_level) else: duration_emoji = "⏱️" if duration < 1000 else "⏳" debug_print(f"{duration_emoji} {block_name} beendet in {duration:.2f} ms", self.debug_level) return Timer(name, level) def debug_exception_handler(logger: Optional[logging.Logger] = None): """ Dekorator, der Ausnahmen abfängt und Details loggt. Args: logger: Logger-Instanz für die Protokollierung (optional) Returns: Dekorierte Funktion """ def decorator(func): @wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: # Logger verwenden oder Fallback auf Standardausgabe log = logger or debug_logger # Ausnahmedetails loggen log.error(f"❌ Ausnahme in {func.__name__}: {str(e)}") # Stack-Trace bei hohem Debug-Level if CURRENT_DEBUG_LEVEL >= DebugLevel.VERBOSE: log.error("🔬 Stack-Trace:") traceback_str = traceback.format_exc() for line in traceback_str.split('\n'): if line.strip(): log.error(f" {line}") # Ausnahme weiterleiten raise return wrapper return decorator # Konsolen-Befehle für interaktives Debugging def dump_all_loggers(): """Gibt Informationen über alle konfigurierten Logger aus.""" import logging debug_print("📋 Konfigurierte Logger:", DebugLevel.VERBOSE) for name, logger in logging.Logger.manager.loggerDict.items(): if isinstance(logger, logging.Logger): level_name = logging.getLevelName(logger.level) handlers = len(logger.handlers) debug_print(f" {name}: Level={level_name}, Handlers={handlers}", DebugLevel.VERBOSE) def dump_environment(): """Gibt Umgebungsvariablen und Systeminformationen aus.""" debug_print("🌐 Umgebungsinformationen:", DebugLevel.VERBOSE) debug_print(f" Python: {sys.version}", DebugLevel.VERBOSE) debug_print(f" Plattform: {sys.platform}", DebugLevel.VERBOSE) debug_print(f" Arbeitsverzeichnis: {os.getcwd()}", DebugLevel.VERBOSE) debug_print("🔑 Umgebungsvariablen:", DebugLevel.VERBOSE) for key, value in sorted(os.environ.items()): # Passwörter und Secrets ausblenden if any(secret_key in key.lower() for secret_key in ['key', 'pass', 'secret', 'token', 'pwd']): value = "********" debug_print(f" {key}={value}", DebugLevel.VERBOSE) def memory_usage(obj: Any = None) -> Dict[str, Any]: """ Gibt Informationen über den Speicherverbrauch zurück. Args: obj: Optional ein Objekt, dessen Größe gemessen werden soll Returns: Dict mit Speicherverbrauchsinformationen """ import psutil import sys process = psutil.Process(os.getpid()) memory_info = process.memory_info() result = { "rss": memory_info.rss / (1024 * 1024), # MB "vms": memory_info.vms / (1024 * 1024), # MB "percent": process.memory_percent(), } if obj is not None: try: import sys result["object_size"] = sys.getsizeof(obj) / 1024 # KB except: result["object_size"] = "Nicht messbar" return result def log_memory_usage(obj_name: str = "Anwendung", obj: Any = None, logger: Optional[logging.Logger] = None): """ Loggt den aktuellen Speicherverbrauch. Args: obj_name: Name des Objekts oder der Anwendung obj: Optional ein Objekt, dessen Größe gemessen werden soll logger: Logger-Instanz für die Protokollierung (optional) """ log = logger or debug_logger memory = memory_usage(obj) log.info(f"📊 Speicherverbrauch von {obj_name}:") log.info(f" RSS: {memory['rss']:.2f} MB") log.info(f" VMS: {memory['vms']:.2f} MB") log.info(f" Prozent: {memory['percent']:.2f}%") if 'object_size' in memory: if isinstance(memory['object_size'], (int, float)): log.info(f" Objektgröße: {memory['object_size']:.2f} KB") else: log.info(f" Objektgröße: {memory['object_size']}") def profile_function(func): """ Dekorator, der eine Funktion profiliert und Statistiken ausgibt. Args: func: Die zu profilierende Funktion Returns: Dekorierte Funktion """ @wraps(func) def wrapper(*args, **kwargs): try: import cProfile import pstats import io # Profiler erstellen und Funktion ausführen profiler = cProfile.Profile() profiler.enable() result = func(*args, **kwargs) profiler.disable() # Statistiken sammeln s = io.StringIO() ps = pstats.Stats(profiler, stream=s).sort_stats('cumulative') ps.print_stats(20) # Top 20 Zeilen # Statistiken ausgeben debug_print(f"📊 Profiling-Ergebnis für {func.__name__}:", DebugLevel.VERBOSE) for line in s.getvalue().split('\n'): if line.strip(): debug_print(f" {line}", DebugLevel.VERBOSE) return result except ImportError: debug_print(f"⚠️ cProfile nicht verfügbar, Funktion wird ohne Profiling ausgeführt", DebugLevel.NORMAL) return func(*args, **kwargs) return wrapper