| | """ |
| | Centralized Logging System for Fragmenta Desktop |
| | Replaces scattered print statements with structured logging |
| | """ |
| |
|
| | import logging |
| | import sys |
| | from pathlib import Path |
| | from typing import Optional |
| | from datetime import datetime |
| | import os |
| |
|
| | |
| |
|
| |
|
| | class Colors: |
| | RESET = '\033[0m' |
| | BOLD = '\033[1m' |
| | RED = '\033[91m' |
| | GREEN = '\033[92m' |
| | YELLOW = '\033[93m' |
| | BLUE = '\033[94m' |
| | PURPLE = '\033[95m' |
| | CYAN = '\033[96m' |
| |
|
| |
|
| | class ColoredFormatter(logging.Formatter): |
| | """Custom formatter that adds colors to log levels""" |
| |
|
| | COLORS = { |
| | 'DEBUG': Colors.CYAN, |
| | 'INFO': Colors.GREEN, |
| | 'WARNING': Colors.YELLOW, |
| | 'ERROR': Colors.RED, |
| | 'CRITICAL': Colors.RED + Colors.BOLD |
| | } |
| |
|
| | def format(self, record): |
| | if hasattr(record, 'levelname'): |
| | color = self.COLORS.get(record.levelname, Colors.RESET) |
| | record.levelname = f"{color}{record.levelname}{Colors.RESET}" |
| |
|
| | formatted = super().format(record) |
| | |
| | if sys.platform == 'win32': |
| | try: |
| | formatted.encode('charmap') |
| | except UnicodeEncodeError: |
| | formatted = formatted.encode('ascii', 'replace').decode('ascii') |
| | |
| | return formatted |
| |
|
| |
|
| | class FragmentaLogger: |
| |
|
| | _instance = None |
| | _loggers = {} |
| |
|
| | def __new__(cls): |
| | if cls._instance is None: |
| | cls._instance = super().__new__(cls) |
| | cls._instance._initialized = False |
| | return cls._instance |
| |
|
| | def __init__(self): |
| | if not self._initialized: |
| | self.setup_logging() |
| | self._initialized = True |
| |
|
| | def setup_logging(self, log_level: str = None, log_file: bool = True): |
| | if sys.platform == 'win32': |
| | try: |
| | sys.stdout.reconfigure(encoding='utf-8') |
| | sys.stderr.reconfigure(encoding='utf-8') |
| | except AttributeError: |
| | import codecs |
| | sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'ignore') |
| | sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'ignore') |
| |
|
| | if log_level is None: |
| | log_level = os.environ.get('FRAGMENTA_LOG_LEVEL', 'INFO').upper() |
| |
|
| | numeric_level = getattr(logging, log_level, logging.INFO) |
| |
|
| | log_dir = Path("logs") |
| | log_dir.mkdir(exist_ok=True) |
| |
|
| | root_logger = logging.getLogger() |
| | root_logger.setLevel(numeric_level) |
| |
|
| | root_logger.handlers.clear() |
| |
|
| | console_handler = logging.StreamHandler(sys.stdout) |
| | console_handler.setLevel(numeric_level) |
| |
|
| | console_format = "%(asctime)s | %(levelname)s | %(name)s | %(message)s" |
| | console_formatter = ColoredFormatter( |
| | console_format, datefmt='%H:%M:%S') |
| | console_handler.setFormatter(console_formatter) |
| | root_logger.addHandler(console_handler) |
| |
|
| | if log_file: |
| | log_filename = f"fragmenta_{datetime.now().strftime('%Y%m%d')}.log" |
| | file_handler = logging.FileHandler(log_dir / log_filename) |
| | file_handler.setLevel(logging.DEBUG) |
| |
|
| | file_format = "%(asctime)s | %(levelname)s | %(name)s | %(funcName)s:%(lineno)d | %(message)s" |
| | file_formatter = logging.Formatter( |
| | file_format, datefmt='%Y-%m-%d %H:%M:%S') |
| | file_handler.setFormatter(file_formatter) |
| | root_logger.addHandler(file_handler) |
| |
|
| | logger = self.get_logger('FragmentaLogger') |
| | logger.info(f"Logging system initialized (Level: {log_level})") |
| | if log_file: |
| | logger.info(f"Log file: {log_dir / log_filename}") |
| |
|
| | def get_logger(self, name: str) -> logging.Logger: |
| | if name not in self._loggers: |
| | self._loggers[name] = logging.getLogger(name) |
| | return self._loggers[name] |
| |
|
| |
|
| | _fragmenta_logger = FragmentaLogger() |
| |
|
| |
|
| | def setup_logging(log_level: str = None, log_file: bool = True): |
| | _fragmenta_logger.setup_logging(log_level, log_file) |
| |
|
| |
|
| | def get_logger(name: str) -> logging.Logger: |
| | return _fragmenta_logger.get_logger(name) |
| |
|
| |
|
| | def log_function_call(func): |
| | def wrapper(*args, **kwargs): |
| | logger = get_logger(func.__module__) |
| | logger.debug( |
| | f"[CALL] {func.__name__} with args={args}, kwargs={kwargs}") |
| | try: |
| | result = func(*args, **kwargs) |
| | logger.debug(f"{func.__name__} completed successfully") |
| | return result |
| | except Exception as e: |
| | logger.error(f"{func.__name__} failed: {e}") |
| | raise |
| | return wrapper |
| |
|
| |
|
| | def log_performance(func): |
| | def wrapper(*args, **kwargs): |
| | import time |
| | logger = get_logger(func.__module__) |
| | start_time = time.time() |
| |
|
| | try: |
| | result = func(*args, **kwargs) |
| | elapsed = time.time() - start_time |
| | logger.info(f"{func.__name__} completed in {elapsed:.2f}s") |
| | return result |
| | except Exception as e: |
| | elapsed = time.time() - start_time |
| | logger.error(f"{func.__name__} failed after {elapsed:.2f}s: {e}") |
| | raise |
| | return wrapper |
| |
|
| | def print_info(message: str, component: str = "Legacy"): |
| | logger = get_logger(component) |
| | logger.info(message) |
| |
|
| |
|
| | def print_error(message: str, component: str = "Legacy"): |
| | logger = get_logger(component) |
| | logger.error(message) |
| |
|
| |
|
| | def print_warning(message: str, component: str = "Legacy"): |
| | logger = get_logger(component) |
| | logger.warning(message) |
| |
|
| |
|
| | def print_debug(message: str, component: str = "Legacy"): |
| | logger = get_logger(component) |
| | logger.debug(message) |
| |
|