logger.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import atexit
  2. import logging
  3. import sys
  4. from loguru import logger
  5. from typing_extensions import override
  6. from app.config.path_conf import LOG_DIR
  7. from app.config.setting import settings
  8. # 全局变量记录日志处理器ID
  9. _logger_handlers = []
  10. class InterceptHandler(logging.Handler):
  11. """
  12. 日志拦截处理器:将所有 Python 标准日志重定向到 Loguru
  13. 工作原理:
  14. 1. 继承自 logging.Handler
  15. 2. 重写 emit 方法处理日志记录
  16. 3. 将标准库日志转换为 Loguru 格式
  17. """
  18. @override
  19. def emit(self, record: logging.LogRecord) -> None:
  20. """
  21. 将标准库 LogRecord 转发到 Loguru。
  22. 参数:
  23. - record (logging.LogRecord): 标准库日志记录。
  24. 返回:
  25. - None
  26. """
  27. # 尝试获取日志级别名称
  28. try:
  29. level = logger.level(record.levelname).name
  30. except ValueError:
  31. level = record.levelno
  32. # 获取调用帧信息,增加None检查
  33. frame, depth = logging.currentframe(), 2
  34. while frame and frame.f_code.co_filename == logging.__file__:
  35. frame = frame.f_back
  36. depth += 1
  37. # 使用 Loguru 记录日志
  38. logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
  39. def cleanup_logging() -> None:
  40. """
  41. 清理日志资源;在程序退出时调用,移除已注册的 Loguru 处理器。
  42. 返回:
  43. - None
  44. """
  45. global _logger_handlers
  46. for handler_id in _logger_handlers:
  47. try:
  48. logger.remove(handler_id)
  49. except ValueError:
  50. # 处理器已不存在,忽略
  51. pass
  52. _logger_handlers.clear()
  53. def setup_logging() -> None:
  54. """
  55. 配置日志系统:控制台彩色输出、文件轮转、错误日志分文件。
  56. 返回:
  57. - None
  58. """
  59. global _logger_handlers
  60. # 添加上下文信息
  61. _ = logger.configure(extra={"app_name": "FastapiAdmin"})
  62. # 步骤1:移除默认处理器
  63. logger.remove()
  64. # 步骤2:定义日志格式
  65. log_format = (
  66. # 时间信息
  67. "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
  68. # 日志级别,居中对齐
  69. "<level>{level: <8}</level> | "
  70. # 文件、函数和行号
  71. "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
  72. # 日志消息
  73. "<level>{message}</level>"
  74. )
  75. # 步骤3:配置控制台输出
  76. handler_id = logger.add(sys.stdout, format=log_format, level=settings.LOGGER_LEVEL)
  77. _logger_handlers.append(handler_id)
  78. # 步骤4:创建日志目录
  79. log_dir = LOG_DIR
  80. # 确保日志目录存在,如果不存在则创建
  81. log_dir.mkdir(parents=True, exist_ok=True)
  82. # 步骤5:配置常规日志文件
  83. handler_id = logger.add(
  84. str(log_dir / "info.log"),
  85. format=log_format,
  86. level="INFO",
  87. rotation="00:00", # 每天午夜轮转
  88. retention=30, # 日志保留天数,超过此天数的日志文件将被自动清理
  89. compression="gz",
  90. encoding="utf-8",
  91. )
  92. _logger_handlers.append(handler_id)
  93. # 步骤6:配置错误日志文件
  94. handler_id = logger.add(
  95. str(log_dir / "error.log"),
  96. format=log_format,
  97. level="ERROR",
  98. rotation="00:00", # 每天午夜轮转
  99. retention=30, # 日志保留天数,超过此天数的日志文件将被自动清理
  100. compression="gz",
  101. encoding="utf-8",
  102. backtrace=True,
  103. diagnose=True,
  104. )
  105. _logger_handlers.append(handler_id)
  106. # 步骤7:配置标准库日志
  107. logging.basicConfig(handlers=[InterceptHandler()], level=settings.LOGGER_LEVEL, force=True)
  108. logger_name_list = list(logging.root.manager.loggerDict)
  109. # 步骤8:配置第三方库日志
  110. for logger_name in logger_name_list:
  111. logger_ = logging.getLogger(logger_name)
  112. logger_.handlers = [InterceptHandler()]
  113. logger_.propagate = False
  114. # 注册退出清理函数
  115. atexit.register(cleanup_logging)
  116. log = logger