import inspect
import json
from loguru import logger
import os
import sys
from typing import Optional, Union
LOG_COLORS = {
"DEBUG": "\033[1;36m", # CYAN
"INFO": "\033[1;32m", # GREEN
"WARNING": "\033[1;33m", # YELLOW
"ERROR": "\033[1;31m", # RED
"CRITICAL": "\033[1;31m", # RED
"EXCEPTION": "\033[1;31m", # RED
}
COLOR_RESET = "\033[1;0m"
[文档]
class Log:
# 静态标记:确保只移除一次默认 handler
_default_handler_removed = False
def __init__(
self,
filename: Optional[str] = None,
cmdlevel: str = "DEBUG",
filelevel: str = "INFO",
backup_count: int = 7, # 默认保留7天/7个文件
limit: Union[int, str] = "20 MB", # 支持字符串格式
when: Optional[str] = None,
colorful: bool = True,
compression: Optional[str] = None, # 新增压缩功能
is_backtrace: bool = True,
):
# 只移除 loguru 默认的 stderr handler (ID 0),避免删除其他模块已添加的 handlers
if not Log._default_handler_removed:
try:
logger.remove(0) # 只移除默认 handler
except ValueError:
pass # 默认 handler 可能已被移除
Log._default_handler_removed = True
self.is_backtrace = is_backtrace
self.logger = logger.bind(task=filename)
# 设置日志文件路径
if filename is None:
filename = getattr(sys.modules["__main__"], "__file__", "log.py")
filename = os.path.basename(filename.replace(".py", ".log"))
# 确保日志目录存在
log_dir = os.path.abspath(os.path.dirname(filename))
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# 控制台输出配置
self.logger.add(
sys.stderr,
level=cmdlevel,
format=self._formatter,
colorize=colorful,
backtrace=True,
enqueue=True,
filter=lambda record: record["extra"].get("task") == filename,
)
# 文件输出配置
rotation_config = self._get_rotation_config(when, limit)
self.logger.add(
filename,
level=filelevel,
format=self._formatter,
backtrace=True,
rotation=rotation_config,
retention=f"{backup_count} days",
compression=compression,
enqueue=True,
filter=lambda record: record["extra"].get("task") == filename,
)
def _formatter(self, record):
# 处理消息内容
message = str(record["message"]).replace("{", "{{").replace("}", "}}")
if isinstance(message, (dict, list)):
try:
message = json.dumps(message, ensure_ascii=False)
except TypeError:
message = str(message)
# 处理调用栈和颜色
if self.is_backtrace:
frame = inspect.currentframe()
while frame:
if (
"loguru" not in frame.f_code.co_filename
and "base_log.py" not in frame.f_code.co_filename
):
break
frame = frame.f_back
file_info = (
f"[{os.path.basename(frame.f_code.co_filename)}:{frame.f_code.co_name}:{frame.f_lineno}]"
if frame
else f"[{record['file']}:{record['function']}:{record['line']}]"
)
else:
file_info = f"[{record['file']}:{record['line']}]"
level_color = LOG_COLORS.get(record["level"].name, "")
return (
f"{level_color}[{record['time'].strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}] "
+ file_info
+ f"[{record['level']}] {message}{COLOR_RESET}\n"
)
def _get_rotation_config(self, when: Optional[str], limit: Union[int, str]):
if when: # 时间轮转
return when # "D"(天)、"H"(小时)、"midnight"等
else: # 大小轮转
if isinstance(limit, int):
return f"{limit / 1024 / 1024} MB"
return limit # 直接支持"10 MB"、"1 GB"等字符串格式
[文档]
@staticmethod
def set_logger(**kwargs) -> bool:
"""For backward compatibility."""
return True
[文档]
def debug(self, *args, **kwargs):
self.logger.debug(*args, **kwargs)
[文档]
def info(self, *args, **kwargs):
self.logger.info(*args, **kwargs)
[文档]
def warning(self, *args, **kwargs):
self.logger.warning(*args, **kwargs)
[文档]
def error(self, *args, **kwargs):
self.logger.error(*args, **kwargs)
[文档]
def critical(self, *args, **kwargs):
self.logger.critical(*args, **kwargs)
[文档]
def set_config(
self,
filename: Optional[str] = None,
cmdlevel: str = "DEBUG",
filelevel: str = "INFO",
backup_count: int = 7,
limit: Union[int, str] = "20 MB",
when: Optional[str] = None,
colorful: bool = True,
compression: Optional[str] = None,
):
"""动态修改日志配置"""
# 移除旧的handler
self.logger.remove()
# 重新添加handler
if filename is None:
filename = getattr(sys.modules["__main__"], "__file__", "log.py")
filename = os.path.basename(filename.replace(".py", ".log"))
# 确保日志目录存在
log_dir = os.path.abspath(os.path.dirname(filename))
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# 控制台输出配置
self.logger.add(
sys.stderr,
level=cmdlevel,
format=self._formatter,
colorize=colorful,
backtrace=True,
enqueue=True,
filter=lambda record: record["extra"].get("task") == filename,
)
# 文件输出配置
rotation_config = self._get_rotation_config(when, limit)
self.logger.add(
filename,
level=filelevel,
format=self._formatter,
backtrace=True,
rotation=rotation_config,
retention=f"{backup_count} days",
compression=compression,
enqueue=True,
filter=lambda record: record["extra"].get("task") == filename,
)
[文档]
def exception(self, *args, **kwargs):
self.logger.exception(*args, **kwargs)