manage-your-printer/utils/debug_utils.py
2025-06-04 10:03:22 +02:00

392 lines
14 KiB
Python

"""
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