手动模式

This commit is contained in:
2026-01-07 17:37:09 +08:00
commit 28c57a040a
57 changed files with 7921 additions and 0 deletions
+354
View File
@@ -0,0 +1,354 @@
"""
统一日志管理模块
提供结构化日志记录功能,支持控制台和文件输出
"""
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)
# 添加颜色
if record.levelname in self.COLORS:
# 为levelname添加颜色
colored_levelname = f"{self.COLORS[record.levelname]}{record.levelname}{self.RESET}"
# 使用原始levelname来确定消息的颜色
record.msg = f"{self.COLORS[record.levelname]}{record.msg}{self.RESET}"
record.levelname = colored_levelname
return super().format(record)
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._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))
# 避免重复添加处理器
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:
log_dir = Path(self._config.log_file).parent
log_dir.mkdir(parents=True, exist_ok=True)
file_handler = logging.handlers.RotatingFileHandler(
self._config.log_file,
maxBytes=self._config.log_max_size,
backupCount=self._config.log_backup_count,
encoding='utf-8'
)
file_handler.setLevel(getattr(logging, self._config.log_level.value))
file_handler.setFormatter(formatter)
logger.addHandler(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)