🐛 Backend Optimization: Removed error monitoring configuration files and utilities, refactored logging configuration for improved performance. 🎉
This commit is contained in:
parent
5543e9ba5e
commit
d1a6281577
@ -222,13 +222,6 @@ def format_datetime_filter(value, format='%d.%m.%Y %H:%M'):
|
|||||||
setup_logging()
|
setup_logging()
|
||||||
log_startup_info()
|
log_startup_info()
|
||||||
|
|
||||||
# Error-Monitoring-System starten
|
|
||||||
try:
|
|
||||||
from utils.logging_config import setup_error_monitoring
|
|
||||||
setup_error_monitoring()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"⚠️ Error-Monitoring konnte nicht gestartet werden: {str(e)}")
|
|
||||||
|
|
||||||
# Logger für verschiedene Komponenten
|
# Logger für verschiedene Komponenten
|
||||||
app_logger = get_logger("app")
|
app_logger = get_logger("app")
|
||||||
auth_logger = get_logger("auth")
|
auth_logger = get_logger("auth")
|
||||||
@ -4867,53 +4860,3 @@ def get_printers():
|
|||||||
"error": f"Fehler beim Laden der Drucker: {str(e)}",
|
"error": f"Fehler beim Laden der Drucker: {str(e)}",
|
||||||
"printers": []
|
"printers": []
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
@app.route('/api/admin/error-monitor/stats', methods=['GET'])
|
|
||||||
@admin_required
|
|
||||||
def get_error_monitor_stats():
|
|
||||||
"""Gibt Statistiken des Error-Monitoring-Systems zurück."""
|
|
||||||
try:
|
|
||||||
from utils.error_monitor import get_error_monitor
|
|
||||||
|
|
||||||
monitor = get_error_monitor()
|
|
||||||
stats = monitor.get_stats()
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"stats": stats
|
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
app_logger.error(f"Fehler beim Abrufen der Error-Monitor-Statistiken: {str(e)}")
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error": str(e)
|
|
||||||
}), 500
|
|
||||||
|
|
||||||
@app.route('/api/admin/error-monitor/force-report', methods=['POST'])
|
|
||||||
@admin_required
|
|
||||||
def force_error_report():
|
|
||||||
"""Erzwingt das sofortige Senden eines Error-Reports (für Tests)."""
|
|
||||||
try:
|
|
||||||
from utils.error_monitor import get_error_monitor
|
|
||||||
|
|
||||||
monitor = get_error_monitor()
|
|
||||||
success = monitor.force_report()
|
|
||||||
|
|
||||||
if success:
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"message": "Error Report erfolgreich versendet"
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error": "Report konnte nicht versendet werden"
|
|
||||||
}), 500
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
app_logger.error(f"Fehler beim Erzwingen des Error-Reports: {str(e)}")
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error": str(e)
|
|
||||||
}), 500
|
|
@ -1,106 +0,0 @@
|
|||||||
# Mercedes-Benz MYP Platform - Error Monitoring Configuration
|
|
||||||
# ==========================================================
|
|
||||||
#
|
|
||||||
# Diese Datei enthält alle Umgebungsvariablen für das automatische
|
|
||||||
# Error-Monitoring-System mit E-Mail-Benachrichtigungen.
|
|
||||||
#
|
|
||||||
# Kopieren Sie diese Datei zu 'error_monitoring.env' und passen Sie
|
|
||||||
# die Werte an Ihre Umgebung an.
|
|
||||||
|
|
||||||
# ===== E-MAIL-KONFIGURATION =====
|
|
||||||
|
|
||||||
# SMTP-Server-Einstellungen
|
|
||||||
SMTP_HOST=smtp.gmail.com
|
|
||||||
SMTP_PORT=587
|
|
||||||
SMTP_USE_TLS=true
|
|
||||||
|
|
||||||
# SMTP-Anmeldedaten (für Gmail: App-Passwort verwenden!)
|
|
||||||
SMTP_USERNAME=myp-system@mercedes-benz.com
|
|
||||||
SMTP_PASSWORD=your-app-password-here
|
|
||||||
|
|
||||||
# E-Mail-Adressen
|
|
||||||
ALERT_EMAIL=admin@mercedes-benz.com
|
|
||||||
FROM_EMAIL=myp-system@mercedes-benz.com
|
|
||||||
FROM_NAME=MYP Platform Error Monitor
|
|
||||||
|
|
||||||
# ===== MONITORING-KONFIGURATION =====
|
|
||||||
|
|
||||||
# Report-Intervall in Sekunden (Standard: 3600 = 1 Stunde)
|
|
||||||
ERROR_REPORT_INTERVAL=3600
|
|
||||||
|
|
||||||
# Minimale Anzahl neuer Fehler für E-Mail-Versand
|
|
||||||
MIN_ERRORS_FOR_EMAIL=1
|
|
||||||
|
|
||||||
# Maximale Anzahl Fehler pro Report (verhindert zu lange E-Mails)
|
|
||||||
MAX_ERRORS_PER_REPORT=50
|
|
||||||
|
|
||||||
# ===== ERWEITERTE EINSTELLUNGEN =====
|
|
||||||
|
|
||||||
# Speicherort der Error-Monitor-Datenbank (relativ zu app-Verzeichnis)
|
|
||||||
ERROR_DB_PATH=database/error_monitor.db
|
|
||||||
|
|
||||||
# Log-Level für Error-Monitor selbst (WARNING, ERROR, CRITICAL)
|
|
||||||
ERROR_MONITOR_LOG_LEVEL=WARNING
|
|
||||||
|
|
||||||
# ===== BEISPIEL-KONFIGURATIONEN =====
|
|
||||||
|
|
||||||
# Für Development (alle 5 Minuten, ab 1 Fehler)
|
|
||||||
# ERROR_REPORT_INTERVAL=300
|
|
||||||
# MIN_ERRORS_FOR_EMAIL=1
|
|
||||||
|
|
||||||
# Für Production (alle 2 Stunden, ab 3 Fehlern)
|
|
||||||
# ERROR_REPORT_INTERVAL=7200
|
|
||||||
# MIN_ERRORS_FOR_EMAIL=3
|
|
||||||
|
|
||||||
# Für Testing (sofortige Reports)
|
|
||||||
# ERROR_REPORT_INTERVAL=60
|
|
||||||
# MIN_ERRORS_FOR_EMAIL=1
|
|
||||||
|
|
||||||
# ===== SMTP-PROVIDER-BEISPIELE =====
|
|
||||||
|
|
||||||
# Gmail (App-Passwort erforderlich)
|
|
||||||
# SMTP_HOST=smtp.gmail.com
|
|
||||||
# SMTP_PORT=587
|
|
||||||
# SMTP_USE_TLS=true
|
|
||||||
|
|
||||||
# Outlook/Hotmail
|
|
||||||
# SMTP_HOST=smtp-mail.outlook.com
|
|
||||||
# SMTP_PORT=587
|
|
||||||
# SMTP_USE_TLS=true
|
|
||||||
|
|
||||||
# Yahoo
|
|
||||||
# SMTP_HOST=smtp.mail.yahoo.com
|
|
||||||
# SMTP_PORT=587
|
|
||||||
# SMTP_USE_TLS=true
|
|
||||||
|
|
||||||
# Mercedes-Benz Corporate (Beispiel)
|
|
||||||
# SMTP_HOST=mail.mercedes-benz.com
|
|
||||||
# SMTP_PORT=587
|
|
||||||
# SMTP_USE_TLS=true
|
|
||||||
|
|
||||||
# ===== SICHERHEITSHINWEISE =====
|
|
||||||
|
|
||||||
# 1. Verwenden Sie App-Passwörter anstatt normale Passwörter
|
|
||||||
# 2. Diese Datei sollte NIEMALS in Git committed werden
|
|
||||||
# 3. Setzen Sie restriktive Dateiberechtigungen (600)
|
|
||||||
# 4. Verwenden Sie separate E-Mail-Accounts für System-Alerts
|
|
||||||
# 5. Testen Sie die SMTP-Konfiguration bevor Sie sie produktiv setzen
|
|
||||||
|
|
||||||
# ===== INSTALLATION =====
|
|
||||||
|
|
||||||
# 1. Kopieren Sie diese Datei:
|
|
||||||
# cp config/error_monitoring.env.example config/error_monitoring.env
|
|
||||||
#
|
|
||||||
# 2. Passen Sie die Werte an Ihre Umgebung an
|
|
||||||
#
|
|
||||||
# 3. Laden Sie die Umgebungsvariablen:
|
|
||||||
# source config/error_monitoring.env
|
|
||||||
#
|
|
||||||
# 4. Oder verwenden Sie python-dotenv:
|
|
||||||
# pip install python-dotenv
|
|
||||||
# # Dann in Ihrer app.py: load_dotenv('config/error_monitoring.env')
|
|
||||||
|
|
||||||
# ===== TESTING =====
|
|
||||||
|
|
||||||
# Zum Testen des Error-Monitoring-Systems:
|
|
||||||
# python -c "from utils.error_monitor import test_error_monitoring; test_error_monitoring()"
|
|
@ -1,593 +0,0 @@
|
|||||||
"""
|
|
||||||
Mercedes-Benz MYP Platform - Live Error Monitoring System
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
Custom Logging Handler der ERROR-Level Logs abfängt und gruppiert.
|
|
||||||
Automatischer E-Mail-Versand von Fehlerzusammenfassungen.
|
|
||||||
|
|
||||||
Features:
|
|
||||||
- Custom LoggingHandler für ERROR-Level-Abfang
|
|
||||||
- Gruppierung nach Exception-Typ und Stacktrace-Hash
|
|
||||||
- Stündliche Markdown-Tabellen-Berichte
|
|
||||||
- SMTP-Versand nur bei neuen Fehlern
|
|
||||||
- Thread-sichere Implementierung
|
|
||||||
- Umgebungsvariablen-Konfiguration
|
|
||||||
|
|
||||||
Autor: MYP Platform Team
|
|
||||||
Datum: 30.05.2025
|
|
||||||
Version: 1.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import hashlib
|
|
||||||
import smtplib
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
from collections import defaultdict, Counter
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from email.mime.text import MIMEText
|
|
||||||
from email.mime.multipart import MIMEMultipart
|
|
||||||
from typing import Dict, List, Optional, Tuple
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import sqlite3
|
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
class ErrorMonitorHandler(logging.Handler):
|
|
||||||
"""
|
|
||||||
Custom Logging Handler der ERROR-Level Logs abfängt und für
|
|
||||||
automatische E-Mail-Berichte sammelt.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, monitor_instance):
|
|
||||||
super().__init__(level=logging.ERROR)
|
|
||||||
self.monitor = monitor_instance
|
|
||||||
self.setFormatter(logging.Formatter(
|
|
||||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
||||||
))
|
|
||||||
|
|
||||||
def emit(self, record):
|
|
||||||
"""
|
|
||||||
Verarbeitet ERROR-Level Log-Records und leitet sie an den ErrorMonitor weiter.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Exception-Informationen extrahieren
|
|
||||||
exc_type = None
|
|
||||||
exc_message = ""
|
|
||||||
stack_trace = ""
|
|
||||||
|
|
||||||
if record.exc_info:
|
|
||||||
exc_type = record.exc_info[0].__name__ if record.exc_info[0] else "UnknownException"
|
|
||||||
exc_message = str(record.exc_info[1]) if record.exc_info[1] else record.getMessage()
|
|
||||||
stack_trace = ''.join(traceback.format_exception(*record.exc_info))
|
|
||||||
else:
|
|
||||||
# Fallback wenn keine exc_info vorhanden
|
|
||||||
exc_type = "LoggedError"
|
|
||||||
exc_message = record.getMessage()
|
|
||||||
stack_trace = f"{record.name}:{record.lineno} - {exc_message}"
|
|
||||||
|
|
||||||
# Error an Monitor weiterleiten
|
|
||||||
self.monitor.add_error(
|
|
||||||
exc_type=exc_type,
|
|
||||||
message=exc_message,
|
|
||||||
stack_trace=stack_trace,
|
|
||||||
logger_name=record.name,
|
|
||||||
level=record.levelname,
|
|
||||||
timestamp=datetime.fromtimestamp(record.created)
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# Fehler beim Fehler-Handling - nur in Konsole ausgeben um Loop zu vermeiden
|
|
||||||
print(f"❌ Error in ErrorMonitorHandler: {str(e)}")
|
|
||||||
|
|
||||||
class ErrorMonitor:
|
|
||||||
"""
|
|
||||||
Haupt-Error-Monitoring-Klasse die Fehler sammelt, gruppiert und E-Mail-Berichte sendet.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
# Konfiguration aus Umgebungsvariablen
|
|
||||||
self.smtp_host = os.getenv('SMTP_HOST', 'localhost')
|
|
||||||
self.smtp_port = int(os.getenv('SMTP_PORT', '587'))
|
|
||||||
self.smtp_username = os.getenv('SMTP_USERNAME', '')
|
|
||||||
self.smtp_password = os.getenv('SMTP_PASSWORD', '')
|
|
||||||
self.smtp_use_tls = os.getenv('SMTP_USE_TLS', 'true').lower() == 'true'
|
|
||||||
|
|
||||||
self.alert_email = os.getenv('ALERT_EMAIL', 'admin@mercedes-benz.com')
|
|
||||||
self.from_email = os.getenv('FROM_EMAIL', 'myp-system@mercedes-benz.com')
|
|
||||||
self.from_name = os.getenv('FROM_NAME', 'MYP Platform Error Monitor')
|
|
||||||
|
|
||||||
# Monitoring-Konfiguration
|
|
||||||
self.report_interval = int(os.getenv('ERROR_REPORT_INTERVAL', '3600')) # Standard: 1 Stunde
|
|
||||||
self.max_errors_per_report = int(os.getenv('MAX_ERRORS_PER_REPORT', '50'))
|
|
||||||
self.min_errors_for_email = int(os.getenv('MIN_ERRORS_FOR_EMAIL', '1'))
|
|
||||||
|
|
||||||
# Thread-sichere Datenstrukturen
|
|
||||||
self._lock = threading.RLock()
|
|
||||||
self._errors = defaultdict(list) # {error_hash: [error_instances]}
|
|
||||||
self._error_counts = Counter() # {error_hash: count}
|
|
||||||
self._last_report_time = datetime.now()
|
|
||||||
self._sent_hashes = set() # Bereits gesendete Error-Hashes
|
|
||||||
|
|
||||||
# Persistente Speicherung
|
|
||||||
self.db_path = os.path.join(os.path.dirname(__file__), '..', 'database', 'error_monitor.db')
|
|
||||||
self._init_database()
|
|
||||||
self._load_sent_hashes()
|
|
||||||
|
|
||||||
# Background-Thread für periodische Reports
|
|
||||||
self._monitoring = True
|
|
||||||
self._monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
|
|
||||||
self._monitor_thread.start()
|
|
||||||
|
|
||||||
# Logger für dieses System (niedriger Level um Loops zu vermeiden)
|
|
||||||
self.logger = logging.getLogger('error_monitor')
|
|
||||||
self.logger.setLevel(logging.WARNING) # Nur WARNING und höher
|
|
||||||
|
|
||||||
# Custom Handler registrieren
|
|
||||||
self.handler = ErrorMonitorHandler(self)
|
|
||||||
|
|
||||||
print(f"✅ ErrorMonitor gestartet - Reports alle {self.report_interval}s an {self.alert_email}")
|
|
||||||
|
|
||||||
def _init_database(self):
|
|
||||||
"""Initialisiert die SQLite-Datenbank für persistente Speicherung."""
|
|
||||||
try:
|
|
||||||
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
|
|
||||||
|
|
||||||
with self._get_db_connection() as conn:
|
|
||||||
conn.execute('''
|
|
||||||
CREATE TABLE IF NOT EXISTS sent_errors (
|
|
||||||
error_hash TEXT PRIMARY KEY,
|
|
||||||
first_sent TIMESTAMP,
|
|
||||||
last_sent TIMESTAMP,
|
|
||||||
send_count INTEGER DEFAULT 1
|
|
||||||
)
|
|
||||||
''')
|
|
||||||
|
|
||||||
conn.execute('''
|
|
||||||
CREATE TABLE IF NOT EXISTS error_history (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
error_hash TEXT,
|
|
||||||
exc_type TEXT,
|
|
||||||
message TEXT,
|
|
||||||
logger_name TEXT,
|
|
||||||
timestamp TIMESTAMP,
|
|
||||||
INDEX(error_hash),
|
|
||||||
INDEX(timestamp)
|
|
||||||
)
|
|
||||||
''')
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Database init error: {str(e)}")
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def _get_db_connection(self):
|
|
||||||
"""Context Manager für sichere Datenbankverbindungen."""
|
|
||||||
conn = None
|
|
||||||
try:
|
|
||||||
conn = sqlite3.connect(self.db_path, timeout=10.0)
|
|
||||||
conn.row_factory = sqlite3.Row
|
|
||||||
yield conn
|
|
||||||
finally:
|
|
||||||
if conn:
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
def _load_sent_hashes(self):
|
|
||||||
"""Lädt bereits gesendete Error-Hashes aus der Datenbank."""
|
|
||||||
try:
|
|
||||||
with self._get_db_connection() as conn:
|
|
||||||
cursor = conn.execute(
|
|
||||||
'SELECT error_hash FROM sent_errors WHERE last_sent > ?',
|
|
||||||
(datetime.now() - timedelta(days=7),) # Nur letzte 7 Tage laden
|
|
||||||
)
|
|
||||||
self._sent_hashes = {row['error_hash'] for row in cursor.fetchall()}
|
|
||||||
|
|
||||||
print(f"📋 {len(self._sent_hashes)} bereits gesendete Fehler-Hashes geladen")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Error loading sent hashes: {str(e)}")
|
|
||||||
self._sent_hashes = set()
|
|
||||||
|
|
||||||
def _generate_error_hash(self, exc_type: str, message: str, stack_trace: str) -> str:
|
|
||||||
"""
|
|
||||||
Generiert einen Hash für einen Fehler basierend auf Typ, Message und Stacktrace.
|
|
||||||
Ähnliche Fehler bekommen den gleichen Hash für Gruppierung.
|
|
||||||
"""
|
|
||||||
# Stack-Trace normalisieren (Zeilen-Nummern und Timestamps entfernen)
|
|
||||||
normalized_stack = []
|
|
||||||
for line in stack_trace.split('\n'):
|
|
||||||
# Entferne Datei-Pfade, Zeilen-Nummern und Timestamps
|
|
||||||
if 'File "' in line and ', line ' in line:
|
|
||||||
# Nur Dateiname und Funktion behalten
|
|
||||||
parts = line.split(', ')
|
|
||||||
if len(parts) >= 2:
|
|
||||||
file_part = parts[0].split('/')[-1] if '/' in parts[0] else parts[0]
|
|
||||||
func_part = parts[-1] if 'in ' in parts[-1] else ''
|
|
||||||
normalized_stack.append(f"{file_part} {func_part}")
|
|
||||||
elif line.strip() and not line.strip().startswith('2025-'): # Timestamps ignorieren
|
|
||||||
normalized_stack.append(line.strip())
|
|
||||||
|
|
||||||
# Hash-Input erstellen
|
|
||||||
hash_input = f"{exc_type}|{message[:200]}|{'|'.join(normalized_stack[:5])}"
|
|
||||||
|
|
||||||
return hashlib.md5(hash_input.encode('utf-8')).hexdigest()[:12]
|
|
||||||
|
|
||||||
def add_error(self, exc_type: str, message: str, stack_trace: str,
|
|
||||||
logger_name: str, level: str, timestamp: datetime):
|
|
||||||
"""
|
|
||||||
Fügt einen neuen Fehler zur Sammlung hinzu.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
error_hash = self._generate_error_hash(exc_type, message, stack_trace)
|
|
||||||
|
|
||||||
error_data = {
|
|
||||||
'hash': error_hash,
|
|
||||||
'exc_type': exc_type,
|
|
||||||
'message': message,
|
|
||||||
'stack_trace': stack_trace,
|
|
||||||
'logger_name': logger_name,
|
|
||||||
'level': level,
|
|
||||||
'timestamp': timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
with self._lock:
|
|
||||||
self._errors[error_hash].append(error_data)
|
|
||||||
self._error_counts[error_hash] += 1
|
|
||||||
|
|
||||||
# In Datenbank speichern für Historien-Tracking
|
|
||||||
self._store_error_in_db(error_hash, exc_type, message, logger_name, timestamp)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Error adding error to monitor: {str(e)}")
|
|
||||||
|
|
||||||
def _store_error_in_db(self, error_hash: str, exc_type: str, message: str,
|
|
||||||
logger_name: str, timestamp: datetime):
|
|
||||||
"""Speichert Fehler in der Datenbank für Historien-Tracking."""
|
|
||||||
try:
|
|
||||||
with self._get_db_connection() as conn:
|
|
||||||
conn.execute('''
|
|
||||||
INSERT INTO error_history (error_hash, exc_type, message, logger_name, timestamp)
|
|
||||||
VALUES (?, ?, ?, ?, ?)
|
|
||||||
''', (error_hash, exc_type, message[:500], logger_name, timestamp))
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Error storing in DB: {str(e)}")
|
|
||||||
|
|
||||||
def _monitor_loop(self):
|
|
||||||
"""Background-Thread der periodische Reports sendet."""
|
|
||||||
while self._monitoring:
|
|
||||||
try:
|
|
||||||
time.sleep(60) # Prüfe jede Minute
|
|
||||||
|
|
||||||
now = datetime.now()
|
|
||||||
time_since_last_report = (now - self._last_report_time).total_seconds()
|
|
||||||
|
|
||||||
if time_since_last_report >= self.report_interval:
|
|
||||||
self._send_error_report()
|
|
||||||
self._last_report_time = now
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Error in monitor loop: {str(e)}")
|
|
||||||
time.sleep(300) # 5 Minuten warten bei Fehler
|
|
||||||
|
|
||||||
def _send_error_report(self):
|
|
||||||
"""Sendet einen E-Mail-Report mit gesammelten Fehlern."""
|
|
||||||
try:
|
|
||||||
with self._lock:
|
|
||||||
if not self._errors:
|
|
||||||
print("📧 Keine neuen Fehler - kein Report versendet")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Neue Fehler identifizieren
|
|
||||||
new_errors = {
|
|
||||||
hash_val: errors for hash_val, errors in self._errors.items()
|
|
||||||
if hash_val not in self._sent_hashes
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(new_errors) < self.min_errors_for_email:
|
|
||||||
print(f"📧 Nur {len(new_errors)} neue Fehler - Minimum {self.min_errors_for_email} nicht erreicht")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Report generieren und senden
|
|
||||||
report = self._generate_markdown_report(new_errors)
|
|
||||||
self._send_email(report, len(new_errors))
|
|
||||||
|
|
||||||
# Als gesendet markieren
|
|
||||||
for error_hash in new_errors.keys():
|
|
||||||
self._sent_hashes.add(error_hash)
|
|
||||||
self._mark_as_sent(error_hash)
|
|
||||||
|
|
||||||
# Fehler-Cache leeren
|
|
||||||
self._errors.clear()
|
|
||||||
self._error_counts.clear()
|
|
||||||
|
|
||||||
print(f"📧 Error Report versendet: {len(new_errors)} neue Fehlertypen")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Error sending report: {str(e)}")
|
|
||||||
|
|
||||||
def _generate_markdown_report(self, errors: Dict) -> str:
|
|
||||||
"""Generiert einen Markdown-Report der Fehler."""
|
|
||||||
now = datetime.now()
|
|
||||||
|
|
||||||
# Header
|
|
||||||
report = f"""# 🚨 MYP Platform - Error Report
|
|
||||||
|
|
||||||
**Zeitraum:** {self._last_report_time.strftime('%d.%m.%Y %H:%M')} - {now.strftime('%d.%m.%Y %H:%M')}
|
|
||||||
**Neue Fehlertypen:** {len(errors)}
|
|
||||||
**System:** Mercedes-Benz MYP Platform
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Fehlerübersicht
|
|
||||||
|
|
||||||
| Exception-Typ | Anzahl | Letzter Zeitstempel | Logger | Hash |
|
|
||||||
|---------------|--------|-------------------|--------|------|
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Fehler nach Anzahl sortieren
|
|
||||||
sorted_errors = sorted(
|
|
||||||
errors.items(),
|
|
||||||
key=lambda x: len(x[1]),
|
|
||||||
reverse=True
|
|
||||||
)
|
|
||||||
|
|
||||||
for error_hash, error_list in sorted_errors[:self.max_errors_per_report]:
|
|
||||||
latest_error = max(error_list, key=lambda x: x['timestamp'])
|
|
||||||
count = len(error_list)
|
|
||||||
|
|
||||||
exc_type = latest_error['exc_type']
|
|
||||||
timestamp = latest_error['timestamp'].strftime('%d.%m.%Y %H:%M:%S')
|
|
||||||
logger_name = latest_error['logger_name']
|
|
||||||
|
|
||||||
report += f"| `{exc_type}` | **{count}x** | {timestamp} | {logger_name} | `{error_hash}` |\n"
|
|
||||||
|
|
||||||
# Details zu den häufigsten Fehlern
|
|
||||||
report += "\n---\n\n## 🔍 Fehlerdetails\n\n"
|
|
||||||
|
|
||||||
for i, (error_hash, error_list) in enumerate(sorted_errors[:5]): # Top 5
|
|
||||||
latest_error = max(error_list, key=lambda x: x['timestamp'])
|
|
||||||
count = len(error_list)
|
|
||||||
|
|
||||||
report += f"### {i+1}. {latest_error['exc_type']} ({count}x)\n\n"
|
|
||||||
report += f"**Hash:** `{error_hash}`\n"
|
|
||||||
report += f"**Logger:** {latest_error['logger_name']}\n"
|
|
||||||
report += f"**Letzter Auftritt:** {latest_error['timestamp'].strftime('%d.%m.%Y %H:%M:%S')}\n\n"
|
|
||||||
report += f"**Message:**\n```\n{latest_error['message'][:300]}{'...' if len(latest_error['message']) > 300 else ''}\n```\n\n"
|
|
||||||
|
|
||||||
# Stack-Trace (gekürzt)
|
|
||||||
stack_lines = latest_error['stack_trace'].split('\n')
|
|
||||||
relevant_stack = [line for line in stack_lines if 'File "' in line or 'Error:' in line or 'Exception:' in line]
|
|
||||||
if relevant_stack:
|
|
||||||
report += f"**Stack-Trace (gekürzt):**\n```\n" + '\n'.join(relevant_stack[-5:]) + "\n```\n\n"
|
|
||||||
|
|
||||||
report += "---\n\n"
|
|
||||||
|
|
||||||
# Footer
|
|
||||||
report += f"""
|
|
||||||
## 📈 Statistiken
|
|
||||||
|
|
||||||
- **Monitoring-Intervall:** {self.report_interval / 3600:.1f} Stunden
|
|
||||||
- **Überwachte Logger:** Alle ERROR-Level Logs
|
|
||||||
- **Report generiert:** {now.strftime('%d.%m.%Y %H:%M:%S')}
|
|
||||||
- **System-Status:** Online ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Dieses ist ein automatisch generierter Report des MYP Platform Error Monitoring Systems.*
|
|
||||||
*Bei kritischen Fehlern kontaktieren Sie bitte sofort das Entwicklerteam.*
|
|
||||||
"""
|
|
||||||
|
|
||||||
return report
|
|
||||||
|
|
||||||
def _send_email(self, report: str, error_count: int):
|
|
||||||
"""Sendet den Error-Report per E-Mail."""
|
|
||||||
try:
|
|
||||||
# E-Mail zusammenstellen
|
|
||||||
msg = MIMEMultipart('alternative')
|
|
||||||
msg['Subject'] = f"🚨 MYP Platform Error Report - {error_count} neue Fehler"
|
|
||||||
msg['From'] = f"{self.from_name} <{self.from_email}>"
|
|
||||||
msg['To'] = self.alert_email
|
|
||||||
msg['X-Priority'] = '2' # High Priority
|
|
||||||
|
|
||||||
# Markdown als Plain Text
|
|
||||||
text_part = MIMEText(report, 'plain', 'utf-8')
|
|
||||||
msg.attach(text_part)
|
|
||||||
|
|
||||||
# E-Mail senden
|
|
||||||
if not self.smtp_username:
|
|
||||||
print("📧 SMTP nicht konfiguriert - Report nur in Console ausgegeben")
|
|
||||||
print("\n" + "="*60)
|
|
||||||
print(report)
|
|
||||||
print("="*60 + "\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
with smtplib.SMTP(self.smtp_host, self.smtp_port) as server:
|
|
||||||
if self.smtp_use_tls:
|
|
||||||
server.starttls()
|
|
||||||
|
|
||||||
server.login(self.smtp_username, self.smtp_password)
|
|
||||||
server.send_message(msg)
|
|
||||||
|
|
||||||
print(f"📧 Error Report erfolgreich versendet an {self.alert_email}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ E-Mail-Versand fehlgeschlagen: {str(e)}")
|
|
||||||
# Fallback: Report in Console ausgeben
|
|
||||||
print("\n" + "="*60)
|
|
||||||
print("FALLBACK ERROR REPORT:")
|
|
||||||
print(report)
|
|
||||||
print("="*60 + "\n")
|
|
||||||
|
|
||||||
def _mark_as_sent(self, error_hash: str):
|
|
||||||
"""Markiert einen Fehler-Hash als versendet in der Datenbank."""
|
|
||||||
try:
|
|
||||||
with self._get_db_connection() as conn:
|
|
||||||
# Prüfen ob bereits vorhanden
|
|
||||||
cursor = conn.execute(
|
|
||||||
'SELECT send_count FROM sent_errors WHERE error_hash = ?',
|
|
||||||
(error_hash,)
|
|
||||||
)
|
|
||||||
existing = cursor.fetchone()
|
|
||||||
|
|
||||||
now = datetime.now()
|
|
||||||
|
|
||||||
if existing:
|
|
||||||
# Update
|
|
||||||
conn.execute('''
|
|
||||||
UPDATE sent_errors
|
|
||||||
SET last_sent = ?, send_count = send_count + 1
|
|
||||||
WHERE error_hash = ?
|
|
||||||
''', (now, error_hash))
|
|
||||||
else:
|
|
||||||
# Insert
|
|
||||||
conn.execute('''
|
|
||||||
INSERT INTO sent_errors (error_hash, first_sent, last_sent, send_count)
|
|
||||||
VALUES (?, ?, ?, 1)
|
|
||||||
''', (error_hash, now, now))
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Error marking as sent: {str(e)}")
|
|
||||||
|
|
||||||
def get_stats(self) -> Dict:
|
|
||||||
"""Gibt aktuelle Monitoring-Statistiken zurück."""
|
|
||||||
with self._lock:
|
|
||||||
current_errors = len(self._errors)
|
|
||||||
total_instances = sum(len(errors) for errors in self._errors.values())
|
|
||||||
|
|
||||||
try:
|
|
||||||
with self._get_db_connection() as conn:
|
|
||||||
cursor = conn.execute('SELECT COUNT(*) as total FROM error_history')
|
|
||||||
historical_total = cursor.fetchone()['total']
|
|
||||||
|
|
||||||
cursor = conn.execute('SELECT COUNT(*) as sent FROM sent_errors')
|
|
||||||
sent_count = cursor.fetchone()['sent']
|
|
||||||
except:
|
|
||||||
historical_total = 0
|
|
||||||
sent_count = 0
|
|
||||||
|
|
||||||
return {
|
|
||||||
'current_error_types': current_errors,
|
|
||||||
'current_total_instances': total_instances,
|
|
||||||
'historical_total_errors': historical_total,
|
|
||||||
'sent_error_types': sent_count,
|
|
||||||
'last_report': self._last_report_time.isoformat(),
|
|
||||||
'monitoring_interval': self.report_interval,
|
|
||||||
'alert_email': self.alert_email,
|
|
||||||
'monitoring_active': self._monitoring
|
|
||||||
}
|
|
||||||
|
|
||||||
def force_report(self) -> bool:
|
|
||||||
"""Erzwingt das sofortige Senden eines Reports (für Tests/Debug)."""
|
|
||||||
try:
|
|
||||||
print("🔧 Erzwinge sofortigen Error Report...")
|
|
||||||
self._send_error_report()
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Force report failed: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""Stoppt das Error-Monitoring."""
|
|
||||||
self._monitoring = False
|
|
||||||
if self._monitor_thread.is_alive():
|
|
||||||
self._monitor_thread.join(timeout=5.0)
|
|
||||||
print("⏹️ ErrorMonitor gestoppt")
|
|
||||||
|
|
||||||
def install_to_logger(self, logger_name: Optional[str] = None):
|
|
||||||
"""
|
|
||||||
Installiert den ErrorMonitorHandler zu einem bestimmten Logger oder Root-Logger.
|
|
||||||
"""
|
|
||||||
if logger_name:
|
|
||||||
logger = logging.getLogger(logger_name)
|
|
||||||
else:
|
|
||||||
logger = logging.getLogger() # Root logger
|
|
||||||
|
|
||||||
# Verhindere Duplikate
|
|
||||||
for handler in logger.handlers:
|
|
||||||
if isinstance(handler, ErrorMonitorHandler):
|
|
||||||
logger.removeHandler(handler)
|
|
||||||
|
|
||||||
logger.addHandler(self.handler)
|
|
||||||
print(f"✅ ErrorMonitorHandler installiert zu Logger: {logger_name or 'root'}")
|
|
||||||
|
|
||||||
# Globale Instanz
|
|
||||||
_error_monitor_instance = None
|
|
||||||
|
|
||||||
def get_error_monitor() -> ErrorMonitor:
|
|
||||||
"""Gibt die globale ErrorMonitor-Instanz zurück (Singleton)."""
|
|
||||||
global _error_monitor_instance
|
|
||||||
if _error_monitor_instance is None:
|
|
||||||
_error_monitor_instance = ErrorMonitor()
|
|
||||||
return _error_monitor_instance
|
|
||||||
|
|
||||||
def install_error_monitoring(logger_names: Optional[List[str]] = None):
|
|
||||||
"""
|
|
||||||
Installiert Error-Monitoring für bestimmte Logger oder alle Logger.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
logger_names: Liste der Logger-Namen oder None für Root-Logger
|
|
||||||
"""
|
|
||||||
monitor = get_error_monitor()
|
|
||||||
|
|
||||||
if logger_names:
|
|
||||||
for logger_name in logger_names:
|
|
||||||
monitor.install_to_logger(logger_name)
|
|
||||||
else:
|
|
||||||
# Root logger - fängt alle Errors ab
|
|
||||||
monitor.install_to_logger()
|
|
||||||
|
|
||||||
print(f"🔍 Error Monitoring aktiviert für {len(logger_names) if logger_names else 'alle'} Logger")
|
|
||||||
|
|
||||||
def stop_error_monitoring():
|
|
||||||
"""Stoppt das Error-Monitoring."""
|
|
||||||
global _error_monitor_instance
|
|
||||||
if _error_monitor_instance:
|
|
||||||
_error_monitor_instance.stop()
|
|
||||||
_error_monitor_instance = None
|
|
||||||
|
|
||||||
# Test-Funktion
|
|
||||||
def test_error_monitoring():
|
|
||||||
"""Testet das Error-Monitoring-System."""
|
|
||||||
print("🧪 Teste Error-Monitoring-System...")
|
|
||||||
|
|
||||||
monitor = get_error_monitor()
|
|
||||||
monitor.install_to_logger()
|
|
||||||
|
|
||||||
# Test-Logger erstellen
|
|
||||||
test_logger = logging.getLogger('test_error_monitor')
|
|
||||||
test_logger.setLevel(logging.ERROR)
|
|
||||||
|
|
||||||
# Test-Fehler generieren
|
|
||||||
try:
|
|
||||||
raise ValueError("Test-Fehler für Error-Monitoring")
|
|
||||||
except Exception as e:
|
|
||||||
test_logger.error("Test Exception aufgetreten", exc_info=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
raise ConnectionError("Test-Verbindungsfehler")
|
|
||||||
except Exception as e:
|
|
||||||
test_logger.error("Verbindungsfehler beim Test", exc_info=True)
|
|
||||||
|
|
||||||
# Gleichen Fehler nochmal (sollte gruppiert werden)
|
|
||||||
try:
|
|
||||||
raise ValueError("Test-Fehler für Error-Monitoring")
|
|
||||||
except Exception as e:
|
|
||||||
test_logger.error("Wiederholter Test Exception", exc_info=True)
|
|
||||||
|
|
||||||
# Stats anzeigen
|
|
||||||
stats = monitor.get_stats()
|
|
||||||
print(f"📊 Current stats: {stats}")
|
|
||||||
|
|
||||||
# Force Report
|
|
||||||
monitor.force_report()
|
|
||||||
|
|
||||||
print("✅ Error-Monitoring-Test abgeschlossen")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_error_monitoring()
|
|
@ -465,29 +465,3 @@ def measure_execution_time(func=None, logger=None, task_name=None):
|
|||||||
if func:
|
if func:
|
||||||
return decorator(func)
|
return decorator(func)
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
# Error-Monitoring Integration
|
|
||||||
def setup_error_monitoring():
|
|
||||||
"""
|
|
||||||
Initialisiert das Error-Monitoring-System für automatische E-Mail-Berichte.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
from utils.error_monitor import install_error_monitoring
|
|
||||||
|
|
||||||
# Error-Monitoring für wichtige Logger aktivieren
|
|
||||||
critical_loggers = [
|
|
||||||
'app', # Hauptanwendung
|
|
||||||
'auth', # Authentifizierung
|
|
||||||
'jobs', # Job-Management
|
|
||||||
'printers', # Drucker-System
|
|
||||||
'scheduler', # Job-Scheduler
|
|
||||||
'database', # Datenbank-Operationen
|
|
||||||
'security' # Sicherheit
|
|
||||||
]
|
|
||||||
|
|
||||||
install_error_monitoring(critical_loggers)
|
|
||||||
|
|
||||||
print("🔍 Error-Monitoring für kritische Systeme aktiviert")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Fehler beim Initialisieren des Error-Monitoring: {str(e)}")
|
|
Loading…
x
Reference in New Issue
Block a user