Log.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import logging
  2. from datetime import datetime
  3. from pathlib import Path
  4. import atexit
  5. from typing import Optional
  6. from Config.Config import Config
  7. class Logger:
  8. def __init__(self, log_path: Optional[str] = None, log_name: Optional[str] = None):
  9. """
  10. 初始化日志记录器
  11. Args:
  12. log_path: 日志存储路径,默认为项目根目录下的Log文件夹
  13. log_name: 日志文件名,默认为当前日期时间
  14. """
  15. # 1. 处理日志路径(默认项目根目录/Logs,不存在则创建)
  16. if log_path is None:
  17. project_root = self._find_project_root()
  18. self.log_path = project_root / "Logs"
  19. else:
  20. self.log_path = Path(log_path)
  21. self.log_path.mkdir(parents=True, exist_ok=True) # 递归创建目录
  22. # 2. 处理日志文件名(默认格式:log_年月日_时分秒.log)
  23. if log_name is None:
  24. log_name = f"log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
  25. self.log_file = self.log_path / log_name
  26. # 3. 初始化日志记录器(避免多实例冲突)
  27. self.logger = logging.getLogger(f"{__name__}_{id(self)}")
  28. self.logger.setLevel(Config.log_level)
  29. self.logger.handlers.clear() # 清除默认处理器,防止重复记录
  30. # 4. 创建文件日志处理器(核心功能,UTF-8编码避免中文乱码)
  31. self.file_handler = None
  32. try:
  33. self.file_handler = logging.FileHandler(self.log_file, encoding='utf-8')
  34. self.file_handler.setLevel(logging.DEBUG) # 文件记录所有级别日志
  35. # 应用配置的日志格式
  36. formatter = logging.Formatter(Config.log_format)
  37. self.file_handler.setFormatter(formatter)
  38. self.logger.addHandler(self.file_handler)
  39. except Exception as e:
  40. # 日志文件创建失败时,仅简单打印(无控制台日志,故用原始print)
  41. print(f"创建日志文件失败: {e}")
  42. # 5. 注册退出清理函数(程序结束时关闭日志文件)
  43. atexit.register(self.cleanup)
  44. # 6. 记录初始化完成日志
  45. if self._is_logging_available():
  46. self.logger.info(f"日志系统初始化完成,日志文件: {self.log_file}")
  47. def _find_project_root(self):
  48. """查找项目根目录(基于常见项目标记文件)"""
  49. current_dir = Path(__file__).resolve().parent
  50. # 向上遍历目录,直到找到项目根目录标记
  51. for parent in [current_dir] + list(current_dir.parents):
  52. if any((parent / marker).exists() for marker in
  53. ['.git', '.project', 'setup.py', 'requirements.txt', 'pyproject.toml']):
  54. return parent
  55. # 未找到标记时,使用当前工作目录
  56. return Path.cwd()
  57. def _is_logging_available(self):
  58. """检查日志系统是否可用"""
  59. return self.file_handler is not None and self.file_handler in self.logger.handlers
  60. def cleanup(self):
  61. """清理资源:关闭日志文件处理器"""
  62. if self.file_handler:
  63. try:
  64. self.file_handler.close()
  65. self.logger.removeHandler(self.file_handler)
  66. except Exception as e:
  67. print(f"关闭日志文件时出错: {e}")
  68. # ------------------------------ 核心日志方法------------------------------
  69. def debug(self, msg):
  70. if self._is_logging_available():
  71. self.logger.debug(msg)
  72. def info(self, msg):
  73. if self._is_logging_available():
  74. self.logger.info(msg)
  75. def warning(self, msg):
  76. if self._is_logging_available():
  77. self.logger.warning(msg)
  78. def error(self, msg):
  79. if self._is_logging_available():
  80. self.logger.error(msg)
  81. def critical(self, msg):
  82. if self._is_logging_available():
  83. self.logger.critical(msg)
  84. def exception(self, msg, exc_info=True):
  85. """记录异常(简化版:仅文件日志,含堆栈)"""
  86. if self._is_logging_available():
  87. self.logger.exception(msg, exc_info=exc_info)
  88. def log_exception(self, e, context=""):
  89. """记录异常及上下文(简化版:仅文件日志)"""
  90. if self._is_logging_available():
  91. error_msg = f"{context}: {type(e).__name__}: {str(e)}"
  92. self.error(error_msg)
  93. # 手动添加堆栈跟踪
  94. import traceback
  95. self.error("堆栈跟踪:\n" + "".join(traceback.format_tb(e.__traceback__)))
  96. # ------------------------------ 快捷日志创建方法------------------------------
  97. def log_for_api():
  98. return Logger(log_path="Logs/API", log_name="api_test.log")
  99. def log_for_ui():
  100. return Logger(log_path="Logs/UI", log_name="ui_test.log")