📝 Commit Details:

This commit is contained in:
2025-05-31 22:40:29 +02:00
parent 91b1886dde
commit df8fb197c0
14061 changed files with 997277 additions and 103548 deletions

View File

@@ -0,0 +1,467 @@
import logging
import logging.handlers
import os
import sys
import time
import platform
import socket
from typing import Dict, Optional, Any
from datetime import datetime
from config.settings import (
LOG_DIR, LOG_SUBDIRS, LOG_LEVEL, LOG_FORMAT, LOG_DATE_FORMAT,
get_log_file, ensure_log_directories
)
# Dictionary zur Speicherung der konfigurierten Logger
_loggers: Dict[str, logging.Logger] = {}
# ANSI-Farbcodes für Log-Level
ANSI_COLORS = {
'RESET': '\033[0m',
'BOLD': '\033[1m',
'BLACK': '\033[30m',
'RED': '\033[31m',
'GREEN': '\033[32m',
'YELLOW': '\033[33m',
'BLUE': '\033[34m',
'MAGENTA': '\033[35m',
'CYAN': '\033[36m',
'WHITE': '\033[37m',
'BG_RED': '\033[41m',
'BG_GREEN': '\033[42m',
'BG_YELLOW': '\033[43m',
'BG_BLUE': '\033[44m'
}
# Emojis für verschiedene Log-Level und Kategorien
LOG_EMOJIS = {
'DEBUG': '🔍',
'INFO': '',
'WARNING': '⚠️',
'ERROR': '',
'CRITICAL': '🔥',
'app': '🖥️',
'scheduler': '⏱️',
'auth': '🔐',
'jobs': '🖨️',
'printers': '🔧',
'errors': '💥',
'user': '👤',
'kiosk': '📺'
}
# ASCII-Fallback für Emojis bei Encoding-Problemen
EMOJI_FALLBACK = {
'🔍': '[DEBUG]',
'': '[INFO]',
'⚠️': '[WARN]',
'': '[ERROR]',
'🔥': '[CRIT]',
'🖥️': '[APP]',
'⏱️': '[SCHED]',
'🔐': '[AUTH]',
'🖨️': '[JOBS]',
'🔧': '[PRINT]',
'💥': '[ERR]',
'👤': '[USER]',
'📺': '[KIOSK]',
'🐞': '[BUG]',
'🚀': '[START]',
'📂': '[FOLDER]',
'📊': '[CHART]',
'💻': '[PC]',
'🌐': '[WEB]',
'📅': '[TIME]',
'📡': '[SIGNAL]',
'🧩': '[CONTENT]',
'📋': '[HEADER]',
'': '[OK]',
'📦': '[SIZE]'
}
def safe_emoji(emoji: str) -> str:
"""Gibt ein Emoji zurück oder einen ASCII-Fallback bei Encoding-Problemen."""
try:
# Erste Priorität: Teste, ob das Emoji dargestellt werden kann
test_encoding = sys.stdout.encoding or 'utf-8'
emoji.encode(test_encoding)
# Zweite Prüfung: Windows-spezifische cp1252-Codierung
if os.name == 'nt':
try:
emoji.encode('cp1252')
except UnicodeEncodeError:
# Wenn cp1252 fehlschlägt, verwende Fallback
return EMOJI_FALLBACK.get(emoji, '[?]')
return emoji
except (UnicodeEncodeError, LookupError, AttributeError):
return EMOJI_FALLBACK.get(emoji, '[?]')
# Prüfen, ob das Terminal ANSI-Farben unterstützt
def supports_color() -> bool:
"""Prüft, ob das Terminal ANSI-Farben unterstützt."""
if os.name == 'nt':
try:
import ctypes
kernel32 = ctypes.windll.kernel32
# Aktiviere VT100-Unterstützung unter Windows
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
# Setze Console-Output auf UTF-8 für bessere Emoji-Unterstützung
try:
kernel32.SetConsoleOutputCP(65001) # UTF-8
except:
pass
# Versuche UTF-8-Encoding für Emojis zu setzen
try:
import locale
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
except:
try:
# Fallback für deutsche Lokalisierung
locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')
except:
pass
return True
except:
return False
else:
return sys.stdout.isatty()
USE_COLORS = supports_color()
class ColoredFormatter(logging.Formatter):
"""Formatter, der Farben und Emojis für Logs hinzufügt."""
level_colors = {
'DEBUG': ANSI_COLORS['CYAN'],
'INFO': ANSI_COLORS['GREEN'],
'WARNING': ANSI_COLORS['YELLOW'],
'ERROR': ANSI_COLORS['RED'],
'CRITICAL': ANSI_COLORS['BG_RED'] + ANSI_COLORS['WHITE'] + ANSI_COLORS['BOLD']
}
def format(self, record):
try:
# Basis-Format erstellen
log_fmt = LOG_FORMAT
date_fmt = LOG_DATE_FORMAT
# Emoji dem Level und der Kategorie hinzufügen
level_name = record.levelname
category_name = record.name.split('.')[-1] if '.' in record.name else record.name
level_emoji = safe_emoji(LOG_EMOJIS.get(level_name, ''))
category_emoji = safe_emoji(LOG_EMOJIS.get(category_name, ''))
# Record-Objekt modifizieren (aber temporär)
original_levelname = record.levelname
original_name = record.name
# Emojis hinzufügen
record.levelname = f"{level_emoji} {level_name}"
record.name = f"{category_emoji} {category_name}"
# Farbe hinzufügen wenn unterstützt
if USE_COLORS:
level_color = self.level_colors.get(original_levelname, ANSI_COLORS['RESET'])
record.levelname = f"{level_color}{record.levelname}{ANSI_COLORS['RESET']}"
record.name = f"{ANSI_COLORS['BOLD']}{record.name}{ANSI_COLORS['RESET']}"
# Formatieren
result = super().format(record)
# Originale Werte wiederherstellen
record.levelname = original_levelname
record.name = original_name
return result
except (UnicodeEncodeError, UnicodeDecodeError, AttributeError) as e:
# Fallback bei Unicode-Problemen: Verwende nur ASCII-Text
original_levelname = record.levelname
original_name = record.name
# Emojis durch ASCII-Fallbacks ersetzen
level_fallback = EMOJI_FALLBACK.get(LOG_EMOJIS.get(original_levelname, ''), '[LOG]')
category_name = record.name.split('.')[-1] if '.' in record.name else record.name
category_fallback = EMOJI_FALLBACK.get(LOG_EMOJIS.get(category_name, ''), '[CAT]')
record.levelname = f"{level_fallback} {original_levelname}"
record.name = f"{category_fallback} {category_name}"
# Basis-Formatierung ohne Farben
result = super().format(record)
# Originale Werte wiederherstellen
record.levelname = original_levelname
record.name = original_name
return result
class DebugInfoFilter(logging.Filter):
"""Filter, der Debug-Informationen zu jedem Log-Eintrag hinzufügt."""
def __init__(self, add_hostname=True, add_process_info=True):
super().__init__()
self.add_hostname = add_hostname
self.add_process_info = add_process_info
self.hostname = socket.gethostname() if add_hostname else None
self.pid = os.getpid() if add_process_info else None
def filter(self, record):
# Debug-Informationen hinzufügen
if self.add_hostname and not hasattr(record, 'hostname'):
record.hostname = self.hostname
if self.add_process_info and not hasattr(record, 'pid'):
record.pid = self.pid
# Zusätzliche Infos für DEBUG-Level
if record.levelno == logging.DEBUG:
# Funktionsname und Zeilennummer hervorheben
if USE_COLORS:
record.funcName = f"{ANSI_COLORS['CYAN']}{record.funcName}{ANSI_COLORS['RESET']}"
record.lineno = f"{ANSI_COLORS['CYAN']}{record.lineno}{ANSI_COLORS['RESET']}"
return True
def setup_logging(debug_mode: bool = False):
"""
Initialisiert das Logging-System und erstellt alle erforderlichen Verzeichnisse.
Args:
debug_mode: Wenn True, wird das Log-Level auf DEBUG gesetzt
"""
ensure_log_directories()
# Log-Level festlegen
log_level = logging.DEBUG if debug_mode else getattr(logging, LOG_LEVEL)
# Root-Logger konfigurieren
root_logger = logging.getLogger()
root_logger.setLevel(log_level)
# Alle Handler entfernen
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
# Formatter erstellen (mit und ohne Farben)
colored_formatter = ColoredFormatter(LOG_FORMAT, LOG_DATE_FORMAT)
file_formatter = logging.Formatter(LOG_FORMAT, LOG_DATE_FORMAT)
# Filter für zusätzliche Debug-Informationen
debug_filter = DebugInfoFilter()
# Console Handler für alle Logs
console_handler = logging.StreamHandler()
console_handler.setLevel(log_level)
console_handler.setFormatter(colored_formatter)
console_handler.addFilter(debug_filter)
# Windows PowerShell UTF-8 Encoding-Unterstützung
if os.name == 'nt' and hasattr(console_handler.stream, 'reconfigure'):
try:
console_handler.stream.reconfigure(encoding='utf-8')
except:
pass
root_logger.addHandler(console_handler)
# File Handler für allgemeine App-Logs
app_log_file = get_log_file("app")
app_handler = logging.handlers.RotatingFileHandler(
app_log_file, maxBytes=10*1024*1024, backupCount=5, encoding='utf-8'
)
app_handler.setLevel(log_level)
app_handler.setFormatter(file_formatter)
root_logger.addHandler(app_handler)
# Wenn Debug-Modus aktiv, Konfiguration loggen
if debug_mode:
bug_emoji = safe_emoji("🐞")
root_logger.debug(f"{bug_emoji} Debug-Modus aktiviert - Ausführliche Logs werden generiert")
def get_logger(category: str) -> logging.Logger:
"""
Gibt einen konfigurierten Logger für eine bestimmte Kategorie zurück.
Args:
category: Log-Kategorie (app, scheduler, auth, jobs, printers, errors)
Returns:
logging.Logger: Konfigurierter Logger
"""
if category in _loggers:
return _loggers[category]
# Logger erstellen
logger = logging.getLogger(f"myp.{category}")
logger.setLevel(getattr(logging, LOG_LEVEL))
# Verhindere doppelte Logs durch Parent-Logger
logger.propagate = False
# Formatter erstellen (mit und ohne Farben)
colored_formatter = ColoredFormatter(LOG_FORMAT, LOG_DATE_FORMAT)
file_formatter = logging.Formatter(LOG_FORMAT, LOG_DATE_FORMAT)
# Filter für zusätzliche Debug-Informationen
debug_filter = DebugInfoFilter()
# Console Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(getattr(logging, LOG_LEVEL))
console_handler.setFormatter(colored_formatter)
console_handler.addFilter(debug_filter)
# Windows PowerShell UTF-8 Encoding-Unterstützung
if os.name == 'nt' and hasattr(console_handler.stream, 'reconfigure'):
try:
console_handler.stream.reconfigure(encoding='utf-8')
except:
pass
logger.addHandler(console_handler)
# File Handler für spezifische Kategorie
log_file = get_log_file(category)
file_handler = logging.handlers.RotatingFileHandler(
log_file, maxBytes=10*1024*1024, backupCount=5, encoding='utf-8'
)
file_handler.setLevel(getattr(logging, LOG_LEVEL))
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
# Error-Logs zusätzlich in errors.log schreiben
if category != "errors":
error_log_file = get_log_file("errors")
error_handler = logging.handlers.RotatingFileHandler(
error_log_file, maxBytes=10*1024*1024, backupCount=5, encoding='utf-8'
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(file_formatter)
logger.addHandler(error_handler)
_loggers[category] = logger
return logger
def log_startup_info():
"""Loggt Startup-Informationen."""
app_logger = get_logger("app")
rocket_emoji = safe_emoji("🚀")
folder_emoji = safe_emoji("📂")
chart_emoji = safe_emoji("📊")
computer_emoji = safe_emoji("💻")
globe_emoji = safe_emoji("🌐")
calendar_emoji = safe_emoji("📅")
app_logger.info("=" * 50)
app_logger.info(f"{rocket_emoji} MYP (Manage Your Printers) wird gestartet...")
app_logger.info(f"{folder_emoji} Log-Verzeichnis: {LOG_DIR}")
app_logger.info(f"{chart_emoji} Log-Level: {LOG_LEVEL}")
app_logger.info(f"{computer_emoji} Betriebssystem: {platform.system()} {platform.release()}")
app_logger.info(f"{globe_emoji} Hostname: {socket.gethostname()}")
app_logger.info(f"{calendar_emoji} Startzeit: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}")
app_logger.info("=" * 50)
# Hilfsfunktionen für das Debugging
def debug_request(logger: logging.Logger, request):
"""
Loggt detaillierte Informationen über eine HTTP-Anfrage.
Args:
logger: Logger-Instanz
request: Flask-Request-Objekt
"""
if logger.level > logging.DEBUG:
return
web_emoji = safe_emoji("🌐")
signal_emoji = safe_emoji("📡")
puzzle_emoji = safe_emoji("🧩")
clipboard_emoji = safe_emoji("📋")
search_emoji = safe_emoji("🔍")
logger.debug(f"{web_emoji} HTTP-Anfrage: {request.method} {request.path}")
logger.debug(f"{signal_emoji} Remote-Adresse: {request.remote_addr}")
logger.debug(f"{puzzle_emoji} Inhaltstyp: {request.content_type}")
# Nur relevante Headers ausgeben
important_headers = ['User-Agent', 'Referer', 'X-Forwarded-For', 'Authorization']
headers = {k: v for k, v in request.headers.items() if k in important_headers}
if headers:
logger.debug(f"{clipboard_emoji} Wichtige Headers: {headers}")
# Request-Parameter (max. 1000 Zeichen)
if request.args:
args_str = str(request.args)
if len(args_str) > 1000:
args_str = args_str[:997] + "..."
logger.debug(f"{search_emoji} URL-Parameter: {args_str}")
def debug_response(logger: logging.Logger, response, duration_ms: float = None):
"""
Loggt detaillierte Informationen über eine HTTP-Antwort.
Args:
logger: Logger-Instanz
response: Flask-Response-Objekt
duration_ms: Verarbeitungsdauer in Millisekunden (optional)
"""
if logger.level > logging.DEBUG:
return
status_emoji = safe_emoji("") if response.status_code < 400 else safe_emoji("")
logger.debug(f"{status_emoji} HTTP-Antwort: {response.status_code}")
if duration_ms is not None:
timer_emoji = safe_emoji("⏱️")
logger.debug(f"{timer_emoji} Verarbeitungsdauer: {duration_ms:.2f} ms")
content_length = response.content_length or 0
if content_length > 0:
size_str = f"{content_length / 1024:.1f} KB" if content_length > 1024 else f"{content_length} Bytes"
package_emoji = safe_emoji("📦")
logger.debug(f"{package_emoji} Antwortgröße: {size_str}")
def measure_execution_time(func=None, logger=None, task_name=None):
"""
Dekorator, der die Ausführungszeit einer Funktion misst und loggt.
Args:
func: Die zu dekorierende Funktion
logger: Logger-Instanz (optional)
task_name: Name der Aufgabe für das Logging (optional)
Returns:
Dekorierte Funktion
"""
from functools import wraps
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
start_time = time.time()
result = f(*args, **kwargs)
end_time = time.time()
duration_ms = (end_time - start_time) * 1000
name = task_name or f.__name__
if logger:
timer_emoji = safe_emoji('⏱️')
if duration_ms > 1000: # Länger als 1 Sekunde
logger.warning(f"{timer_emoji} Langsame Ausführung: {name} - {duration_ms:.2f} ms")
else:
logger.debug(f"{timer_emoji} Ausführungszeit: {name} - {duration_ms:.2f} ms")
return result
return wrapper
if func:
return decorator(func)
return decorator