logger.py 4.5 KB

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