Files
gogo2/utils/logging_config.py
2025-11-22 12:47:43 +02:00

234 lines
7.8 KiB
Python

"""
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")