debug.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. # -*- coding: utf-8 -*-
  2. """
  3. Debug-Ausgabefunktionen für Trixy.
  4. Stellt farbcodierte Ausgabefunktionen bereit, die zwischen
  5. Debug-Modus (stdout) und Logging umschalten können.
  6. """
  7. import sys
  8. import logging
  9. from datetime import datetime
  10. from typing import Any
  11. # ANSI-Farbcodes für Terminal-Ausgabe
  12. class Colors:
  13. """ANSI-Escape-Codes für farbige Terminal-Ausgabe."""
  14. RESET = "\033[0m"
  15. BOLD = "\033[1m"
  16. # Vordergrundfarben
  17. BLACK = "\033[30m"
  18. RED = "\033[31m"
  19. GREEN = "\033[32m"
  20. YELLOW = "\033[33m"
  21. BLUE = "\033[34m"
  22. MAGENTA = "\033[35m"
  23. CYAN = "\033[36m"
  24. WHITE = "\033[37m"
  25. # Helle Varianten
  26. BRIGHT_RED = "\033[91m"
  27. BRIGHT_GREEN = "\033[92m"
  28. BRIGHT_YELLOW = "\033[93m"
  29. BRIGHT_BLUE = "\033[94m"
  30. BRIGHT_MAGENTA = "\033[95m"
  31. BRIGHT_CYAN = "\033[96m"
  32. BRIGHT_WHITE = "\033[97m"
  33. # Globaler Debug-Modus-Flag
  34. _debug_mode: bool = False
  35. # Logger für nicht-Debug-Modus
  36. _logger = logging.getLogger("trixy")
  37. # File-Router für automatisches File-Logging (optional)
  38. _file_router = None
  39. def set_debug_mode(enabled: bool) -> None:
  40. """
  41. Aktiviert oder deaktiviert den Debug-Modus.
  42. Im Debug-Modus werden alle Ausgaben direkt auf stdout geschrieben.
  43. Andernfalls wird das Logging-System verwendet.
  44. Args:
  45. enabled: True für Debug-Modus (stdout), False für Logging
  46. """
  47. global _debug_mode
  48. _debug_mode = enabled
  49. def is_debug_mode() -> bool:
  50. """Gibt zurück, ob der Debug-Modus aktiv ist."""
  51. return _debug_mode
  52. def _setup_file_routing(router) -> None:
  53. """
  54. Setzt den File-Router fuer automatisches File-Logging.
  55. Args:
  56. router: FileLogRouter-Instanz oder None zum Deaktivieren
  57. """
  58. global _file_router
  59. _file_router = router
  60. def _format_message(level: str, color: str, message: str, *args: Any) -> str:
  61. """
  62. Formatiert eine Debug-Nachricht mit Zeitstempel und Farbe.
  63. Args:
  64. level: Log-Level-String (INFO, DEBUG, ERROR, WARN)
  65. color: ANSI-Farbcode
  66. message: Nachrichtenformat-String
  67. *args: Argumente für Format-String
  68. Returns:
  69. Formatierte Nachricht mit Farbe und Zeitstempel
  70. """
  71. timestamp = datetime.now().strftime("%H:%M:%S")
  72. if args:
  73. try:
  74. formatted_msg = message % args
  75. except TypeError:
  76. formatted_msg = message + " " + " ".join(str(a) for a in args)
  77. else:
  78. formatted_msg = message
  79. return f"{Colors.BRIGHT_WHITE}[{timestamp}]{Colors.RESET} {color}{Colors.BOLD}[{level}]{Colors.RESET} {formatted_msg}"
  80. def _output(level: str, color: str, log_level: int, message: str, *args: Any) -> None:
  81. """
  82. Gibt eine Nachricht aus (Debug-Modus) oder loggt sie.
  83. Args:
  84. level: Level-String für Anzeige
  85. color: ANSI-Farbcode
  86. log_level: logging-Level für Logger
  87. message: Nachricht oder Format-String
  88. *args: Argumente für Format-String
  89. """
  90. if _debug_mode:
  91. formatted = _format_message(level, color, message, *args)
  92. print(formatted, file=sys.stdout if log_level < logging.ERROR else sys.stderr)
  93. else:
  94. if args:
  95. try:
  96. _logger.log(log_level, message, *args)
  97. except TypeError:
  98. _logger.log(log_level, message + " " + " ".join(str(a) for a in args))
  99. else:
  100. _logger.log(log_level, message)
  101. # File-Logging (in beiden Modi, gesteuert durch file_log_level)
  102. if _file_router is not None:
  103. if args:
  104. try:
  105. file_msg = message % args
  106. except TypeError:
  107. file_msg = message + " " + " ".join(str(a) for a in args)
  108. else:
  109. file_msg = message
  110. _file_router.log(log_level, file_msg)
  111. def pinfo(message: str, *args: Any) -> None:
  112. """
  113. Gibt eine Info-Nachricht aus.
  114. Args:
  115. message: Nachricht oder Format-String
  116. *args: Argumente für Format-String
  117. """
  118. _output("INFO", Colors.BRIGHT_GREEN, logging.INFO, message, *args)
  119. def pdebug(message: str, *args: Any) -> None:
  120. """
  121. Gibt eine Debug-Nachricht aus.
  122. Args:
  123. message: Nachricht oder Format-String
  124. *args: Argumente für Format-String
  125. """
  126. _output("DEBUG", Colors.BRIGHT_CYAN, logging.DEBUG, message, *args)
  127. def perror(message: str, *args: Any) -> None:
  128. """
  129. Gibt eine Fehlermeldung aus.
  130. Args:
  131. message: Nachricht oder Format-String
  132. *args: Argumente für Format-String
  133. """
  134. _output("ERROR", Colors.BRIGHT_RED, logging.ERROR, message, *args)
  135. def pwarn(message: str, *args: Any) -> None:
  136. """
  137. Gibt eine Warnmeldung aus.
  138. Args:
  139. message: Nachricht oder Format-String
  140. *args: Argumente für Format-String
  141. """
  142. _output("WARN", Colors.BRIGHT_YELLOW, logging.WARNING, message, *args)
  143. def pprogress(label: str, current: int, total: int, suffix: str = "") -> None:
  144. """
  145. Zeigt eine sich in-place aktualisierende Progressbar an.
  146. Im Debug-Modus: Terminal-Progressbar mit \\r
  147. Sonst: Nur alle 5% eine Log-Zeile
  148. Args:
  149. label: Beschriftung der Progressbar
  150. current: Aktueller Fortschritt in Bytes
  151. total: Gesamtgroesse in Bytes
  152. suffix: Zusaetzliche Info (Geschwindigkeit, ETA)
  153. """
  154. if total <= 0:
  155. return
  156. percent = (current / total) * 100
  157. if _debug_mode:
  158. # Terminal-Breite ermitteln
  159. try:
  160. import shutil
  161. term_width = shutil.get_terminal_size().columns
  162. except Exception:
  163. term_width = 80
  164. # Label auf max. 25 Zeichen kuerzen
  165. short_label = label if len(label) <= 25 else "..." + label[-22:]
  166. # Progressbar-Laenge dynamisch (Minimum 15)
  167. # Aufbau: [HH:MM:SS] [PROGRESS] label: [bar] xx.x% suffix
  168. # ANSI-Codes zaehlen nicht zur sichtbaren Breite
  169. fixed_len = 10 + 1 + 10 + 1 + len(short_label) + 2 + 2 + 7 + 1 + len(suffix)
  170. bar_length = max(15, term_width - fixed_len)
  171. if bar_length > 30:
  172. bar_length = 30
  173. filled = int(bar_length * current / total)
  174. bar = "█" * filled + "░" * (bar_length - filled)
  175. timestamp = datetime.now().strftime("%H:%M:%S")
  176. line = (
  177. f"\r{Colors.BRIGHT_WHITE}[{timestamp}]{Colors.RESET} "
  178. f"{Colors.BRIGHT_BLUE}{Colors.BOLD}[PROGRESS]{Colors.RESET} "
  179. f"{short_label}: [{bar}] {percent:5.1f}% {suffix}"
  180. )
  181. # Zeile mit Leerzeichen auf Terminal-Breite auffuellen (alte Zeichen loeschen)
  182. visible_len = 10 + 1 + 10 + 1 + len(short_label) + 2 + bar_length + 2 + 7 + len(suffix)
  183. padding = max(0, term_width - visible_len)
  184. line += " " * padding
  185. print(line, end="", flush=True)
  186. # Bei 100%: Newline ausgeben
  187. if current >= total:
  188. print()
  189. else:
  190. # Logging-Modus: Nur alle 5%
  191. current_step = int(percent / 5)
  192. prev_percent = ((current - 8192) / total) * 100 if current > 8192 else -1
  193. prev_step = int(prev_percent / 5)
  194. if current >= total or current_step > prev_step:
  195. _logger.info(f"{label}: {percent:.0f}% {suffix}")