""" Modular Logging Configuration System Provides granular control over logging channels for different subsystems. Configure which channels to enable/disable at startup. """ import logging import os from typing import Dict, Set from pathlib import Path # Define logging channels class LogChannel: """Available logging channels""" CORE = "core" # Core system operations TRADING = "trading" # Trading operations TRAINING = "training" # Model training INFERENCE = "inference" # Model inference PIVOTS = "pivots" # Pivot calculations (Williams structure) DATA = "data" # Data fetching/caching WEBSOCKET = "websocket" # WebSocket communications API = "api" # API requests/responses WEBUI = "webui" # Web UI requests/responses PERFORMANCE = "performance" # Performance metrics DEBUG = "debug" # Debug information # Default channel configuration (which channels are enabled) DEFAULT_CHANNEL_CONFIG = { LogChannel.CORE: True, LogChannel.TRADING: True, LogChannel.TRAINING: True, LogChannel.INFERENCE: True, LogChannel.PIVOTS: False, # Disabled by default (too verbose) LogChannel.DATA: True, LogChannel.WEBSOCKET: False, # Disabled by default LogChannel.API: False, # Disabled by default LogChannel.WEBUI: False, # Disabled by default (too verbose) LogChannel.PERFORMANCE: True, LogChannel.DEBUG: False # Disabled by default } class ChannelLogger: """Logger with channel-based filtering""" _instance = None _initialized = False def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def __init__(self): if not self._initialized: self.enabled_channels: Set[str] = set() self.loggers: Dict[str, logging.Logger] = {} self._load_config() ChannelLogger._initialized = True def _load_config(self): """Load channel configuration from environment or use defaults""" # Load from environment variables (e.g., LOG_CHANNELS="core,trading,inference") env_channels = os.getenv('LOG_CHANNELS', None) if env_channels: # Use environment config self.enabled_channels = set(env_channels.split(',')) print(f"Logging channels (from env): {', '.join(sorted(self.enabled_channels))}") else: # Use default config self.enabled_channels = { channel for channel, enabled in DEFAULT_CHANNEL_CONFIG.items() if enabled } print(f"Logging channels (default): {', '.join(sorted(self.enabled_channels))}") def get_logger(self, name: str, channel: str = LogChannel.CORE) -> logging.Logger: """ Get a logger for a specific channel Args: name: Logger name (usually __name__) channel: Logging channel (from LogChannel) Returns: Logger instance with channel filtering """ logger_key = f"{name}:{channel}" if logger_key not in self.loggers: logger = logging.getLogger(name) # Wrap logger to check channel before logging wrapped_logger = ChannelFilteredLogger(logger, channel, self) self.loggers[logger_key] = wrapped_logger return self.loggers[logger_key] def is_channel_enabled(self, channel: str) -> bool: """Check if a channel is enabled""" return channel in self.enabled_channels def enable_channel(self, channel: str): """Enable a logging channel at runtime""" self.enabled_channels.add(channel) print(f"Enabled logging channel: {channel}") def disable_channel(self, channel: str): """Disable a logging channel at runtime""" self.enabled_channels.discard(channel) print(f"Disabled logging channel: {channel}") def set_channels(self, channels: Set[str]): """Set enabled channels""" self.enabled_channels = channels print(f"Logging channels updated: {', '.join(sorted(channels))}") def get_enabled_channels(self) -> Set[str]: """Get currently enabled channels""" return self.enabled_channels.copy() class ChannelFilteredLogger: """Wrapper around logging.Logger that filters by channel""" def __init__(self, logger: logging.Logger, channel: str, channel_logger: ChannelLogger): self.logger = logger self.channel = channel self.channel_logger = channel_logger def _should_log(self) -> bool: """Check if this channel should log""" return self.channel_logger.is_channel_enabled(self.channel) def debug(self, msg, *args, **kwargs): if self._should_log(): self.logger.debug(f"[{self.channel}] {msg}", *args, **kwargs) def info(self, msg, *args, **kwargs): if self._should_log(): self.logger.info(f"[{self.channel}] {msg}", *args, **kwargs) def warning(self, msg, *args, **kwargs): if self._should_log(): self.logger.warning(f"[{self.channel}] {msg}", *args, **kwargs) def error(self, msg, *args, **kwargs): # Errors always log regardless of channel self.logger.error(f"[{self.channel}] {msg}", *args, **kwargs) def exception(self, msg, *args, **kwargs): # Exceptions always log regardless of channel self.logger.exception(f"[{self.channel}] {msg}", *args, **kwargs) def critical(self, msg, *args, **kwargs): # Critical always logs regardless of channel self.logger.critical(f"[{self.channel}] {msg}", *args, **kwargs) # Global instance _channel_logger = ChannelLogger() def get_channel_logger(name: str, channel: str = LogChannel.CORE) -> ChannelFilteredLogger: """ Get a channel-filtered logger Usage: from utils.logging_config import get_channel_logger, LogChannel logger = get_channel_logger(__name__, LogChannel.PIVOTS) logger.info("Pivot calculated") # Only logs if PIVOTS channel is enabled Args: name: Logger name (usually __name__) channel: Logging channel Returns: Channel-filtered logger """ return _channel_logger.get_logger(name, channel) def configure_logging_channels(channels: Set[str]): """ Configure which logging channels are enabled Args: channels: Set of channel names to enable """ _channel_logger.set_channels(channels) def enable_channel(channel: str): """Enable a specific logging channel""" _channel_logger.enable_channel(channel) def disable_channel(channel: str): """Disable a specific logging channel""" _channel_logger.disable_channel(channel) def get_enabled_channels() -> Set[str]: """Get currently enabled channels""" return _channel_logger.get_enabled_channels() def print_channel_status(): """Print status of all logging channels""" print("\n=== Logging Channel Status ===") all_channels = [ LogChannel.CORE, LogChannel.TRADING, LogChannel.TRAINING, LogChannel.INFERENCE, LogChannel.PIVOTS, LogChannel.DATA, LogChannel.WEBSOCKET, LogChannel.API, LogChannel.WEBUI, LogChannel.PERFORMANCE, LogChannel.DEBUG ] enabled = _channel_logger.get_enabled_channels() for channel in all_channels: status = "ENABLED" if channel in enabled else "DISABLED" print(f" {channel:15s} : {status}") print("=" * 31 + "\n")