AI-RVC / lib /logger.py
mason369's picture
Upload folder using huggingface_hub
b6f9c90 verified
# -*- coding: utf-8 -*-
"""
日志工具模块 - 支持时间戳和颜色输出
"""
import sys
import logging
from datetime import datetime
try:
from colorama import init, Fore, Style, Back
init(autoreset=True) # 初始化 colorama (Windows 兼容), autoreset确保每行重置
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()
# ============ 配置标准 logging 模块使用颜色 ============
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使用颜色输出"""
# 获取根logger
root_logger = logging.getLogger()
root_logger.setLevel(level)
# 移除现有的handlers
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
# 添加带颜色的handler
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(level)
console_handler.setFormatter(ColoredFormatter())
root_logger.addHandler(console_handler)
return root_logger
# 自动配置logging颜色
setup_colored_logging(logging.INFO)
# 抑制第三方库的英文日志
logging.getLogger("faiss").setLevel(logging.WARNING)
logging.getLogger("audio_separator").setLevel(logging.WARNING)