File size: 7,388 Bytes
b6f9c90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# -*- 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)