# logger.py — In-memory log handler for scraper and forecast log separation import logging from datetime import datetime from typing import List, Dict, Optional class MemoryLogHandler(logging.Handler): """Stores log entries in memory for API log streaming.""" def __init__(self, capacity: int = 500): super().__init__() self.capacity = capacity self.logs: List[Dict] = [] def emit(self, record): try: msg = record.getMessage() log_entry = { "timestamp": datetime.fromtimestamp(record.created).isoformat(), "level": record.levelname, "source": record.name.split('.')[-1], "message": msg } self.logs.append(log_entry) if len(self.logs) > self.capacity: self.logs.pop(0) except Exception: self.handleError(record) def get_logs(self, limit: int = 100, level: Optional[str] = None) -> List[Dict]: filtered = self.logs if level and level.upper() != "ALL": filtered = [l for l in filtered if l["level"] == level.upper()] return filtered[-limit:] def clear_logs(self): self.logs.clear() # Separated handlers scraper_memory_handler = MemoryLogHandler(capacity=500) forecast_memory_handler = MemoryLogHandler(capacity=500) def get_app_logger(name: str) -> logging.Logger: """Default app logger for scraping operations.""" logger = logging.getLogger(f"app.scraper.{name}") logger.setLevel(logging.INFO) # Add memory handler if not any(h is scraper_memory_handler for h in logger.handlers): logger.addHandler(scraper_memory_handler) # Add console stream handler if not any(isinstance(h, logging.StreamHandler) and not isinstance(h, MemoryLogHandler) for h in logger.handlers): console = logging.StreamHandler() console.setLevel(logging.INFO) console.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] scraper.%(name)s: %(message)s")) logger.addHandler(console) return logger def get_forecast_logger(name: str) -> logging.Logger: """Dedicated app logger for forecasting operations.""" logger = logging.getLogger(f"app.forecast.{name}") logger.setLevel(logging.INFO) # Add memory handler if not any(h is forecast_memory_handler for h in logger.handlers): logger.addHandler(forecast_memory_handler) # Add console stream handler if not any(isinstance(h, logging.StreamHandler) and not isinstance(h, MemoryLogHandler) for h in logger.handlers): console = logging.StreamHandler() console.setLevel(logging.INFO) console.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] forecast.%(name)s: %(message)s")) logger.addHandler(console) return logger