Log.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import logging
  2. import sys
  3. import traceback
  4. from datetime import datetime
  5. from pathlib import Path
  6. from Config.Config import Config
  7. class Logger:
  8. def __init__(self, log_path: str = None, log_name: str = None):
  9. """
  10. 初始化日志记录器
  11. Args:
  12. log_path: 日志存储路径,默认为项目根目录下的Log文件夹
  13. log_name: 日志文件名,默认为当前日期时间
  14. """
  15. # 设置默认日志路径为项目根目录下的Log文件夹
  16. if log_path is None:
  17. project_root = self._find_project_root()
  18. log_path = project_root / "Log"
  19. # 创建日志目录(如果不存在)
  20. self.log_path = Path(log_path)
  21. self.log_path.mkdir(parents=True, exist_ok=True)
  22. # 设置日志文件名
  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. # 配置日志记录器
  27. self.logger = logging.getLogger(__name__)
  28. self.logger.setLevel(Config.log_level)
  29. # 清除现有处理器(避免重复记录)
  30. self.logger.handlers.clear()
  31. # 创建格式化器
  32. formatter = logging.Formatter(Config.log_format)
  33. # 文件处理器(记录所有级别日志)
  34. file_handler = logging.FileHandler(self.log_file, encoding='utf-8')
  35. file_handler.setLevel(logging.DEBUG)
  36. file_handler.setFormatter(formatter)
  37. self.logger.addHandler(file_handler)
  38. # 控制台处理器(仅记录INFO及以上级别)
  39. console_handler = logging.StreamHandler()
  40. console_handler.setLevel(logging.INFO)
  41. console_handler.setFormatter(formatter)
  42. self.logger.addHandler(console_handler)
  43. # 设置异常钩子,捕获未处理的异常
  44. sys.excepthook = self._handle_exception
  45. # 重定向标准输出和标准错误
  46. self._redirect_stdout_stderr()
  47. def _find_project_root(self):
  48. """查找项目根目录"""
  49. # 查找包含.git、.project、setup.py或requirements.txt等标记文件的目录
  50. current_dir = Path(__file__).resolve().parent
  51. # 向上查找直到找到项目根目录标记
  52. for parent in [current_dir] + list(current_dir.parents):
  53. if any((parent / marker).exists() for marker in
  54. ['.git', '.project', 'setup.py', 'requirements.txt', 'pyproject.toml']):
  55. return parent
  56. # 如果找不到标记文件,则使用当前工作目录
  57. return Path.cwd()
  58. def _redirect_stdout_stderr(self):
  59. """
  60. 重定向标准输出和标准错误到日志
  61. :return:
  62. """
  63. class StreamToLogger:
  64. def __init__(self, logger, level):
  65. self.logger = logger
  66. self.level = level
  67. self.linebuf = ''
  68. def write(self, buf):
  69. for line in buf.rstrip().splitlines():
  70. if line.strip(): # 忽略空行
  71. self.logger.log(self.level, line.rstrip())
  72. def flush(self):
  73. pass
  74. # 重定向标准输出到INFO级别
  75. sys.stdout = StreamToLogger(self.logger, logging.INFO)
  76. # 重定向标准错误到ERROR级别
  77. sys.stderr = StreamToLogger(self.logger, logging.ERROR)
  78. def _handle_exception(self, exc_type, exc_value, exc_traceback):
  79. """处理未捕获的异常"""
  80. # 忽略KeyboardInterrupt,以便控制台可以正常退出
  81. if issubclass(exc_type, KeyboardInterrupt):
  82. sys.__excepthook__(exc_type, exc_value, exc_traceback)
  83. return
  84. self.logger.error(
  85. "未捕获的异常:",
  86. exc_info=(exc_type, exc_value, exc_traceback)
  87. )
  88. def debug(self, msg):
  89. self.logger.debug(msg)
  90. def info(self, msg):
  91. self.logger.info(msg)
  92. def warning(self, msg):
  93. self.logger.warning(msg)
  94. def error(self, msg):
  95. self.logger.error(msg)
  96. def critical(self, msg):
  97. self.logger.critical(msg)
  98. def exception(self, msg, exc_info=True):
  99. """记录异常信息,包括堆栈跟踪"""
  100. self.logger.exception(msg, exc_info=exc_info)
  101. def log_exception(self, e, context=""):
  102. """记录异常及其上下文信息"""
  103. error_msg = f"{context}: {type(e).__name__}: {str(e)}"
  104. self.error(error_msg)
  105. self.error("堆栈跟踪:\n" + "".join(traceback.format_tb(e.__traceback__)))
  106. # 使用示例
  107. if __name__ == "__main__":
  108. # 创建日志记录器实例(使用默认路径)
  109. logger = Logger(log_name="my_app.log")
  110. # 记录不同级别的日志
  111. logger.debug("这是一条调试信息")
  112. logger.info("程序启动成功")
  113. logger.warning("磁盘空间不足")
  114. # 模拟一个异常
  115. try:
  116. result = 1 / 0
  117. except Exception as e:
  118. logger.log_exception(e, "除法运算时发生错误")
  119. # 模拟控制台输出
  120. print("这是一条普通的控制台输出")
  121. # 模拟控制台错误输出
  122. sys.stderr.write("这是一条错误输出\n")
  123. # 模拟未捕获的异常
  124. def cause_exception():
  125. raise ValueError("这是一个未捕获的异常")
  126. # 调用会抛出异常的函数
  127. cause_exception()
  128. logger.info("程序执行完成")