log_manager.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import logging
  2. import os
  3. import threading
  4. from logging.handlers import QueueHandler, QueueListener
  5. from queue import Queue
  6. class LogManager:
  7. _instance = None
  8. _lock = threading.Lock()
  9. _configured = False # 确保单例配置唯一性
  10. def __new__(cls, log_path="/home/kevin/kevin/zhaoyuan/zhaoyuan/log/app.log"):
  11. with cls._lock:
  12. if not cls._instance:
  13. cls._instance = super().__new__(cls)
  14. # 路径处理逻辑
  15. cls._instance._full_path = log_path
  16. cls._instance._init_logger()
  17. return cls._instance
  18. @classmethod
  19. def _validate_path(cls, path):
  20. """路径验证与创建"""
  21. default_path = os.path.join(os.getcwd(), "logs")
  22. target_path = path or default_path
  23. try:
  24. os.makedirs(target_path, exist_ok=True)
  25. # 测试写入权限
  26. test_file = os.path.join(target_path, "write_test.tmp")
  27. with open(test_file, "w") as f:
  28. f.write("permission_test")
  29. os.remove(test_file)
  30. return target_path
  31. except PermissionError:
  32. logging.error(f"Insufficient permissions for {target_path}, using default")
  33. os.makedirs(default_path, exist_ok=True)
  34. return default_path
  35. except Exception as e:
  36. logging.error(f"Path error: {str(e)}, using default")
  37. return default_path
  38. @staticmethod
  39. def _sanitize_filename(name):
  40. """文件名合法性过滤"""
  41. invalid_chars = {'/', '\\', ':', '*', '?', '"', '<', '>', '|'}
  42. cleaned = ''.join(c for c in name if c not in invalid_chars)
  43. return cleaned[:50] # 限制文件名长度
  44. def _init_logger(self):
  45. """初始化日志系统"""
  46. self.log_queue = Queue(-1)
  47. self.logger = logging.getLogger("GlobalLogger")
  48. self.logger.setLevel(logging.DEBUG)
  49. if not self.logger.handlers:
  50. # 创建带线程标识的格式器
  51. formatter = logging.Formatter(
  52. "[%(asctime)s][%(levelname)s][%(threadName)s] %(message)s"
  53. )
  54. # 文件处理器(自动UTF-8编码)
  55. file_handler = logging.FileHandler(
  56. self._full_path,
  57. encoding='utf-8',
  58. delay=True # 延迟打开文件直到实际写入
  59. )
  60. file_handler.setFormatter(formatter)
  61. # 控制台处理器(仅ERROR级别)
  62. console_handler = logging.StreamHandler()
  63. console_handler.setLevel(logging.ERROR)
  64. console_handler.setFormatter(formatter)
  65. # 异步监听器
  66. self.listener = QueueListener(
  67. self.log_queue,
  68. file_handler,
  69. console_handler,
  70. respect_handler_level=True
  71. )
  72. self.listener.start()
  73. # 队列处理器配置
  74. queue_handler = QueueHandler(self.log_queue)
  75. queue_handler.setLevel(logging.DEBUG)
  76. self.logger.addHandler(queue_handler)
  77. self.logger.propagate = False
  78. def get_logger(self):
  79. """获取线程安全日志器"""
  80. return self.logger
  81. @classmethod
  82. def shutdown(cls):
  83. """安全关闭日志系统"""
  84. if cls._instance:
  85. cls._instance.listener.stop()
  86. cls._instance = None
  87. # 使用示例
  88. if __name__ == "__main__":
  89. # 自定义路径和文件名
  90. custom_logger = LogManager(
  91. log_path="/home/kevin/kevin/zhaoyuan/zhaoyuan/log/runtime.log"
  92. ).get_logger()
  93. custom_logger.info("Custom logger configured successfully")
  94. # 默认配置
  95. default_logger = LogManager().get_logger()
  96. default_logger.warning("Using default configuration")
  97. # 安全关闭
  98. LogManager.shutdown()