""" TransPyC 彩色日志系统 支持终端颜色输出和日志文件转储 """ from __future__ import annotations import sys import os from datetime import datetime from typing import Any, Callable, Optional from lib.constants.config import mode as _ConfigMode # ANSI 颜色代码 class Colors: RESET = '\033[0m' BOLD = '\033[1m' DIM = '\033[2m' UNDERLINE = '\033[4m' # 前景色 BLACK = '\033[30m' RED = '\033[31m' GREEN = '\033[32m' YELLOW = '\033[33m' BLUE = '\033[34m' MAGENTA = '\033[35m' CYAN = '\033[36m' WHITE = '\033[37m' # 高亮前景色 BRIGHT_RED = '\033[91m' BRIGHT_GREEN = '\033[92m' BRIGHT_YELLOW = '\033[93m' BRIGHT_BLUE = '\033[94m' BRIGHT_MAGENTA = '\033[95m' BRIGHT_CYAN = '\033[96m' # 背景色 BG_RED = '\033[41m' BG_GREEN = '\033[42m' BG_YELLOW = '\033[43m' BG_BLUE = '\033[44m' BG_MAGENTA = '\033[45m' BG_CYAN = '\033[46m' # 渐变颜色(用于详细日志) GRAY = '\033[90m' # 检查是否支持颜色 def supports_color() -> bool: if not hasattr(sys.stdout, 'isatty'): return False if not sys.stdout.isatty(): return False if os.environ.get('NO_COLOR'): return False if os.environ.get('FORCE_COLOR'): return True return True # 全局颜色支持标志 USE_COLORS = supports_color() # 日志级别 class LogLevel: DEBUG = 0 INFO = 1 WARNING = 2 ERROR = 3 CRITICAL = 4 class Logger: def __init__(self, name: str = "TransPyC", log_file: Optional[str] = None, level: int = LogLevel.INFO, use_colors: bool = True): self.name = name self.level = level self.use_colors = use_colors and USE_COLORS self.log_file = log_file self.file_handler = None if log_file: os.makedirs(os.path.dirname(os.path.abspath(log_file)), exist_ok=True) self.file_handler = open(log_file, 'w', encoding='utf-8') # 进度跟踪 self.current_phase = "" self.total_items = 0 self.current_item = 0 def _colorize(self, text: str, color: str) -> str: if self.use_colors: return f"{color}{text}{Colors.RESET}" return text def _format_time(self) -> str: return datetime.now().strftime("%H:%M:%S") def _write(self, message: str, level: str = ""): # 输出到控制台 print(message, file=sys.stdout) sys.stdout.flush() # 输出到文件 if self.file_handler: # 文件输出不带颜色 clean_msg = message for color in vars(Colors).values(): if isinstance(color, str) and color.startswith('\033'): clean_msg = clean_msg.replace(color, '') timestamp = self._format_time() self.file_handler.write(f"[{timestamp}] {level} {clean_msg}\n") self.file_handler.flush() def set_phase(self, phase: str, total: int = 0) -> None: self.current_phase = phase self.total_items = total self.current_item = 0 self.progress(phase, 0, total) def progress(self, message: str = "", current: int = 0, total: int = 0) -> None: if total > 0: self.total_items = total if current > 0: self.current_item = current if self.total_items > 0: percent: float = (self.current_item / self.total_items) * 100 bar_length: int = 30 filled: int = int(bar_length * self.current_item / self.total_items) bar: str = '█' * filled + '░' * (bar_length - filled) status: str = f"[{self._colorize(bar, Colors.BLUE)}] {self.current_item}/{self.total_items} ({percent:.1f}%)" else: status = "" msg: str = f"{self._colorize('▶', Colors.BRIGHT_CYAN)} {self.current_phase}" if status: msg += f" {status}" if message: msg += f" - {message}" # 使用 \r 实现进度更新 sys.stdout.write('\r' + msg) sys.stdout.flush() def debug(self, message: str, category: str = "") -> None: if self.level > LogLevel.DEBUG: return prefix: str = self._colorize("DEBUG", Colors.GRAY) if category: prefix += f"[{self._colorize(category, Colors.CYAN)}]" msg: str = f"{self._colorize('├', Colors.GRAY)} {prefix}: {message}" self._write(msg, "DEBUG") def info(self, message: str, category: str = "") -> None: prefix: str = self._colorize("INFO", Colors.GREEN) if category: prefix += f"[{self._colorize(category, Colors.CYAN)}]" msg: str = f"{self._colorize('├', Colors.GREEN)} {prefix}: {message}" self._write(msg, "INFO") def warning(self, message: str, category: str = "", exc_info: Exception | None = None) -> None: prefix: str = self._colorize("WARN", Colors.BRIGHT_YELLOW) if category: prefix += f"[{self._colorize(category, Colors.CYAN)}]" msg: str = f"{self._colorize('├', Colors.YELLOW)} {prefix}: {message}" self._write(msg, "WARNING") def error(self, message: str, category: str = "", exception: Optional[Exception] = None) -> None: prefix: str = self._colorize("ERROR", Colors.BRIGHT_RED) if category: prefix += f"[{self._colorize(category, Colors.CYAN)}]" msg: str = f"{self._colorize('├', Colors.RED)} {prefix}: {message}" if exception: msg += f"\n{self._colorize('│', Colors.RED)} Exception: {type(exception).__name__}: {exception}" self._write(msg, "ERROR") def critical(self, message: str, category: str = "") -> None: prefix: str = self._colorize("CRITICAL", Colors.BG_RED + Colors.WHITE + Colors.BOLD) if category: prefix += f"[{self._colorize(category, Colors.CYAN)}]" msg: str = f"{self._colorize('╠', Colors.RED)} {prefix}: {message}" self._write(msg, "CRITICAL") def success(self, message: str, category: str = "") -> None: prefix: str = self._colorize("SUCCESS", Colors.BRIGHT_GREEN) if category: prefix += f"[{self._colorize(category, Colors.CYAN)}]" msg: str = f"{self._colorize('├', Colors.GREEN)} {prefix}: {message}" self._write(msg, "SUCCESS") def banner(self, message: str, category: str = "") -> None: """输出醒目的横幅标题""" header: str = self._colorize(message, Colors.BRIGHT_BLUE + Colors.BOLD) line: str = self._colorize('═' * 60, Colors.BLUE) msg: str = f"\n{self._colorize('╔', Colors.BLUE)}{line}{self._colorize('╗', Colors.BLUE)}\n" msg += f"{self._colorize('║', Colors.BLUE)} {header}\n" msg += f"{self._colorize('╚', Colors.BLUE)}{line}{self._colorize('╝', Colors.BLUE)}" self._write(msg, "BANNER") def compile_error(self, message: str, file: str = "", line: int = 0, code: str = "") -> None: """编译错误格式化输出""" header: str = self._colorize("编译错误", Colors.BG_RED + Colors.WHITE + Colors.BOLD) msg: str = f"\n{self._colorize('╔', Colors.RED)}{header}{self._colorize('╗', Colors.RED)}\n" if file and line > 0: location: str = f"{self._colorize(file, Colors.CYAN)}:{self._colorize(str(line), Colors.YELLOW)}" msg += f"{self._colorize('║', Colors.RED)} 位置:{location}\n" if code: msg += f"{self._colorize('║', Colors.RED)} 代码:{self._colorize(code, Colors.WHITE)}\n" msg += f"{self._colorize('║', Colors.RED)} 错误:{self._colorize(message, Colors.BRIGHT_RED)}\n" msg += f"{self._colorize('╚', Colors.RED)}{self._colorize('═' * 50, Colors.RED)}{self._colorize('╝', Colors.RED)}" self._write(msg, "ERROR") def compile_warning(self, message: str, file: str = "", line: int = 0, code: str = "") -> None: """编译警告格式化输出""" header: str = self._colorize("警告", Colors.BG_YELLOW + Colors.BLACK) msg: str = f"\n{self._colorize('╔', Colors.YELLOW)}{header}{self._colorize('╗', Colors.YELLOW)}\n" if file and line > 0: location: str = f"{self._colorize(file, Colors.CYAN)}:{self._colorize(str(line), Colors.YELLOW)}" msg += f"{self._colorize('║', Colors.YELLOW)} 位置:{location}\n" if code: msg += f"{self._colorize('║', Colors.YELLOW)} 代码:{self._colorize(code, Colors.WHITE)}\n" msg += f"{self._colorize('║', Colors.YELLOW)} 警告:{self._colorize(message, Colors.BRIGHT_YELLOW)}\n" msg += f"{self._colorize('╚', Colors.YELLOW)}{self._colorize('═' * 50, Colors.YELLOW)}{self._colorize('╝', Colors.YELLOW)}" self._write(msg, "WARNING") def summary(self, stats: dict[str, int | str]) -> None: """输出编译摘要""" header: str = self._colorize("编译摘要", Colors.BG_BLUE + Colors.WHITE + Colors.BOLD) msg: str = f"\n{self._colorize('╔', Colors.BLUE)}{header}{self._colorize('╗', Colors.BLUE)}\n" for key, value in stats.items(): color: str = Colors.GREEN if value > 0 else Colors.WHITE msg += f"{self._colorize('║', Colors.BLUE)} {key}: {self._colorize(str(value), color)}\n" msg += f"{self._colorize('╚', Colors.BLUE)}{self._colorize('═' * 50, Colors.BLUE)}{self._colorize('╝', Colors.BLUE)}" self._write(msg, "SUMMARY") def close(self) -> None: if self.file_handler: self.file_handler.close() self.info(f"日志已保存到:{self.log_file}") # 全局日志实例 _global_logger: Optional[Logger] = None def get_logger(name: str = "TransPyC", log_file: Optional[str] = None, level: int = LogLevel.INFO, use_colors: bool = True) -> Logger: global _global_logger if _global_logger is None: _global_logger = Logger(name, log_file, level, use_colors) return _global_logger def set_logger(logger: Logger) -> None: global _global_logger _global_logger = logger # 便捷函数 def debug(msg: str, category: str = "") -> None: get_logger().debug(msg, category) def info(msg: str, category: str = "") -> None: get_logger().info(msg, category) def warning(msg: str, category: str = "") -> None: get_logger().warning(msg, category) def error(msg: str, category: str = "", exception: Exception | None = None) -> None: get_logger().error(msg, category, exception) def success(msg: str, category: str = "") -> None: get_logger().success(msg, category) def safe_call(func: Callable[..., Any], *args: Any, context: str = "", **kwargs: Any) -> Any: """安全调用函数,在异常时根据模式处理。 - strict 模式:重新抛出异常 - relaxed 模式:打印彩色警告日志并返回 None Args: func: 要调用的可调用对象 *args, **kwargs: 传给 func 的参数 context: 描述性上下文(用于日志消息) Returns: func 的返回值,或 None(异常时) """ try: return func(*args, **kwargs) except Exception as e: msg = f"{context}: {type(e).__name__}: {e}" if context else f"{type(e).__name__}: {e}" if _ConfigMode == "strict": raise get_logger().warning(msg, "Exception") return None