类别:a(编译器缺陷)+ b(机制漏豁免)—— 类型解析链成环被当成"可忽略异常"吞掉, 上游根因不可见、声明静默退化。 - HandlesImports.py:978 / :1475 / :1583 三处新增 `except RecursionError: raise`, 对齐家族口径(HandlesBase.py:1060-1068:「RecursionError 不是可忽略异常, 是类型解析/声明发射链成环的真缺陷信号」)。成环路径: _TryLoadClassMembersFromPyi ↔ _EmitExternalClassDeclLlvm ↔ _TryLoadStructFromStub (每轮 4 帧),现场与 :2985 深探针记录一致(match.MatchOr.patterns、 stmts.Interactive.body 等"首字段即指针/容器"的类)。 修复前表现为一行 `WARN: HandlesImports: 忽略异常 maximum recursion depth exceeded` + 返回类型退化成 CInt / 形参退化成 i32。 - VLogger.py:warning() 的 exc_info 形参**从未被使用** —— 调用方传了异常却无任何 输出,吞异常点只剩一行 WARN、栈不可见。现与 error() 同口径打印 `Exception: <类型>: <消息>` + traceback(重复帧由 traceback 模块自动折叠为 "[Previous line repeated N more times]",不刷屏)。
328 lines
13 KiB
Python
328 lines
13 KiB
Python
"""
|
|
TransPyC 彩色日志系统
|
|
支持终端颜色输出和日志文件转储
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import os
|
|
import traceback
|
|
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}"
|
|
if exc_info is not None:
|
|
# 此前 exc_info 形参**从未被使用**:调用方 `_vlog().warning(..., exc_info=e)`
|
|
# 传了异常却无任何输出 ⇒ 吞异常点只剩一行 WARN、栈不可见,上游根因无从定位
|
|
# (如 `HandlesImports: 忽略异常 maximum recursion depth exceeded`)。
|
|
# 现与 error() 同口径打印异常类型/消息,并附 traceback;重复帧由 traceback
|
|
# 模块自动折叠为 "[Previous line repeated N more times]",不会刷屏。
|
|
msg += f"\n{self._colorize('│', Colors.YELLOW)} Exception: {type(exc_info).__name__}: {exc_info}"
|
|
tb_text: str = traceback.format_exc()
|
|
# 不在 except 块内调用时 format_exc() 返回 "NoneType: None",此时不附栈
|
|
if 'NoneType: None' not in tb_text:
|
|
tb_lines: str = tb_text.rstrip('\n').replace('\n', f"\n{self._colorize('│', Colors.YELLOW)} ")
|
|
msg += f"\n{self._colorize('│', Colors.YELLOW)} {tb_lines}"
|
|
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
|