# utils/logger.py import logging import os import traceback from logging.handlers import TimedRotatingFileHandler from config.settings import LogConfig # 延迟导入告警同步服务,避免循环导入 _alert_sync_imported = False _sync_alert_from_record = None def _import_alert_sync(): """延迟导入告警同步服务""" global _alert_sync_imported, _sync_alert_from_record if not _alert_sync_imported: try: from utils.alert_sync import sync_alert_from_record _sync_alert_from_record = sync_alert_from_record _alert_sync_imported = True except ImportError as e: print(f"告警同步模块导入失败: {e}") _alert_sync_imported = True # 避免重复尝试 class LevelBasedFormatter(logging.Formatter): """ 基于日志级别的格式化器 对不同级别的日志使用不同的格式 """ def __init__(self): # 普通日志格式 self.normal_format = logging.Formatter( "%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) # 详细日志格式(用于警告和错误) self.detailed_format = logging.Formatter( "%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(funcName)s() - %(message)s" ) def format(self, record): # 如果是警告或错误级别,使用详细格式 if record.levelno >= logging.WARNING: return self.detailed_format.format(record) else: return self.normal_format.format(record) class AlertSyncHandler(logging.Handler): """ 告警同步处理器 拦截WARNING及以上级别的日志,同步到主系统 """ def __init__(self): super().__init__() self.setLevel(logging.WARNING) # 只处理WARNING及以上级别 def emit(self, record): """ 处理日志记录,同步告警到主系统 Args: record: 日志记录 """ pass # try: # # 过滤登录相关的日志,防止循环触发 # # if self._is_login_related_log(record): # # return # # # 延迟导入告警同步服务 # # _import_alert_sync() # # # # # 如果告警同步服务可用,同步告警 # # if _sync_alert_from_record: # # _sync_alert_from_record(record) # # except Exception: # # 静默处理错误,避免影响正常日志记录 # pass def _is_login_related_log(self, record) -> bool: """ 判断是否为登录相关的日志 Args: record: 日志记录 Returns: bool: True 表示是登录相关日志,需要过滤 """ try: # 检查日志来源是否为同步服务 if record.name == "services.sync_service": return True # 检查日志内容是否包含登录关键词 login_keywords = [ "登录失败", "登录token", "获取登录", "token失败", "调用登录接口失败", "解析登录响应", "登录频率过高", "login", "token", "authentication", "认证" ] message = getattr(record, 'msg', '') or '' if isinstance(message, str): for keyword in login_keywords: if keyword in message: return True except Exception: # 如果出错,为了安全起见,不过滤 pass return False def setup_logger(): """设置日志""" # 获取日志配置 LOG_CONFIG = LogConfig.as_dict() # 创建日志目录 log_dir = os.path.dirname(LOG_CONFIG["file"]) if not os.path.exists(log_dir): os.makedirs(log_dir) # 设置日志级别 log_level = getattr(logging, LOG_CONFIG["level"].upper(), logging.INFO) # 设置日志格式 - 增加行号、函数名等信息 log_format = logging.Formatter(LOG_CONFIG.get("format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s")) # 为警告和错误级别创建特殊格式 detailed_format = logging.Formatter( "%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(funcName)s() - %(message)s" ) # 创建根日志记录器 root_logger = logging.getLogger() root_logger.setLevel(log_level) # 清除现有处理器 for handler in root_logger.handlers[:]: root_logger.removeHandler(handler) # 添加控制台处理器 - 总是显示DEBUG及以上级别到控制台 console_handler = logging.StreamHandler() console_handler.setFormatter(log_format) console_handler.setLevel(logging.DEBUG) # 控制台总是显示所有级别 root_logger.addHandler(console_handler) # 添加专门的错误控制台处理器,显示更多详细信息 error_console_handler = logging.StreamHandler() error_console_handler.setFormatter(detailed_format) error_console_handler.setLevel(logging.WARNING) # 只处理警告及以上级别 root_logger.addHandler(error_console_handler) # 添加统一的文件处理器,使用基于级别的格式化器 # 使用TimedRotatingFileHandler实现每天一个日志文件 file_handler = TimedRotatingFileHandler( LOG_CONFIG["file"], when='midnight', # 每天午夜轮转 interval=1, # 每1天轮转一次 backupCount=7, # 保留30天的日志文件 encoding='utf-8', utc=False # 使用本地时间 ) # 设置日志文件名后缀格式为日期 file_handler.suffix = "%Y-%m-%d" # 使用自定义的格式化器,根据日志级别自动选择格式 file_handler.setFormatter(LevelBasedFormatter()) # 根据DEBUG模式设置文件日志级别 try: from config.settings import settings if settings.DEBUG: file_handler.setLevel(logging.DEBUG) # DEBUG模式写入所有级别 else: file_handler.setLevel(logging.INFO) # 非DEBUG模式不写入DEBUG日志 except ImportError: file_handler.setLevel(logging.INFO) # 默认不写入DEBUG日志 root_logger.addHandler(file_handler) # 添加告警同步处理器 alert_sync_handler = AlertSyncHandler() root_logger.addHandler(alert_sync_handler) # 设置第三方库的日志级别 logging.getLogger("werkzeug").setLevel(logging.WARNING) logging.getLogger("urllib3").setLevel(logging.WARNING) # 根据环境模式设置循环任务模块的日志级别 # try: # from config.settings import settings # if not settings.DEBUG and settings._env == 'production': # # 生产模式:循环任务模块日志级别设为WARNING,减少日志输出 # logging.getLogger("services.execution.handlers.storage_location").setLevel(logging.WARNING) # logging.getLogger("services.execution.handlers.robot_scheduling").setLevel(logging.WARNING) # print(f"生产模式:循环任务模块日志级别设为WARNING") # else: # # DEBUG模式:保持INFO级别,正常输出循环任务日志 # print(f"DEBUG模式:循环任务模块保持正常日志级别") # except ImportError: # # 如果导入settings失败,使用默认设置 # pass return root_logger class Logger: """ 日志记录器类 封装不同级别的日志记录功能 """ def __init__(self, name): """ 初始化日志记录器 Args: name (str): 日志记录器名称,通常为模块名称 """ # 获取指定名称的日志记录器 self.logger = logging.getLogger(name) self.name = name def debug(self, message, *args, **kwargs): """ 记录调试日志 Args: message: 日志消息 *args: 位置参数 **kwargs: 关键字参数 """ self.logger.debug(message, *args, **kwargs) def info(self, message, *args, **kwargs): """ 记录一般信息日志 Args: message: 日志消息 *args: 位置参数 **kwargs: 关键字参数 """ self.logger.info(message, *args, **kwargs) def warning(self, message, *args, exc_info=False, stack_info=False, **kwargs): """ 记录警告日志 Args: message: 日志消息 *args: 位置参数 exc_info: 是否包含异常信息,默认False stack_info: 是否包含堆栈信息,默认False **kwargs: 关键字参数 """ # 默认启用堆栈信息以便显示行号 # 如果不想显示整个堆栈跟踪,可以保持stack_info为False,系统会自动记录行号 self.logger.warning(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs) def error(self, message, *args, exc_info=True, stack_info=True, **kwargs): """ 记录错误日志 Args: message: 日志消息 *args: 位置参数 exc_info: 是否包含异常信息,默认True stack_info: 是否包含堆栈信息,默认True **kwargs: 关键字参数 """ # 由于我们已经在日志格式中添加了行号和文件名,这里不需要特殊处理 self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs) def critical(self, message, *args, exc_info=True, stack_info=True, **kwargs): """ 记录严重错误日志 Args: message: 日志消息 *args: 位置参数 exc_info: 是否包含异常信息,默认True stack_info: 是否包含堆栈信息,默认True **kwargs: 关键字参数 """ self.logger.critical(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs) def exception(self, message, *args, **kwargs): """ 记录异常日志,自动包含异常堆栈信息 Args: message: 日志消息 *args: 位置参数 **kwargs: 关键字参数 """ self.logger.exception(message, *args, **kwargs) def log_error_with_trace(self, message, e=None): """ 记录带有详细追踪信息的错误日志 Args: message: 日志消息 e: 异常对象,可选 """ error_msg = f"{message}" if e: error_msg += f": {str(e)}" # 使用traceback模块获取完整的堆栈跟踪 trace = traceback.format_exc() self.logger.error(f"{error_msg}\n{trace}") def log_startup(self, module_name, version=None): """ 记录模块启动日志 Args: module_name: 模块名称 version: 版本号,可选 """ version_str = f" v{version}" if version else "" self.logger.info(f"=== {module_name}{version_str} 启动完成 ===") def log_shutdown(self, module_name): """ 记录模块关闭日志 Args: module_name: 模块名称 """ self.logger.info(f"=== {module_name} 已关闭 ===") def get_logger(name): """ 获取指定名称的日志记录器 Args: name (str): 日志记录器名称,通常为模块名称 Returns: Logger: 日志记录器实例 """ # 确保全局日志配置已初始化 setup_logger() # 创建并返回Logger实例 return Logger(name)