372 lines
12 KiB
Python
372 lines
12 KiB
Python
"""
|
|
统一日志管理模块
|
|
提供结构化日志记录功能,支持控制台和文件输出
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import logging
|
|
import logging.handlers
|
|
from pathlib import Path
|
|
from typing import Optional, Dict, Any
|
|
from datetime import datetime
|
|
|
|
from .config_manager import get_config
|
|
|
|
|
|
class ColoredFormatter(logging.Formatter):
|
|
"""带颜色的日志格式化器"""
|
|
|
|
# ANSI颜色代码
|
|
COLORS = {
|
|
'DEBUG': '\033[36m', # 青色
|
|
'INFO': '\033[32m', # 绿色
|
|
'WARNING': '\033[33m', # 黄色
|
|
'ERROR': '\033[31m', # 红色
|
|
'CRITICAL': '\033[35m', # 紫色
|
|
}
|
|
RESET = '\033[0m' # 重置颜色
|
|
|
|
def format(self, record):
|
|
# 检查是否已经包含ANSI颜色代码
|
|
if '\033[' in record.levelname:
|
|
# 如果已经着色,直接返回原始格式
|
|
return super().format(record)
|
|
|
|
# 保存原始值
|
|
original_levelname = record.levelname
|
|
original_msg = record.msg
|
|
|
|
# 添加颜色
|
|
if record.levelname in self.COLORS:
|
|
# 为levelname添加颜色
|
|
record.levelname = f"{self.COLORS[record.levelname]}{record.levelname}{self.RESET}"
|
|
# 为消息添加颜色
|
|
record.msg = f"{self.COLORS[original_levelname]}{record.msg}{self.RESET}"
|
|
|
|
# 格式化
|
|
result = super().format(record)
|
|
|
|
# 恢复原始值,避免影响其他处理器
|
|
record.levelname = original_levelname
|
|
record.msg = original_msg
|
|
|
|
return result
|
|
|
|
|
|
class Logger:
|
|
"""统一日志管理器"""
|
|
|
|
_instance = None
|
|
_initialized = False
|
|
|
|
def __new__(cls):
|
|
if cls._instance is None:
|
|
cls._instance = super().__new__(cls)
|
|
return cls._instance
|
|
|
|
def __init__(self):
|
|
if not self._initialized:
|
|
self._loggers = {}
|
|
self._config = None
|
|
self._file_handler = None # 全局文件处理器,避免重复创建
|
|
self._initialized = True
|
|
|
|
def init_logger(self, name: str = "gx_gp_monitor", config=None) -> logging.Logger:
|
|
"""
|
|
初始化日志器
|
|
|
|
Args:
|
|
name: 日志器名称
|
|
config: 配置对象,如果为None则从全局配置加载
|
|
|
|
Returns:
|
|
logging.Logger: 配置好的日志器实例
|
|
"""
|
|
if name in self._loggers:
|
|
return self._loggers[name]
|
|
|
|
# 获取配置
|
|
if config is None:
|
|
try:
|
|
self._config = get_config()
|
|
except RuntimeError:
|
|
# 配置未加载,使用默认配置
|
|
self._config = self._get_default_config()
|
|
else:
|
|
self._config = config
|
|
|
|
# 创建日志器
|
|
logger = logging.getLogger(name)
|
|
logger.setLevel(getattr(logging, self._config.log_level.value))
|
|
|
|
# 防止消息传播到父logger,避免重复记录
|
|
logger.propagate = False
|
|
|
|
# 避免重复添加处理器
|
|
if logger.handlers:
|
|
return logger
|
|
|
|
# 创建格式化器
|
|
formatter = logging.Formatter(
|
|
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
)
|
|
|
|
# 控制台处理器
|
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
console_handler.setLevel(getattr(logging, self._config.log_level.value))
|
|
|
|
# 使用彩色格式化器(如果支持)
|
|
if sys.platform != 'win32' and 'TERM' in os.environ:
|
|
colored_formatter = ColoredFormatter(
|
|
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
)
|
|
console_handler.setFormatter(colored_formatter)
|
|
else:
|
|
console_handler.setFormatter(formatter)
|
|
|
|
logger.addHandler(console_handler)
|
|
|
|
# 文件处理器(如果配置了日志文件)- 使用全局文件处理器避免重复
|
|
if self._config.log_file and self._file_handler is None:
|
|
log_dir = Path(self._config.log_file).parent
|
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
self._file_handler = logging.handlers.RotatingFileHandler(
|
|
self._config.log_file,
|
|
maxBytes=self._config.log_max_size,
|
|
backupCount=self._config.log_backup_count,
|
|
encoding='utf-8'
|
|
)
|
|
self._file_handler.setLevel(getattr(logging, self._config.log_level.value))
|
|
self._file_handler.setFormatter(formatter)
|
|
|
|
# 为所有logger添加全局文件处理器
|
|
if self._file_handler is not None:
|
|
logger.addHandler(self._file_handler)
|
|
|
|
self._loggers[name] = logger
|
|
return logger
|
|
|
|
def _get_default_config(self):
|
|
"""获取默认配置"""
|
|
from .config_manager import LogLevel
|
|
|
|
class DefaultConfig:
|
|
def __init__(self):
|
|
self.log_level = LogLevel.INFO
|
|
self.log_file = "logs/gx_gp_monitor.log"
|
|
self.log_max_size = 10485760 # 10MB
|
|
self.log_backup_count = 5
|
|
|
|
return DefaultConfig()
|
|
|
|
def get_logger(self, name: str = "gx_gp_monitor") -> logging.Logger:
|
|
"""
|
|
获取日志器
|
|
|
|
Args:
|
|
name: 日志器名称
|
|
|
|
Returns:
|
|
logging.Logger: 日志器实例
|
|
"""
|
|
if name not in self._loggers:
|
|
return self.init_logger(name)
|
|
return self._loggers[name]
|
|
|
|
def log_crawl_start(self, source_name: str, logger: Optional[logging.Logger] = None):
|
|
"""记录爬取开始"""
|
|
if logger is None:
|
|
logger = self.get_logger()
|
|
logger.info(f"开始爬取 {source_name}")
|
|
|
|
def log_crawl_success(self, source_name: str, count: int, duration: float,
|
|
logger: Optional[logging.Logger] = None):
|
|
"""记录爬取成功"""
|
|
if logger is None:
|
|
logger = self.get_logger()
|
|
logger.info(f"{source_name} 爬取完成,共获取 {count} 条公告,耗时 {duration:.2f}秒")
|
|
|
|
def log_crawl_error(self, source_name: str, error: str,
|
|
logger: Optional[logging.Logger] = None):
|
|
"""记录爬取错误"""
|
|
if logger is None:
|
|
logger = self.get_logger()
|
|
logger.error(f"{source_name} 爬取失败: {error}")
|
|
|
|
def log_announcement_filtered(self, reason: str, count: int,
|
|
logger: Optional[logging.Logger] = None):
|
|
"""记录公告筛选信息"""
|
|
if logger is None:
|
|
logger = self.get_logger()
|
|
logger.info(f"公告筛选 - {reason}: {count} 条")
|
|
|
|
def log_database_operation(self, operation: str, table: str, count: int = 0,
|
|
logger: Optional[logging.Logger] = None):
|
|
"""记录数据库操作"""
|
|
if logger is None:
|
|
logger = self.get_logger()
|
|
if count > 0:
|
|
logger.info(f"数据库操作 - {operation} {table}: {count} 条记录")
|
|
else:
|
|
logger.info(f"数据库操作 - {operation} {table}")
|
|
|
|
def log_notification_sent(self, channel: str, recipient_count: int,
|
|
logger: Optional[logging.Logger] = None):
|
|
"""记录通知发送"""
|
|
if logger is None:
|
|
logger = self.get_logger()
|
|
logger.info(f"通知发送 - {channel}: 向 {recipient_count} 个接收者发送")
|
|
|
|
def log_system_metrics(self, metrics: Dict[str, Any],
|
|
logger: Optional[logging.Logger] = None):
|
|
"""记录系统指标"""
|
|
if logger is None:
|
|
logger = self.get_logger()
|
|
metrics_str = ", ".join([f"{k}={v}" for k, v in metrics.items()])
|
|
logger.info(f"系统指标: {metrics_str}")
|
|
|
|
def log_performance_warning(self, operation: str, duration: float, threshold: float,
|
|
logger: Optional[logging.Logger] = None):
|
|
"""记录性能警告"""
|
|
if logger is None:
|
|
logger = self.get_logger()
|
|
logger.warning(f"性能警告 - {operation} 耗时 {duration:.2f}秒,超过阈值 {threshold:.2f}秒")
|
|
|
|
|
|
# 全局日志管理器实例
|
|
_logger_manager = Logger()
|
|
|
|
|
|
def get_logger(name: str = "gx_gp_monitor") -> logging.Logger:
|
|
"""
|
|
获取日志器
|
|
|
|
Args:
|
|
name: 日志器名称
|
|
|
|
Returns:
|
|
logging.Logger: 日志器实例
|
|
"""
|
|
return _logger_manager.get_logger(name)
|
|
|
|
|
|
def init_logger(name: str = "gx_gp_monitor", config=None) -> logging.Logger:
|
|
"""
|
|
初始化并获取日志器
|
|
|
|
Args:
|
|
name: 日志器名称
|
|
config: 配置对象
|
|
|
|
Returns:
|
|
logging.Logger: 日志器实例
|
|
"""
|
|
return _logger_manager.init_logger(name, config)
|
|
|
|
|
|
def log_function_call(func_name: str, args: Optional[Dict[str, Any]] = None,
|
|
logger: Optional[logging.Logger] = None):
|
|
"""
|
|
装饰器:记录函数调用
|
|
|
|
Args:
|
|
func_name: 函数名称
|
|
args: 函数参数
|
|
logger: 日志器实例
|
|
"""
|
|
def decorator(func):
|
|
def wrapper(*args, **kwargs):
|
|
nonlocal logger
|
|
if logger is None:
|
|
logger = get_logger()
|
|
|
|
start_time = datetime.now()
|
|
logger.debug(f"调用函数: {func_name}")
|
|
|
|
try:
|
|
result = func(*args, **kwargs)
|
|
duration = (datetime.now() - start_time).total_seconds()
|
|
logger.debug(f"函数 {func_name} 执行完成,耗时 {duration:.3f}秒")
|
|
return result
|
|
except Exception as e:
|
|
duration = (datetime.now() - start_time).total_seconds()
|
|
logger.error(f"函数 {func_name} 执行失败,耗时 {duration:.3f}秒: {str(e)}")
|
|
raise
|
|
|
|
return wrapper
|
|
return decorator
|
|
|
|
|
|
# 便捷函数
|
|
def log_info(message: str, logger: Optional[logging.Logger] = None):
|
|
"""记录信息日志"""
|
|
if logger is None:
|
|
logger = get_logger()
|
|
logger.info(message)
|
|
|
|
|
|
def log_warning(message: str, logger: Optional[logging.Logger] = None):
|
|
"""记录警告日志"""
|
|
if logger is None:
|
|
logger = get_logger()
|
|
logger.warning(message)
|
|
|
|
|
|
def log_error(message: str, logger: Optional[logging.Logger] = None):
|
|
"""记录错误日志"""
|
|
if logger is None:
|
|
logger = get_logger()
|
|
logger.error(message)
|
|
|
|
|
|
def log_debug(message: str, logger: Optional[logging.Logger] = None):
|
|
"""记录调试日志"""
|
|
if logger is None:
|
|
logger = get_logger()
|
|
logger.debug(message)
|
|
|
|
|
|
# 便捷的爬取日志记录函数
|
|
def log_crawl_start(source_name: str):
|
|
"""记录爬取开始"""
|
|
_logger_manager.log_crawl_start(source_name)
|
|
|
|
|
|
def log_crawl_success(source_name: str, count: int, duration: float):
|
|
"""记录爬取成功"""
|
|
_logger_manager.log_crawl_success(source_name, count, duration)
|
|
|
|
|
|
def log_crawl_error(source_name: str, error: str):
|
|
"""记录爬取错误"""
|
|
_logger_manager.log_crawl_error(source_name, error)
|
|
|
|
|
|
def log_announcement_filtered(reason: str, count: int):
|
|
"""记录公告筛选信息"""
|
|
_logger_manager.log_announcement_filtered(reason, count)
|
|
|
|
|
|
def log_database_operation(operation: str, table: str, count: int = 0):
|
|
"""记录数据库操作"""
|
|
_logger_manager.log_database_operation(operation, table, count)
|
|
|
|
|
|
def log_notification_sent(channel: str, recipient_count: int):
|
|
"""记录通知发送"""
|
|
_logger_manager.log_notification_sent(channel, recipient_count)
|
|
|
|
|
|
def log_system_metrics(metrics: Dict[str, Any]):
|
|
"""记录系统指标"""
|
|
_logger_manager.log_system_metrics(metrics)
|
|
|
|
|
|
def log_performance_warning(operation: str, duration: float, threshold: float):
|
|
"""记录性能警告"""
|
|
_logger_manager.log_performance_warning(operation, duration, threshold)
|