| |
| """ |
| 日志工具模块 - 支持时间戳和颜色输出 |
| """ |
| import sys |
| import logging |
| from datetime import datetime |
|
|
| try: |
| from colorama import init, Fore, Style, Back |
| init(autoreset=True) |
| COLORAMA_AVAILABLE = True |
| except ImportError: |
| COLORAMA_AVAILABLE = False |
| |
| class Fore: |
| LIGHTBLACK_EX = GREEN = YELLOW = RED = CYAN = BLUE = MAGENTA = WHITE = LIGHTGREEN_EX = LIGHTCYAN_EX = LIGHTYELLOW_EX = LIGHTMAGENTA_EX = "" |
| class Style: |
| RESET_ALL = BRIGHT = DIM = "" |
| class Back: |
| pass |
|
|
|
|
| class Logger: |
| """统一日志工具""" |
|
|
| SAFE_CHAR_MAP = { |
| "✓": "[OK] ", |
| "✗": "[X] ", |
| "→": "->", |
| "◆": "*", |
| } |
|
|
| COLORS = { |
| "DEBUG": Fore.LIGHTBLACK_EX, |
| "INFO": Fore.GREEN, |
| "SUCCESS": Fore.LIGHTGREEN_EX, |
| "WARNING": Fore.YELLOW, |
| "ERROR": Fore.RED, |
| "STEP": Fore.CYAN, |
| "DETAIL": Fore.LIGHTCYAN_EX, |
| "PROGRESS": Fore.MAGENTA, |
| "MODEL": Fore.LIGHTMAGENTA_EX, |
| "AUDIO": Fore.BLUE, |
| "CONFIG": Fore.LIGHTYELLOW_EX, |
| } |
|
|
| RESET = Style.RESET_ALL |
| BRIGHT = Style.BRIGHT |
| DIM = Style.DIM |
|
|
| |
| verbose = True |
|
|
| @staticmethod |
| def _sanitize_console_text(text: str) -> str: |
| """将不兼容当前终端编码的字符替换为安全文本。""" |
| sanitized = text |
| for src, dst in Logger.SAFE_CHAR_MAP.items(): |
| sanitized = sanitized.replace(src, dst) |
| return sanitized |
|
|
| @staticmethod |
| def _emit(text: str): |
| """安全输出到终端,避免 Windows/GBK 控制台因 Unicode 崩溃。""" |
| try: |
| print(text, flush=True) |
| return |
| except UnicodeEncodeError: |
| pass |
|
|
| fallback = Logger._sanitize_console_text(text) |
| encoding = getattr(sys.stdout, "encoding", None) or "utf-8" |
| try: |
| print( |
| fallback.encode(encoding, errors="replace").decode(encoding), |
| flush=True, |
| ) |
| except Exception: |
| print( |
| fallback.encode("ascii", errors="replace").decode("ascii"), |
| flush=True, |
| ) |
|
|
| @staticmethod |
| def _log(level: str, msg: str, force_print: bool = True): |
| """内部日志方法""" |
| timestamp = datetime.now().strftime("%H:%M:%S") |
| color = Logger.COLORS.get(level, "") |
| reset = Logger.RESET |
|
|
| |
| if level in ("INFO", "STEP", "SUCCESS"): |
| prefix = "" |
| elif level == "DETAIL": |
| prefix = " → " |
| elif level == "PROGRESS": |
| prefix = " ◆ " |
| elif level == "MODEL": |
| prefix = "[模型] " |
| elif level == "AUDIO": |
| prefix = "[音频] " |
| elif level == "CONFIG": |
| prefix = "[配置] " |
| else: |
| prefix = f"[{level}] " |
|
|
| output = f"{color}[{timestamp}]{prefix}{msg}{reset}" |
| Logger._emit(output) |
|
|
| @staticmethod |
| def debug(msg: str): |
| """调试日志 (灰色) - 仅在verbose模式下显示""" |
| if Logger.verbose: |
| Logger._log("DEBUG", msg) |
|
|
| @staticmethod |
| def info(msg: str): |
| """信息日志 (绿色)""" |
| Logger._log("INFO", msg) |
|
|
| @staticmethod |
| def success(msg: str): |
| """成功日志 (亮绿色)""" |
| Logger._log("SUCCESS", f"✓ {msg}") |
|
|
| @staticmethod |
| def warning(msg: str): |
| """警告日志 (黄色)""" |
| Logger._log("WARNING", msg) |
|
|
| @staticmethod |
| def error(msg: str): |
| """错误日志 (红色)""" |
| Logger._log("ERROR", msg) |
|
|
| @staticmethod |
| def step(current: int, total: int, msg: str): |
| """步骤日志 (青色)""" |
| timestamp = datetime.now().strftime("%H:%M:%S") |
| color = Logger.COLORS.get("STEP", "") |
| reset = Logger.RESET |
| Logger._emit(f"{color}[{timestamp}][{current}/{total}] {msg}{reset}") |
|
|
| @staticmethod |
| def detail(msg: str): |
| """详细日志 (浅青色) - 用于显示处理细节""" |
| if Logger.verbose: |
| Logger._log("DETAIL", msg) |
|
|
| @staticmethod |
| def progress(msg: str): |
| """进度日志 (紫色) - 用于显示处理进度""" |
| Logger._log("PROGRESS", msg) |
|
|
| @staticmethod |
| def model(msg: str): |
| """模型日志 (浅紫色) - 用于模型加载/卸载信息""" |
| Logger._log("MODEL", msg) |
|
|
| @staticmethod |
| def audio(msg: str): |
| """音频日志 (蓝色) - 用于音频处理信息""" |
| Logger._log("AUDIO", msg) |
|
|
| @staticmethod |
| def config(msg: str): |
| """配置日志 (浅黄色) - 用于配置信息""" |
| if Logger.verbose: |
| Logger._log("CONFIG", msg) |
|
|
| @staticmethod |
| def header(msg: str): |
| """标题日志 (带分隔线)""" |
| timestamp = datetime.now().strftime("%H:%M:%S") |
| color = Logger.COLORS.get("INFO", "") |
| reset = Logger.RESET |
| Logger._emit(f"{color}[{timestamp}] {'=' * 50}{reset}") |
| Logger._emit(f"{color}[{timestamp}] {msg}{reset}") |
| Logger._emit(f"{color}[{timestamp}] {'=' * 50}{reset}") |
|
|
| @staticmethod |
| def separator(char: str = "-", length: int = 40): |
| """分隔线""" |
| timestamp = datetime.now().strftime("%H:%M:%S") |
| color = Logger.COLORS.get("DEBUG", "") |
| reset = Logger.RESET |
| Logger._emit(f"{color}[{timestamp}] {char * length}{reset}") |
|
|
| @staticmethod |
| def set_verbose(enabled: bool): |
| """设置详细日志模式""" |
| Logger.verbose = enabled |
|
|
|
|
| |
| log = Logger() |
|
|
|
|
| |
|
|
| class ColoredFormatter(logging.Formatter): |
| """为标准logging模块添加颜色支持""" |
|
|
| LEVEL_COLORS = { |
| logging.DEBUG: Fore.LIGHTBLACK_EX, |
| logging.INFO: Fore.GREEN, |
| logging.WARNING: Fore.YELLOW, |
| logging.ERROR: Fore.RED, |
| logging.CRITICAL: Fore.RED + Style.BRIGHT, |
| } |
|
|
| def format(self, record): |
| |
| color = self.LEVEL_COLORS.get(record.levelno, "") |
| reset = Style.RESET_ALL |
|
|
| |
| timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
|
| |
| level_name = record.levelname |
| module_name = record.name |
|
|
| |
| formatted = f"{color}{timestamp} | {level_name} | {module_name} | {record.getMessage()}{reset}" |
| return formatted |
|
|
|
|
| def setup_colored_logging(level=logging.INFO): |
| """配置全局logging使用颜色输出""" |
| |
| root_logger = logging.getLogger() |
| root_logger.setLevel(level) |
|
|
| |
| for handler in root_logger.handlers[:]: |
| root_logger.removeHandler(handler) |
|
|
| |
| console_handler = logging.StreamHandler(sys.stdout) |
| console_handler.setLevel(level) |
| console_handler.setFormatter(ColoredFormatter()) |
| root_logger.addHandler(console_handler) |
|
|
| return root_logger |
|
|
|
|
| |
| setup_colored_logging(logging.INFO) |
|
|
| |
| logging.getLogger("faiss").setLevel(logging.WARNING) |
| logging.getLogger("audio_separator").setLevel(logging.WARNING) |
|
|