""" core/logger.py — Zentralisiertes Logging ========================================== Schreibt sowohl in Konsole als auch in eine rotierende Logdatei. Wird von allen anderen Modulen über `from core.logger import log` genutzt. Verwendung: log.info("Trade BUY 0.5L @ 74.30") log.warning("Slow API response") log.error("Order fehlgeschlagen", exc_info=True) log.debug("Detail-Info") # nur bei DEBUG-Level sichtbar Vorteile gegenüber print(): - Persistente Datei-Logs (überleben Crashes / pythonw) - Log-Rotation (max 5 MB pro Datei, 5 Backups) - Filterbar nach Komponente (logger.getChild('trade')) - Zeit + Level pro Eintrag """ from __future__ import annotations import logging import sys from logging.handlers import RotatingFileHandler from pathlib import Path # ── Konfiguration ────────────────────────────── LOG_FILE = Path(__file__).parent.parent / "oil_widget.log" LOG_MAX_BYTES = 5_000_000 LOG_BACKUP_COUNT = 5 # Format mit Komponente, Level, Zeit _FILE_FORMAT = "%(asctime)s [%(levelname)-7s] %(name)-14s | %(message)s" _CONSOLE_FORMAT = "[%(name)s] %(message)s" def _setup_logger(level: int = logging.INFO) -> logging.Logger: """Erstellt den Root-Logger 'oil' mit Datei- und Konsolen-Handler.""" logger = logging.getLogger("oil") logger.setLevel(logging.DEBUG) # Detail-Filter erfolgt pro Handler logger.propagate = False # Doppelte Handler vermeiden (z.B. bei Reload) if logger.handlers: return logger # Datei-Handler – komplettes Detail try: fh = RotatingFileHandler( LOG_FILE, maxBytes=LOG_MAX_BYTES, backupCount=LOG_BACKUP_COUNT, encoding="utf-8") fh.setLevel(logging.DEBUG) fh.setFormatter(logging.Formatter(_FILE_FORMAT, datefmt="%Y-%m-%d %H:%M:%S")) logger.addHandler(fh) except Exception as e: # Fallback: kein File-Log möglich (z.B. Read-Only-FS) sys.stderr.write(f"[Logger] Datei-Logging fehlgeschlagen: {e}\n") # Konsolen-Handler – kompakt ch = logging.StreamHandler(sys.stdout) ch.setLevel(level) ch.setFormatter(logging.Formatter(_CONSOLE_FORMAT)) logger.addHandler(ch) return logger # Globaler Root-Logger – einmalig instanziiert log = _setup_logger() def get_logger(component: str) -> logging.Logger: """ Liefert einen Sub-Logger pro Komponente, z.B. 'trade', 'mt5', 'ai'. Erscheint im Log dann als 'oil.trade'. """ return log.getChild(component) def set_console_level(level: str | int): """Erlaubt Runtime-Änderung des Konsolen-Log-Levels.""" if isinstance(level, str): level = getattr(logging, level.upper(), logging.INFO) for h in log.handlers: if isinstance(h, logging.StreamHandler) and not isinstance( h, RotatingFileHandler): h.setLevel(level)