File size: 5,380 Bytes
94fd0b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
霜云(Shimokumo) - 日志工具模块

支持彩色终端日志输出和文件日志记录。
提供DEBUG、INFO、WARNING、ERROR四个级别。
"""

import logging
import os
import sys
from datetime import datetime
from typing import Optional


class ColoredFormatter(logging.Formatter):
    """彩色日志格式化器"""

    # ANSI颜色代码
    COLORS = {
        "DEBUG": "\033[36m",      # 青色
        "INFO": "\033[32m",       # 绿色
        "WARNING": "\033[33m",    # 黄色
        "ERROR": "\033[31m",      # 红色
        "CRITICAL": "\033[35m",   # 紫色
    }
    RESET = "\033[0m"

    def format(self, record: logging.LogRecord) -> str:
        """格式化日志记录,添加颜色"""
        color = self.COLORS.get(record.levelname, self.RESET)
        # 保存原始level名称
        record.levelname_colored = f"{color}{record.levelname}{self.RESET}"
        return super().format(record)


class ShimokumoLogger:
    """霜云专用日志管理器

    提供统一的日志接口,支持彩色终端输出和文件记录。

    用法:
        logger = ShimokumoLogger(log_level="DEBUG", log_file="app.log")
        logger.info("服务启动成功")
        logger.error("连接失败", exc_info=True)
    """

    # 日志格式模板
    CONSOLE_FORMAT = (
        "\033[90m%(asctime)s\033[0m | "
        "%(levelname_colored)-8s | "
        "\033[90m%(name)s\033[0m | "
        "%(message)s"
    )
    FILE_FORMAT = (
        "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s"
    )
    DATE_FORMAT = "%Y-%m-%d %H:%M:%S"

    def __init__(
        self,
        name: str = "Shimokumo",
        log_level: str = "INFO",
        log_file: Optional[str] = None,
        console_output: bool = True,
    ):
        """
        初始化日志管理器。

        Args:
            name: 日志器名称
            log_level: 日志级别 (DEBUG/INFO/WARNING/ERROR)
            log_file: 日志文件路径,为None则不写入文件
            console_output: 是否输出到终端
        """
        self.name = name
        self.log_level = getattr(logging, log_level.upper(), logging.INFO)
        self.log_file = log_file
        self._logger = self._setup_logger(console_output)

    def _setup_logger(self, console_output: bool) -> logging.Logger:
        """配置日志器"""
        logger = logging.getLogger(self.name)
        logger.setLevel(self.log_level)
        # 清除已有handler防止重复
        logger.handlers.clear()

        # 终端输出handler
        if console_output:
            console_handler = logging.StreamHandler(sys.stdout)
            console_handler.setLevel(self.log_level)
            console_formatter = ColoredFormatter(
                self.CONSOLE_FORMAT,
                datefmt=self.DATE_FORMAT,
            )
            console_handler.setFormatter(console_formatter)
            logger.addHandler(console_handler)

        # 文件输出handler
        if self.log_file:
            log_dir = os.path.dirname(self.log_file)
            if log_dir:
                os.makedirs(log_dir, exist_ok=True)

            file_handler = logging.FileHandler(
                self.log_file,
                encoding="utf-8",
                mode="a",
            )
            file_handler.setLevel(self.log_level)
            file_formatter = logging.Formatter(
                self.FILE_FORMAT,
                datefmt=self.DATE_FORMAT,
            )
            file_handler.setFormatter(file_formatter)
            logger.addHandler(file_handler)

        return logger

    def debug(self, msg: str, **kwargs) -> None:
        """输出DEBUG级别日志"""
        self._logger.debug(msg, **kwargs)

    def info(self, msg: str, **kwargs) -> None:
        """输出INFO级别日志"""
        self._logger.info(msg, **kwargs)

    def warning(self, msg: str, **kwargs) -> None:
        """输出WARNING级别日志"""
        self._logger.warning(msg, **kwargs)

    def error(self, msg: str, **kwargs) -> None:
        """输出ERROR级别日志"""
        self._logger.error(msg, **kwargs)

    def critical(self, msg: str, **kwargs) -> None:
        """输出CRITICAL级别日志"""
        self._logger.critical(msg, **kwargs)

    def exception(self, msg: str, **kwargs) -> None:
        """输出异常信息(ERROR级别,包含堆栈跟踪)"""
        self._logger.exception(msg, **kwargs)

    @staticmethod
    def get_logger(
        name: str = "Shimokumo",
        log_level: str = "INFO",
        log_file: Optional[str] = None,
    ) -> "ShimokumoLogger":
        """
        工厂方法:获取或创建日志实例。

        Args:
            name: 日志器名称
            log_level: 日志级别
            log_file: 日志文件路径

        Returns:
            ShimokumoLogger实例
        """
        return ShimokumoLogger(
            name=name,
            log_level=log_level,
            log_file=log_file,
        )


def get_logger(
    name: str = "Shimokumo",
    log_level: str = "INFO",
    log_file: Optional[str] = None,
) -> ShimokumoLogger:
    """
    快捷函数:获取霜云日志器。

    Args:
        name: 日志器名称
        log_level: 日志级别
        log_file: 日志文件路径

    Returns:
        ShimokumoLogger实例
    """
    return ShimokumoLogger.get_logger(name, log_level, log_file)