Initial commit: Oil Trading Bot (MT5, WTI)
Headless FastAPI-Backend (server.py + core/engine.py) mit Mobile-PWA (web/), Strategie-/Backtest-Suite und Doku. Secrets, DB, Logs und Laufzeit-State sind via .gitignore ausgeschlossen; Config-Vorlage: oil_widget_config.ini.example. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user