Files
abot/plugins/stats_collector/main.py
liuwei 0878f0d4ea 剥离无效事件系统并收口插件统计链路
- 删除未被实际消费的事件系统实现与相关发布逻辑
- 将插件调用统计改为在机器人主链路中直接埋点记录
- 重构统计收集插件初始化与记录方式,移除事件总线依赖
- 同步更新工程优化文档中的性能与链路治理描述
2026-04-30 14:54:22 +08:00

171 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from loguru import logger
from typing import Dict, Any, Tuple, Optional, List
from base.plugin_common.plugin_interface import PluginInterface, PluginStatus
from db.stats_db import StatsDBOperator
from db.connection import DBConnectionManager
class StatsCollectorPlugin(PluginInterface):
"""统计收集插件"""
@property
def name(self) -> str:
return "指令记录"
@property
def version(self) -> str:
return "1.0.0"
@property
def description(self) -> str:
return "群聊指令数据记录"
@property
def author(self) -> str:
return "Liuwei"
@property
def command_prefix(self) -> Optional[str]:
return "#"
@property
def commands(self) -> List[str]:
return []
def __init__(self):
super().__init__()
self.LOG = logger
self.LOG.debug(f"正在初始化 {self.name} 插件...")
# 默认配置:
# 1. 这个插件现在不再依赖事件总线,而是由主消息分发链路直接回调;
# 2. 因此这里保留一份轻量配置,只控制“是否记录”和“排除哪些插件”;
# 3. 这样既能延续原有统计面板数据结构,也能避免事件系统带来的额外复杂度。
self.config = {
"enable": True,
"record_all_plugins": True, # 是否记录所有插件的调用
"excluded_plugins": [], # 排除的插件列表
}
self.db_manager = DBConnectionManager.get_instance()
self.stats_db = StatsDBOperator(self.db_manager)
def initialize(self, context: Dict[str, Any]) -> bool:
"""初始化插件"""
# 这里显式只读取插件自己的配置,不再把 system_context 整体 merge 进来:
# 1. 旧实现把 initialize 入参当成“插件配置”使用,但实际传入的是 system_context
# 2. 结果会把 db_manager、redis_pool 等运行时对象误写到 self.config 中;
# 3. 改成只消费 load_config 后的 self._config避免配置结构持续污染。
if isinstance(self._config, dict):
self.config.update(self._config)
# 若主系统已经初始化了 DB 管理器,则优先复用统一实例,避免插件侧再走兜底单例。
if isinstance(context, dict) and context.get("db_manager") is not None:
self.db_manager = context.get("db_manager")
self.stats_db = StatsDBOperator(self.db_manager)
if not self.config["enable"]:
self.LOG.info("统计收集插件已禁用")
return False
return True
def record_plugin_call(
self,
*,
plugin_name: str,
command: str,
user_id: str,
group_id: Optional[str],
is_group: bool,
process_result: bool,
process_time_ms: float,
) -> None:
"""由主链路直接调用,记录一次插件执行结果。"""
# 主链路可能会在高频场景下频繁回调这里,因此先做最便宜的开关判断,
# 避免对已关闭或被排除插件继续产生数据库写入成本。
if not self._should_record_plugin(plugin_name):
return
try:
self.stats_db.record_plugin_call(
plugin_name=plugin_name,
command=command,
user_id=user_id,
group_id=group_id if is_group else None,
success=process_result,
process_time_ms=process_time_ms,
)
self.LOG.debug(
f"记录插件调用结束: {plugin_name} - {command} - 成功: {process_result} - 处理时间: {process_time_ms}ms"
)
except Exception as e:
self.LOG.error(f"记录插件调用统计数据出错: {e}")
def record_plugin_error(
self,
*,
plugin_name: str,
command: str,
user_id: str,
group_id: Optional[str],
is_group: bool,
error_message: str,
stack_trace: Optional[str] = None,
) -> None:
"""由主链路直接调用,记录一次插件执行异常。"""
if not self._should_record_plugin(plugin_name):
return
try:
self.stats_db.record_error(
plugin_name=plugin_name,
command=command,
user_id=user_id,
group_id=group_id if is_group else None,
error_message=error_message,
stack_trace=stack_trace,
)
self.LOG.debug(f"记录插件调用错误: {plugin_name} - {command} - {error_message}")
except Exception as e:
self.LOG.error(f"记录插件错误信息出错: {e}")
def _should_record_plugin(self, plugin_name: str) -> bool:
"""检查是否应该记录该插件的调用"""
if not self.config["record_all_plugins"]:
return False
if plugin_name in self.config["excluded_plugins"]:
return False
# 不记录自身的调用
if plugin_name == self.name:
return False
return True
def match_command(self, content: str) -> bool:
"""匹配命令"""
# 该插件不处理用户消息
return False
def process_message(self, message: Dict[str, Any]) -> Tuple[bool, str]:
"""处理消息"""
# 该插件不处理用户消息
return False, ""
def shutdown(self) -> None:
"""关闭插件"""
self.LOG.info("统计收集插件已关闭")
def start(self) -> bool:
"""启动插件"""
self.LOG.debug(f"[{self.name}] 插件已启动")
self.status = PluginStatus.RUNNING
return True
def stop(self) -> bool:
"""停止插件"""
self.LOG.info(f"[{self.name}] 插件已停止")
self.status = PluginStatus.STOPPED
return True