""" monitoring/logger.py — Structured JSON logging with file and console handlers. All log entries are structured JSON for machine parsing. """ from __future__ import annotations import json import logging import os import sys from datetime import datetime, timezone from pathlib import Path import config LOG_DIR = Path(config.BASE_DIR) / "logs" LOG_DIR.mkdir(parents=True, exist_ok=True) class JSONFormatter(logging.Formatter): """Structured JSON log formatter.""" def format(self, record: logging.LogRecord) -> str: log_entry = { "timestamp": datetime.now(timezone.utc).isoformat(), "level": record.levelname, "logger": record.name, "message": record.getMessage(), "module": record.module, "function": record.funcName, "line": record.lineno, } if record.exc_info and record.exc_info[1]: log_entry["exception"] = str(record.exc_info[1]) if config.DRY_RUN: log_entry["mode"] = "DRY_RUN" elif config.TRADING_MODE == "paper": log_entry["mode"] = "PAPER" else: log_entry["mode"] = config.TRADING_MODE.upper() return json.dumps(log_entry) class ConsoleFormatter(logging.Formatter): """Colored console formatter.""" COLORS = { "DEBUG": "\033[36m", "INFO": "\033[32m", "WARNING": "\033[33m", "ERROR": "\033[31m", "CRITICAL": "\033[1;31m", } RESET = "\033[0m" def format(self, record: logging.LogRecord) -> str: color = self.COLORS.get(record.levelname, "") prefix = "[DRY_RUN] " if config.DRY_RUN else "" return ( f"{color}{record.levelname:8s}{self.RESET} " f"{prefix}{record.name}: {record.getMessage()}" ) def setup_logging(level: str = "INFO"): """Configure logging for the entire trading system.""" root = logging.getLogger() root.setLevel(getattr(logging, level.upper(), logging.INFO)) # Remove existing handlers root.handlers.clear() # Console handler console = logging.StreamHandler(sys.stdout) console.setFormatter(ConsoleFormatter()) console.setLevel(logging.INFO) root.addHandler(console) # JSON file handler today = datetime.now().strftime("%Y-%m-%d") log_file = LOG_DIR / f"trading_{today}.jsonl" file_handler = logging.FileHandler(str(log_file), encoding="utf-8") file_handler.setFormatter(JSONFormatter()) file_handler.setLevel(logging.DEBUG) root.addHandler(file_handler) # Error file handler error_file = LOG_DIR / f"errors_{today}.jsonl" error_handler = logging.FileHandler(str(error_file), encoding="utf-8") error_handler.setFormatter(JSONFormatter()) error_handler.setLevel(logging.ERROR) root.addHandler(error_handler) logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("websocket").setLevel(logging.WARNING) logger = logging.getLogger("trading_system") logger.info("Logging initialized: console + %s", log_file) return logger