🎉 Verbesserte Backend-Funktionalität durch Windows-sichere Disk-Usage-Bestimmung, Uptime-Berechnung und Einführung eines Kiosk-Timers. Dokumentation aktualisiert und nicht mehr benötigte Dateien entfernt. 🧹
This commit is contained in:
@@ -1,467 +1,374 @@
|
||||
import logging
|
||||
import logging.handlers
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Windows-sichere Logging-Konfiguration für MYP Platform
|
||||
======================================================
|
||||
|
||||
Robuste Logging-Konfiguration mit Windows-spezifischen Fixes für File-Locking-Probleme.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import platform
|
||||
import socket
|
||||
from typing import Dict, Optional, Any
|
||||
import logging
|
||||
import threading
|
||||
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
|
||||
)
|
||||
from functools import wraps
|
||||
from typing import Optional, Dict, Any
|
||||
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
||||
|
||||
# Dictionary zur Speicherung der konfigurierten Logger
|
||||
_loggers: Dict[str, logging.Logger] = {}
|
||||
# ===== WINDOWS-SICHERE LOGGING-KLASSE =====
|
||||
|
||||
# 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'
|
||||
}
|
||||
class WindowsSafeRotatingFileHandler(RotatingFileHandler):
|
||||
"""
|
||||
Windows-sichere Implementierung von RotatingFileHandler.
|
||||
Behebt das WinError 32 Problem bei gleichzeitigen Log-Dateizugriffen.
|
||||
"""
|
||||
|
||||
def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False):
|
||||
# Verwende UTF-8 Encoding standardmäßig
|
||||
if encoding is None:
|
||||
encoding = 'utf-8'
|
||||
|
||||
# Windows-spezifische Konfiguration
|
||||
self._windows_safe_mode = os.name == 'nt'
|
||||
self._rotation_lock = threading.Lock()
|
||||
|
||||
super().__init__(filename, mode, maxBytes, backupCount, encoding, delay)
|
||||
|
||||
def doRollover(self):
|
||||
"""
|
||||
Windows-sichere Log-Rotation mit verbessertem Error-Handling.
|
||||
"""
|
||||
if not self._windows_safe_mode:
|
||||
# Normale Rotation für Unix-Systeme
|
||||
return super().doRollover()
|
||||
|
||||
# Windows-spezifische sichere Rotation
|
||||
with self._rotation_lock:
|
||||
try:
|
||||
if self.stream:
|
||||
self.stream.close()
|
||||
self.stream = None
|
||||
|
||||
# Warte kurz bevor Rotation versucht wird
|
||||
time.sleep(0.1)
|
||||
|
||||
# Versuche Rotation mehrmals mit exponentialem Backoff
|
||||
max_attempts = 5
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
# Rotation durchführen
|
||||
super().doRollover()
|
||||
break
|
||||
except (PermissionError, OSError) as e:
|
||||
if attempt == max_attempts - 1:
|
||||
# Bei letztem Versuch: Erstelle neue Log-Datei ohne Rotation
|
||||
print(f"WARNUNG: Log-Rotation fehlgeschlagen - erstelle neue Datei: {e}")
|
||||
self._create_new_log_file()
|
||||
break
|
||||
else:
|
||||
# Warte exponentiell länger bei jedem Versuch
|
||||
wait_time = 0.5 * (2 ** attempt)
|
||||
time.sleep(wait_time)
|
||||
|
||||
except Exception as e:
|
||||
print(f"KRITISCHER FEHLER bei Log-Rotation: {e}")
|
||||
# Notfall: Erstelle neue Log-Datei
|
||||
self._create_new_log_file()
|
||||
|
||||
def _create_new_log_file(self):
|
||||
"""
|
||||
Erstellt eine neue Log-Datei als Fallback wenn Rotation fehlschlägt.
|
||||
"""
|
||||
try:
|
||||
# Füge Timestamp zum Dateinamen hinzu
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
base_name, ext = os.path.splitext(self.baseFilename)
|
||||
new_filename = f"{base_name}_{timestamp}{ext}"
|
||||
|
||||
# Öffne neue Datei
|
||||
self.baseFilename = new_filename
|
||||
self.stream = self._open()
|
||||
|
||||
except Exception as e:
|
||||
print(f"NOTFALL: Kann keine neue Log-Datei erstellen: {e}")
|
||||
# Letzter Ausweg: Console-Logging
|
||||
self.stream = sys.stderr
|
||||
|
||||
# Emojis für verschiedene Log-Level und Kategorien
|
||||
LOG_EMOJIS = {
|
||||
'DEBUG': '🔍',
|
||||
'INFO': 'ℹ️',
|
||||
'WARNING': '⚠️',
|
||||
'ERROR': '❌',
|
||||
'CRITICAL': '🔥',
|
||||
'app': '🖥️',
|
||||
'scheduler': '⏱️',
|
||||
'auth': '🔐',
|
||||
'jobs': '🖨️',
|
||||
'printers': '🔧',
|
||||
'errors': '💥',
|
||||
'user': '👤',
|
||||
'kiosk': '📺'
|
||||
}
|
||||
# ===== GLOBALE LOGGING-KONFIGURATION =====
|
||||
|
||||
# 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]'
|
||||
}
|
||||
# Logger-Registry für Singleton-Pattern
|
||||
_logger_registry: Dict[str, logging.Logger] = {}
|
||||
_logging_initialized = False
|
||||
_init_lock = threading.Lock()
|
||||
|
||||
def safe_emoji(emoji: str) -> str:
|
||||
"""Gibt ein Emoji zurück oder einen ASCII-Fallback bei Encoding-Problemen."""
|
||||
def setup_logging(log_level: str = "INFO", base_log_dir: str = None) -> None:
|
||||
"""
|
||||
Initialisiert das zentrale Logging-System mit Windows-sicherer Konfiguration.
|
||||
|
||||
Args:
|
||||
log_level: Logging-Level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||
base_log_dir: Basis-Verzeichnis für Log-Dateien
|
||||
"""
|
||||
global _logging_initialized
|
||||
|
||||
with _init_lock:
|
||||
if _logging_initialized:
|
||||
return
|
||||
|
||||
try:
|
||||
# Bestimme Log-Verzeichnis
|
||||
if base_log_dir is None:
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
base_log_dir = os.path.join(current_dir, '..', 'logs')
|
||||
|
||||
# Erstelle Log-Verzeichnisse
|
||||
log_dirs = ['app', 'auth', 'jobs', 'printers', 'scheduler', 'errors']
|
||||
for log_dir in log_dirs:
|
||||
full_path = os.path.join(base_log_dir, log_dir)
|
||||
os.makedirs(full_path, exist_ok=True)
|
||||
|
||||
# Konfiguriere Root-Logger
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(getattr(logging, log_level.upper(), logging.INFO))
|
||||
|
||||
# Entferne existierende Handler um Duplikate zu vermeiden
|
||||
for handler in root_logger.handlers[:]:
|
||||
root_logger.removeHandler(handler)
|
||||
|
||||
# Console-Handler für kritische Meldungen
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(logging.WARNING)
|
||||
console_formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - [%(levelname)s] %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
console_handler.setFormatter(console_formatter)
|
||||
root_logger.addHandler(console_handler)
|
||||
|
||||
_logging_initialized = True
|
||||
print(f"✅ Logging-System erfolgreich initialisiert (Level: {log_level})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ KRITISCHER FEHLER bei Logging-Initialisierung: {e}")
|
||||
# Notfall-Konfiguration
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, log_level.upper(), logging.INFO),
|
||||
format='%(asctime)s - %(name)s - [%(levelname)s] - %(message)s',
|
||||
handlers=[logging.StreamHandler(sys.stdout)]
|
||||
)
|
||||
_logging_initialized = True
|
||||
|
||||
def get_logger(name: str, log_level: str = None) -> logging.Logger:
|
||||
"""
|
||||
Erstellt oder gibt einen konfigurierten Logger zurück.
|
||||
|
||||
Args:
|
||||
name: Name des Loggers (z.B. 'app', 'auth', 'jobs')
|
||||
log_level: Optionaler spezifischer Log-Level für diesen Logger
|
||||
|
||||
Returns:
|
||||
Konfigurierter Logger
|
||||
"""
|
||||
global _logger_registry
|
||||
|
||||
# Stelle sicher, dass Logging initialisiert ist
|
||||
if not _logging_initialized:
|
||||
setup_logging()
|
||||
|
||||
# Prüfe Registry für existierenden Logger
|
||||
if name in _logger_registry:
|
||||
return _logger_registry[name]
|
||||
|
||||
try:
|
||||
# Erste Priorität: Teste, ob das Emoji dargestellt werden kann
|
||||
test_encoding = sys.stdout.encoding or 'utf-8'
|
||||
emoji.encode(test_encoding)
|
||||
# Erstelle neuen Logger
|
||||
logger = logging.getLogger(name)
|
||||
|
||||
# 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, '[?]')
|
||||
# Setze spezifischen Level falls angegeben
|
||||
if log_level:
|
||||
logger.setLevel(getattr(logging, log_level.upper(), logging.INFO))
|
||||
|
||||
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
|
||||
# Erstelle File-Handler mit Windows-sicherer Rotation
|
||||
log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'logs', name)
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
if self.add_process_info and not hasattr(record, 'pid'):
|
||||
record.pid = self.pid
|
||||
log_file = os.path.join(log_dir, f'{name}.log')
|
||||
|
||||
# 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'
|
||||
# Windows-sicherer RotatingFileHandler
|
||||
file_handler = WindowsSafeRotatingFileHandler(
|
||||
log_file,
|
||||
maxBytes=10*1024*1024, # 10MB
|
||||
backupCount=5,
|
||||
encoding='utf-8'
|
||||
)
|
||||
error_handler.setLevel(logging.ERROR)
|
||||
error_handler.setFormatter(file_formatter)
|
||||
logger.addHandler(error_handler)
|
||||
|
||||
_loggers[category] = logger
|
||||
return logger
|
||||
|
||||
# Detaillierter Formatter für File-Logs
|
||||
file_formatter = logging.Formatter(
|
||||
'%(asctime)s - [%(name)s] %(name)s - [%(levelname)s] %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
file_handler.setFormatter(file_formatter)
|
||||
|
||||
# Handler hinzufügen
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Verhindere Propagation zu Root-Logger um Duplikate zu vermeiden
|
||||
logger.propagate = False
|
||||
|
||||
# In Registry speichern
|
||||
_logger_registry[name] = logger
|
||||
|
||||
return logger
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Fehler beim Erstellen des Loggers '{name}': {e}")
|
||||
# Fallback: Einfacher Logger ohne File-Handler
|
||||
fallback_logger = logging.getLogger(name)
|
||||
if name not in _logger_registry:
|
||||
_logger_registry[name] = fallback_logger
|
||||
return fallback_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)
|
||||
# ===== PERFORMANCE-MEASUREMENT DECORATOR =====
|
||||
|
||||
# Hilfsfunktionen für das Debugging
|
||||
|
||||
def debug_request(logger: logging.Logger, request):
|
||||
def measure_execution_time(logger: logging.Logger = None, task_name: str = "Task"):
|
||||
"""
|
||||
Loggt detaillierte Informationen über eine HTTP-Anfrage.
|
||||
Decorator zur Messung und Protokollierung der Ausführungszeit von Funktionen.
|
||||
|
||||
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)
|
||||
|
||||
logger: Logger-Instanz für die Ausgabe
|
||||
task_name: Bezeichnung der Aufgabe für die Logs
|
||||
|
||||
Returns:
|
||||
Dekorierte Funktion
|
||||
Decorator-Funktion
|
||||
"""
|
||||
from functools import wraps
|
||||
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
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__
|
||||
# Verwende provided Logger oder erstelle Standard-Logger
|
||||
log = logger or get_logger("performance")
|
||||
|
||||
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
|
||||
try:
|
||||
# Führe Funktion aus
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
# Berechne Ausführungszeit
|
||||
execution_time = (time.time() - start_time) * 1000 # in Millisekunden
|
||||
|
||||
# Protokolliere Erfolg
|
||||
log.info(f"✅ {task_name} '{func.__name__}' erfolgreich in {execution_time:.2f}ms")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
# Berechne Ausführungszeit auch bei Fehlern
|
||||
execution_time = (time.time() - start_time) * 1000
|
||||
|
||||
# Protokolliere Fehler
|
||||
log.error(f"❌ {task_name} '{func.__name__}' fehlgeschlagen nach {execution_time:.2f}ms: {str(e)}")
|
||||
|
||||
# Exception weiterleiten
|
||||
raise
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
# ===== STARTUP/DEBUG LOGGING =====
|
||||
|
||||
def log_startup_info():
|
||||
"""
|
||||
Protokolliert System-Startup-Informationen.
|
||||
"""
|
||||
startup_logger = get_logger("startup")
|
||||
|
||||
if func:
|
||||
return decorator(func)
|
||||
return decorator
|
||||
try:
|
||||
startup_logger.info("=" * 50)
|
||||
startup_logger.info("🚀 MYP Platform Backend wird gestartet...")
|
||||
startup_logger.info(f"🐍 Python Version: {sys.version}")
|
||||
startup_logger.info(f"💻 Betriebssystem: {os.name} ({sys.platform})")
|
||||
startup_logger.info(f"📁 Arbeitsverzeichnis: {os.getcwd()}")
|
||||
startup_logger.info(f"⏰ Startzeit: {datetime.now().isoformat()}")
|
||||
|
||||
# Windows-spezifische Informationen
|
||||
if os.name == 'nt':
|
||||
startup_logger.info("🪟 Windows-Modus: Aktiviert")
|
||||
startup_logger.info("🔒 Windows-sichere Log-Rotation: Aktiviert")
|
||||
|
||||
startup_logger.info("=" * 50)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Fehler beim Startup-Logging: {e}")
|
||||
|
||||
def debug_request(logger: logging.Logger, request) -> None:
|
||||
"""
|
||||
Detailliertes Request-Debugging.
|
||||
|
||||
Args:
|
||||
logger: Logger für die Ausgabe
|
||||
request: Flask Request-Objekt
|
||||
"""
|
||||
try:
|
||||
logger.debug(f"📨 REQUEST: {request.method} {request.path}")
|
||||
logger.debug(f"🌐 Remote-Adresse: {request.remote_addr}")
|
||||
logger.debug(f"🔤 Content-Type: {request.content_type}")
|
||||
|
||||
if request.args:
|
||||
logger.debug(f"❓ Query-Parameter: {dict(request.args)}")
|
||||
|
||||
if request.form and logger.level <= logging.DEBUG:
|
||||
# Filtere sensible Daten aus Form-Daten
|
||||
safe_form = {k: '***' if 'password' in k.lower() else v for k, v in request.form.items()}
|
||||
logger.debug(f"📝 Form-Daten: {safe_form}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Fehler beim Request-Debugging: {str(e)}")
|
||||
|
||||
def debug_response(logger: logging.Logger, response, duration_ms: Optional[float] = None) -> None:
|
||||
"""
|
||||
Detailliertes Response-Debugging.
|
||||
|
||||
Args:
|
||||
logger: Logger für die Ausgabe
|
||||
response: Flask Response-Objekt
|
||||
duration_ms: Optionale Ausführungszeit in Millisekunden
|
||||
"""
|
||||
try:
|
||||
status_emoji = "✅" if response.status_code < 400 else "❌" if response.status_code >= 500 else "⚠️"
|
||||
|
||||
log_msg = f"📤 RESPONSE: {status_emoji} {response.status_code}"
|
||||
|
||||
if duration_ms is not None:
|
||||
log_msg += f" ({duration_ms:.2f}ms)"
|
||||
|
||||
logger.debug(log_msg)
|
||||
logger.debug(f"📏 Content-Length: {response.content_length or 'Unbekannt'}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Fehler beim Response-Debugging: {str(e)}")
|
||||
|
||||
# ===== NOTFALL-LOGGING =====
|
||||
|
||||
def emergency_log(message: str, level: str = "ERROR") -> None:
|
||||
"""
|
||||
Notfall-Logging das auch funktioniert wenn das Hauptsystem fehlschlägt.
|
||||
|
||||
Args:
|
||||
message: Nachricht
|
||||
level: Log-Level
|
||||
"""
|
||||
try:
|
||||
# Versuche normales Logging
|
||||
logger = get_logger("emergency")
|
||||
getattr(logger, level.lower(), logger.error)(message)
|
||||
except:
|
||||
# Fallback zu Print
|
||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
print(f"[NOTFALL {timestamp}] [{level}] {message}")
|
||||
|
||||
# Auto-Initialisierung beim Import
|
||||
if __name__ != "__main__":
|
||||
try:
|
||||
setup_logging()
|
||||
except Exception as e:
|
||||
print(f"❌ Auto-Initialisierung des Logging-Systems fehlgeschlagen: {e}")
|
Reference in New Issue
Block a user