|
|
@@ -1,145 +1,121 @@
|
|
|
import logging
|
|
|
-import sys
|
|
|
-import traceback
|
|
|
from datetime import datetime
|
|
|
from pathlib import Path
|
|
|
+import atexit
|
|
|
+from typing import Optional
|
|
|
from Config.Config import Config
|
|
|
|
|
|
|
|
|
class Logger:
|
|
|
- def __init__(self, log_path: str = None, log_name: str = None):
|
|
|
+ def __init__(self, log_path: Optional[str] = None, log_name: Optional[str] = None):
|
|
|
"""
|
|
|
初始化日志记录器
|
|
|
Args:
|
|
|
log_path: 日志存储路径,默认为项目根目录下的Log文件夹
|
|
|
log_name: 日志文件名,默认为当前日期时间
|
|
|
"""
|
|
|
- # 设置默认日志路径为项目根目录下的Log文件夹
|
|
|
-
|
|
|
+ # 1. 处理日志路径(默认项目根目录/Logs,不存在则创建)
|
|
|
if log_path is None:
|
|
|
project_root = self._find_project_root()
|
|
|
- log_path = project_root / "Log"
|
|
|
- # 创建日志目录(如果不存在)
|
|
|
- self.log_path = Path(log_path)
|
|
|
- self.log_path.mkdir(parents=True, exist_ok=True)
|
|
|
- # 设置日志文件名
|
|
|
+ self.log_path = project_root / "Logs"
|
|
|
+ else:
|
|
|
+ self.log_path = Path(log_path)
|
|
|
+ self.log_path.mkdir(parents=True, exist_ok=True) # 递归创建目录
|
|
|
+
|
|
|
+ # 2. 处理日志文件名(默认格式:log_年月日_时分秒.log)
|
|
|
if log_name is None:
|
|
|
log_name = f"log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
|
|
self.log_file = self.log_path / log_name
|
|
|
- # 配置日志记录器
|
|
|
- self.logger = logging.getLogger(__name__)
|
|
|
+
|
|
|
+ # 3. 初始化日志记录器(避免多实例冲突)
|
|
|
+ self.logger = logging.getLogger(f"{__name__}_{id(self)}")
|
|
|
self.logger.setLevel(Config.log_level)
|
|
|
- # 清除现有处理器(避免重复记录)
|
|
|
- self.logger.handlers.clear()
|
|
|
- # 创建格式化器
|
|
|
- formatter = logging.Formatter(Config.log_format)
|
|
|
- # 文件处理器(记录所有级别日志)
|
|
|
- file_handler = logging.FileHandler(self.log_file, encoding='utf-8')
|
|
|
- file_handler.setLevel(logging.DEBUG)
|
|
|
- file_handler.setFormatter(formatter)
|
|
|
- self.logger.addHandler(file_handler)
|
|
|
- # 控制台处理器(仅记录INFO及以上级别)
|
|
|
- console_handler = logging.StreamHandler()
|
|
|
- console_handler.setLevel(logging.INFO)
|
|
|
- console_handler.setFormatter(formatter)
|
|
|
- self.logger.addHandler(console_handler)
|
|
|
- # 设置异常钩子,捕获未处理的异常
|
|
|
- sys.excepthook = self._handle_exception
|
|
|
- # 重定向标准输出和标准错误
|
|
|
- self._redirect_stdout_stderr()
|
|
|
+ self.logger.handlers.clear() # 清除默认处理器,防止重复记录
|
|
|
+
|
|
|
+ # 4. 创建文件日志处理器(核心功能,UTF-8编码避免中文乱码)
|
|
|
+ self.file_handler = None
|
|
|
+ try:
|
|
|
+ self.file_handler = logging.FileHandler(self.log_file, encoding='utf-8')
|
|
|
+ self.file_handler.setLevel(logging.DEBUG) # 文件记录所有级别日志
|
|
|
+ # 应用配置的日志格式
|
|
|
+ formatter = logging.Formatter(Config.log_format)
|
|
|
+ self.file_handler.setFormatter(formatter)
|
|
|
+ self.logger.addHandler(self.file_handler)
|
|
|
+ except Exception as e:
|
|
|
+ # 日志文件创建失败时,仅简单打印(无控制台日志,故用原始print)
|
|
|
+ print(f"创建日志文件失败: {e}")
|
|
|
+
|
|
|
+ # 5. 注册退出清理函数(程序结束时关闭日志文件)
|
|
|
+ atexit.register(self.cleanup)
|
|
|
+
|
|
|
+ # 6. 记录初始化完成日志
|
|
|
+ if self._is_logging_available():
|
|
|
+ self.logger.info(f"日志系统初始化完成,日志文件: {self.log_file}")
|
|
|
|
|
|
def _find_project_root(self):
|
|
|
- """查找项目根目录"""
|
|
|
- # 查找包含.git、.project、setup.py或requirements.txt等标记文件的目录
|
|
|
+ """查找项目根目录(基于常见项目标记文件)"""
|
|
|
current_dir = Path(__file__).resolve().parent
|
|
|
- # 向上查找直到找到项目根目录标记
|
|
|
+ # 向上遍历目录,直到找到项目根目录标记
|
|
|
for parent in [current_dir] + list(current_dir.parents):
|
|
|
if any((parent / marker).exists() for marker in
|
|
|
['.git', '.project', 'setup.py', 'requirements.txt', 'pyproject.toml']):
|
|
|
return parent
|
|
|
- # 如果找不到标记文件,则使用当前工作目录
|
|
|
+ # 未找到标记时,使用当前工作目录
|
|
|
return Path.cwd()
|
|
|
|
|
|
- def _redirect_stdout_stderr(self):
|
|
|
- """
|
|
|
- 重定向标准输出和标准错误到日志
|
|
|
- :return:
|
|
|
- """
|
|
|
- class StreamToLogger:
|
|
|
- def __init__(self, logger, level):
|
|
|
- self.logger = logger
|
|
|
- self.level = level
|
|
|
- self.linebuf = ''
|
|
|
-
|
|
|
- def write(self, buf):
|
|
|
- for line in buf.rstrip().splitlines():
|
|
|
- if line.strip(): # 忽略空行
|
|
|
- self.logger.log(self.level, line.rstrip())
|
|
|
-
|
|
|
- def flush(self):
|
|
|
- pass
|
|
|
- # 重定向标准输出到INFO级别
|
|
|
- sys.stdout = StreamToLogger(self.logger, logging.INFO)
|
|
|
- # 重定向标准错误到ERROR级别
|
|
|
- sys.stderr = StreamToLogger(self.logger, logging.ERROR)
|
|
|
-
|
|
|
- def _handle_exception(self, exc_type, exc_value, exc_traceback):
|
|
|
- """处理未捕获的异常"""
|
|
|
- # 忽略KeyboardInterrupt,以便控制台可以正常退出
|
|
|
- if issubclass(exc_type, KeyboardInterrupt):
|
|
|
- sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
|
|
- return
|
|
|
- self.logger.error(
|
|
|
- "未捕获的异常:",
|
|
|
- exc_info=(exc_type, exc_value, exc_traceback)
|
|
|
- )
|
|
|
+ def _is_logging_available(self):
|
|
|
+ """检查日志系统是否可用"""
|
|
|
+ return self.file_handler is not None and self.file_handler in self.logger.handlers
|
|
|
+
|
|
|
+ def cleanup(self):
|
|
|
+ """清理资源:关闭日志文件处理器"""
|
|
|
+ if self.file_handler:
|
|
|
+ try:
|
|
|
+ self.file_handler.close()
|
|
|
+ self.logger.removeHandler(self.file_handler)
|
|
|
+ except Exception as e:
|
|
|
+ print(f"关闭日志文件时出错: {e}")
|
|
|
|
|
|
+ # ------------------------------ 核心日志方法------------------------------
|
|
|
def debug(self, msg):
|
|
|
- self.logger.debug(msg)
|
|
|
+ if self._is_logging_available():
|
|
|
+ self.logger.debug(msg)
|
|
|
|
|
|
def info(self, msg):
|
|
|
- self.logger.info(msg)
|
|
|
+ if self._is_logging_available():
|
|
|
+ self.logger.info(msg)
|
|
|
|
|
|
def warning(self, msg):
|
|
|
- self.logger.warning(msg)
|
|
|
+ if self._is_logging_available():
|
|
|
+ self.logger.warning(msg)
|
|
|
|
|
|
def error(self, msg):
|
|
|
- self.logger.error(msg)
|
|
|
+ if self._is_logging_available():
|
|
|
+ self.logger.error(msg)
|
|
|
|
|
|
def critical(self, msg):
|
|
|
- self.logger.critical(msg)
|
|
|
+ if self._is_logging_available():
|
|
|
+ self.logger.critical(msg)
|
|
|
|
|
|
def exception(self, msg, exc_info=True):
|
|
|
- """记录异常信息,包括堆栈跟踪"""
|
|
|
- self.logger.exception(msg, exc_info=exc_info)
|
|
|
+ """记录异常(简化版:仅文件日志,含堆栈)"""
|
|
|
+ if self._is_logging_available():
|
|
|
+ self.logger.exception(msg, exc_info=exc_info)
|
|
|
|
|
|
def log_exception(self, e, context=""):
|
|
|
- """记录异常及其上下文信息"""
|
|
|
- error_msg = f"{context}: {type(e).__name__}: {str(e)}"
|
|
|
- self.error(error_msg)
|
|
|
- self.error("堆栈跟踪:\n" + "".join(traceback.format_tb(e.__traceback__)))
|
|
|
-
|
|
|
-
|
|
|
-# 使用示例
|
|
|
-if __name__ == "__main__":
|
|
|
- # 创建日志记录器实例(使用默认路径)
|
|
|
- logger = Logger(log_name="my_app.log")
|
|
|
- # 记录不同级别的日志
|
|
|
- logger.debug("这是一条调试信息")
|
|
|
- logger.info("程序启动成功")
|
|
|
- logger.warning("磁盘空间不足")
|
|
|
- # 模拟一个异常
|
|
|
- try:
|
|
|
- result = 1 / 0
|
|
|
- except Exception as e:
|
|
|
- logger.log_exception(e, "除法运算时发生错误")
|
|
|
- # 模拟控制台输出
|
|
|
- print("这是一条普通的控制台输出")
|
|
|
- # 模拟控制台错误输出
|
|
|
- sys.stderr.write("这是一条错误输出\n")
|
|
|
- # 模拟未捕获的异常
|
|
|
- def cause_exception():
|
|
|
- raise ValueError("这是一个未捕获的异常")
|
|
|
- # 调用会抛出异常的函数
|
|
|
- cause_exception()
|
|
|
- logger.info("程序执行完成")
|
|
|
+ """记录异常及上下文(简化版:仅文件日志)"""
|
|
|
+ if self._is_logging_available():
|
|
|
+ error_msg = f"{context}: {type(e).__name__}: {str(e)}"
|
|
|
+ self.error(error_msg)
|
|
|
+ # 手动添加堆栈跟踪
|
|
|
+ import traceback
|
|
|
+ self.error("堆栈跟踪:\n" + "".join(traceback.format_tb(e.__traceback__)))
|
|
|
+
|
|
|
+
|
|
|
+# ------------------------------ 快捷日志创建方法------------------------------
|
|
|
+def log_for_api():
|
|
|
+ return Logger(log_path="Logs/API", log_name="api_test.log")
|
|
|
+
|
|
|
+
|
|
|
+def log_for_ui():
|
|
|
+ return Logger(log_path="Logs/UI", log_name="ui_test.log")
|