# Description: Structured logging system for EU-Utility # Centralized logging with levels, rotation, and formatting """ EU-Utility Logging System Structured logging with: - Multiple log levels (DEBUG, INFO, WARNING, ERROR, CRITICAL) - Log rotation by size and time - Formatted output - File and console handlers - Context tracking """ import logging import logging.handlers import os import sys from datetime import datetime from pathlib import Path class Logger: """ Centralized logging system for EU-Utility. Usage: from core.logger import get_logger logger = get_logger("plugin_name") logger.info("Application started") logger.error("Something went wrong", exc_info=True) """ _instance = None _initialized = False def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def __init__(self): if Logger._initialized: return Logger._initialized = True # Setup log directory self.log_dir = Path.home() / '.eu-utility' / 'logs' self.log_dir.mkdir(parents=True, exist_ok=True) # Create main logger self.logger = logging.getLogger('eu_utility') self.logger.setLevel(logging.DEBUG) # Prevent duplicate handlers if self.logger.handlers: return # File handler with rotation (10MB per file, keep 5 backups) file_handler = logging.handlers.RotatingFileHandler( self.log_dir / 'eu_utility.log', maxBytes=10*1024*1024, # 10MB backupCount=5, encoding='utf-8' ) file_handler.setLevel(logging.DEBUG) # Console handler console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(logging.INFO) # Formatters file_formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) console_formatter = logging.Formatter( '[%(levelname)s] %(message)s' ) file_handler.setFormatter(file_formatter) console_handler.setFormatter(console_formatter) self.logger.addHandler(file_handler) self.logger.addHandler(console_handler) # Plugin loggers cache self._plugin_loggers = {} def get_logger(self, name): """Get a logger for a specific component.""" if name not in self._plugin_loggers: logger = logging.getLogger(f'eu_utility.{name}') self._plugin_loggers[name] = logger return self._plugin_loggers[name] def set_level(self, level): """Set the global log level.""" self.logger.setLevel(level) def get_log_path(self): """Get the path to the current log file.""" return str(self.log_dir / 'eu_utility.log') def get_recent_logs(self, lines=100): """Get recent log entries.""" try: log_file = self.log_dir / 'eu_utility.log' if not log_file.exists(): return [] with open(log_file, 'r', encoding='utf-8') as f: all_lines = f.readlines() return all_lines[-lines:] except Exception: return [] # Global instance _logger = None def get_logger(name=None): """ Get a logger instance. Args: name: Component name (e.g., 'loot_tracker', 'core.event_bus') Returns: logging.Logger instance """ global _logger if _logger is None: _logger = Logger() if name: return _logger.get_logger(name) return _logger.logger # Convenience functions def debug(msg, *args, **kwargs): """Log debug message.""" get_logger().debug(msg, *args, **kwargs) def info(msg, *args, **kwargs): """Log info message.""" get_logger().info(msg, *args, **kwargs) def warning(msg, *args, **kwargs): """Log warning message.""" get_logger().warning(msg, *args, **kwargs) def error(msg, *args, **kwargs): """Log error message.""" get_logger().error(msg, *args, **kwargs) def critical(msg, *args, **kwargs): """Log critical message.""" get_logger().critical(msg, *args, **kwargs) class LogViewer: """Utility class for viewing and filtering logs.""" def __init__(self): self.log_dir = Path.home() / '.eu-utility' / 'logs' def get_logs(self, level=None, component=None, since=None, until=None): """ Get filtered logs. Args: level: Filter by level (DEBUG, INFO, WARNING, ERROR, CRITICAL) component: Filter by component name since: Filter by start datetime until: Filter by end datetime Returns: List of log entries """ log_file = self.log_dir / 'eu_utility.log' if not log_file.exists(): return [] entries = [] try: with open(log_file, 'r', encoding='utf-8') as f: for line in f: # Parse log line # Format: 2025-01-15 10:30:45 - name - LEVEL - [file:line] - message try: parts = line.split(' - ', 4) if len(parts) < 4: continue timestamp_str = parts[0] name = parts[1] log_level = parts[2] message = parts[-1] # Parse timestamp timestamp = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S') # Apply filters if level and log_level != level: continue if component and component not in name: continue if since and timestamp < since: continue if until and timestamp > until: continue entries.append({ 'timestamp': timestamp, 'component': name, 'level': log_level, 'message': message.strip() }) except Exception: continue except Exception as e: print(f"Error reading logs: {e}") return entries def tail(self, lines=50): """Get last N log lines.""" return get_logger().get_recent_logs(lines) def export_logs(self, output_path, **filters): """Export filtered logs to file.""" entries = self.get_logs(**filters) with open(output_path, 'w', encoding='utf-8') as f: for entry in entries: f.write(f"{entry['timestamp']} - {entry['component']} - {entry['level']} - {entry['message']}\n") # Plugin integration helper class PluginLogger: """Helper class for plugins to use logging.""" def __init__(self, plugin_name): self.logger = get_logger(f"plugins.{plugin_name}") def debug(self, msg, **kwargs): self.logger.debug(msg, **kwargs) def info(self, msg, **kwargs): self.logger.info(msg, **kwargs) def warning(self, msg, **kwargs): self.logger.warning(msg, **kwargs) def error(self, msg, **kwargs): self.logger.error(msg, **kwargs) def critical(self, msg, **kwargs): self.logger.critical(msg, **kwargs) def exception(self, msg, **kwargs): self.logger.exception(msg, **kwargs)