ForcePilot/backend/package/yuxi/channel/infrastructure/metrics/prometheus_metrics.py
Kris 9a8a27bf36 feat(channel): 新增渠道网关模块完整实现
本次提交新增了完整的多渠道消息网关系统,包括:
1. 支持飞书、钉钉、Web、Hook 四种渠道的适配器与配置
2. 领域模型层:消息、会话、绑定、出箱等核心实体
3. 应用服务层:管道、中间件、DTO 与业务逻辑
4. 基础设施层:持久化、过滤器、队列等端口实现
5. 接口层:REST API、SSE、WebSocket 通信端点
6. 前端页面与路由配置,添加渠道管理菜单
7. 新增相关依赖包与 docker-compose 部署配置
2026-05-30 21:53:09 +08:00

254 lines
8.3 KiB
Python

from __future__ import annotations
import logging
from prometheus_client import Counter, Gauge, Histogram
from yuxi.channel.domain.port.metrics_port import MetricsPort
logger = logging.getLogger(__name__)
PIPELINE_DURATION = Histogram(
"channel_pipeline_duration_seconds",
"Inbound pipeline duration",
["channel_type"],
buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25],
)
PIPELINE_TOTAL = Counter(
"channel_pipeline_total",
"Total messages processed by pipeline",
["channel_type"],
)
PIPELINE_ABORTED = Counter(
"channel_pipeline_aborted_total",
"Total messages aborted in pipeline",
["channel_type", "reason"],
)
WORKER_DISPATCH_DURATION = Histogram(
"channel_worker_dispatch_duration_seconds",
"Worker dispatch duration (6 steps)",
["channel_type"],
buckets=[1, 5, 10, 30, 60, 120],
)
WORKER_DISPATCH_TOTAL = Counter(
"channel_worker_dispatch_total",
"Total worker dispatches",
["channel_type", "result"],
)
MIDDLEWARE_DURATION = Histogram(
"channel_middleware_duration_seconds",
"Middleware execution duration",
["middleware", "channel_type"],
buckets=[0.001, 0.005, 0.01, 0.025, 0.05],
)
WORKER_STEP_DURATION = Histogram(
"channel_worker_step_duration_seconds",
"Worker step duration",
["step", "channel_type"],
buckets=[0.01, 0.05, 0.1, 0.5, 1, 5],
)
OUTBOX_PENDING = Gauge(
"channel_outbox_pending",
"Pending outbox entries",
)
OUTBOX_ENQUEUED = Counter(
"channel_outbox_enqueued_total",
"Total messages enqueued to outbox for retry",
["channel_type"],
)
OUTBOX_RETRY_TOTAL = Counter(
"channel_outbox_retry_total",
"Total outbox retry attempts",
["channel_type", "status"],
)
SSE_CONNECTIONS = Gauge(
"channel_sse_connections",
"Active SSE connections",
)
AUTH_ATTEMPTS = Counter(
"channel_auth_attempts_total",
"Total authentication attempts",
["result"],
)
BOT_LOOP_BLOCKED = Counter(
"channel_bot_loop_blocked_total",
"Total messages blocked by bot loop guard",
["channel_type", "context"],
)
ACCESS_POLICY_BLOCKED = Counter(
"channel_access_policy_blocked_total",
"Total messages blocked by access policy",
["channel_type", "policy"],
)
MENTION_GATE_SKIPPED = Counter(
"channel_mention_gate_skipped_total",
"Total group messages skipped by mention gate",
["channel_type"],
)
HOOKS_RECEIVED = Counter(
"channel_hooks_received_total",
"Total webhook hooks received",
["match_path"],
)
CONTENT_FILTER_BLOCKED = Counter(
"channel_content_filter_blocked_total",
"Total messages blocked by content filter in worker",
["channel_type"],
)
CIRCUIT_BREAKER_STATE = Gauge(
"channel_circuit_breaker_state",
"Circuit breaker state (0=closed, 1=half_open, 2=open)",
["agent_config_id"],
)
CIRCUIT_BREAKER_REJECTED = Counter(
"channel_circuit_breaker_rejected_total",
"Total requests rejected by circuit breaker",
["agent_config_id"],
)
WS_CONNECTION_STATUS = Gauge(
"channel_ws_connection_status",
"WebSocket connection status (0=disconnected, 1=connected)",
["channel_type"],
)
WS_MESSAGES_RECEIVED = Counter(
"channel_ws_messages_received_total",
"Total messages received via WebSocket",
["channel_type"],
)
class PrometheusMetricsAdapter(MetricsPort):
async def record_middleware_duration(self, middleware: str, channel_type: str, duration_s: float) -> None:
try:
MIDDLEWARE_DURATION.labels(middleware=middleware, channel_type=channel_type).observe(duration_s)
except Exception:
logger.debug("failed to record middleware_duration metric")
async def record_pipeline_duration(self, channel_type: str, duration_s: float) -> None:
try:
PIPELINE_DURATION.labels(channel_type=channel_type).observe(duration_s)
except Exception:
logger.debug("failed to record pipeline_duration metric")
async def record_pipeline_total(self, channel_type: str) -> None:
try:
PIPELINE_TOTAL.labels(channel_type=channel_type).inc()
except Exception:
logger.debug("failed to record pipeline_total metric")
async def record_pipeline_aborted(self, channel_type: str, reason: str) -> None:
try:
PIPELINE_ABORTED.labels(channel_type=channel_type, reason=reason).inc()
except Exception:
logger.debug("failed to record pipeline_aborted metric")
async def record_worker_dispatch_duration(self, channel_type: str, duration_s: float) -> None:
try:
WORKER_DISPATCH_DURATION.labels(channel_type=channel_type).observe(duration_s)
except Exception:
logger.debug("failed to record worker_dispatch_duration metric")
async def record_worker_dispatch_total(self, channel_type: str, result: str) -> None:
try:
WORKER_DISPATCH_TOTAL.labels(channel_type=channel_type, result=result).inc()
except Exception:
logger.debug("failed to record worker_dispatch_total metric")
async def record_worker_step_duration(self, step: str, channel_type: str, duration_s: float) -> None:
try:
WORKER_STEP_DURATION.labels(step=step, channel_type=channel_type).observe(duration_s)
except Exception:
logger.debug("failed to record worker_step_duration metric")
async def set_outbox_pending(self, count: int) -> None:
try:
OUTBOX_PENDING.set(count)
except Exception:
logger.debug("failed to set outbox_pending metric")
async def record_outbox_enqueued(self, channel_type: str) -> None:
try:
OUTBOX_ENQUEUED.labels(channel_type=channel_type).inc()
except Exception:
logger.debug("failed to record outbox_enqueued metric")
async def record_outbox_retry_total(self, channel_type: str, status: str) -> None:
try:
OUTBOX_RETRY_TOTAL.labels(channel_type=channel_type, status=status).inc()
except Exception:
logger.debug("failed to record outbox_retry_total metric")
async def set_sse_connections(self, count: int) -> None:
try:
SSE_CONNECTIONS.set(count)
except Exception:
logger.debug("failed to set sse_connections metric")
async def record_auth_attempts(self, result: str) -> None:
try:
AUTH_ATTEMPTS.labels(result=result).inc()
except Exception:
logger.debug("failed to record auth_attempts metric")
async def record_bot_loop_blocked(self, channel_type: str, context: str) -> None:
try:
BOT_LOOP_BLOCKED.labels(channel_type=channel_type, context=context).inc()
except Exception:
logger.debug("failed to record bot_loop_blocked metric")
async def record_access_policy_blocked(self, channel_type: str, policy: str) -> None:
try:
ACCESS_POLICY_BLOCKED.labels(channel_type=channel_type, policy=policy).inc()
except Exception:
logger.debug("failed to record access_policy_blocked metric")
async def record_mention_gate_skipped(self, channel_type: str) -> None:
try:
MENTION_GATE_SKIPPED.labels(channel_type=channel_type).inc()
except Exception:
logger.debug("failed to record mention_gate_skipped metric")
async def record_hooks_received(self, match_path: str) -> None:
try:
HOOKS_RECEIVED.labels(match_path=match_path).inc()
except Exception:
logger.debug("failed to record hooks_received metric")
async def record_content_filter_blocked(self, channel_type: str) -> None:
try:
CONTENT_FILTER_BLOCKED.labels(channel_type=channel_type).inc()
except Exception:
logger.debug("failed to record content_filter_blocked metric")
async def set_circuit_breaker_state(self, agent_config_id: int, state: int) -> None:
try:
CIRCUIT_BREAKER_STATE.labels(agent_config_id=str(agent_config_id)).set(state)
except Exception:
logger.debug("failed to set circuit_breaker_state metric")
async def record_circuit_breaker_rejected(self, agent_config_id: int) -> None:
try:
CIRCUIT_BREAKER_REJECTED.labels(agent_config_id=str(agent_config_id)).inc()
except Exception:
logger.debug("failed to record circuit_breaker_rejected metric")