🐛 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()
|
||||
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
|
||||
app_logger = get_logger("app")
|
||||
auth_logger = get_logger("auth")
|
||||
@ -4867,53 +4860,3 @@ def get_printers():
|
||||
"error": f"Fehler beim Laden der Drucker: {str(e)}",
|
||||
"printers": []
|
||||
}), 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:
|
||||
return decorator(func)
|
||||
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