Files
inkreach-prefect/flows/monitor/alert_failure.py
T
yeuimu 1d0da2a00b feat: 接入企微智能机器人——异常报警/数据核验日报/设备健康日报
- flows/shared/wecom.py: WSClient 按需连接(连接→认证→发送→断开),全容错,超长分段
- flows/monitor/alert_failure.py: Prefect 事件触发(Failed/Crashed→run deployment),发群报警
- flows/monitor/daily_report.py: 每日 18:30 近7天 RIIN/SDS 核验(复用 verify)+商品完整性+OCR+失败回顾
- flows/monitor/device_check.py: 每日 09:00 内存/磁盘/GPU(80%阈值)+Docker 容器异常+服务探活
- db.py: 新增 get_goods_integrity/ping; compose worker 挂载 docker.sock+/:/host:ro+GPU
- scripts/wecom_capture_chatid.py: 一次性捕获群 chat_id 工具
2026-08-27 09:40:53 +00:00

130 lines
5.0 KiB
Python

"""企微机器人异常报警:任意 flow run 失败/崩溃时由 Prefect 事件触发器自动调用。
对应 wecom-alert deployment(prefect.yaml 里的 triggers 声明):
- match: prefect.resource.id = prefect.flow-run.*
- expect: prefect.flow-run.Failed / Crashed
- parameters.event = "{{ event }}"
flow 体全容错——本 flow 自身永不进入 Failed 状态,避免自我触发循环。
event 可能以 dict 或 str(jinja 渲染结果)传入,均做防御性解析;
解析出 flow run id 后再查 Prefect API 补全权威信息(名称/错误消息/时间)。
"""
from __future__ import annotations
import json
import os
import re
from datetime import datetime
from typing import Any
import httpx
from prefect import flow
from flows.shared import send_markdown
UI_BASE = os.environ.get("PREFECT_UI_URL", "http://192.168.124.92:4200")
def _parse_event(event: Any) -> dict:
if isinstance(event, dict):
return event
if isinstance(event, str) and event.strip():
# jinja "{{ event }}" 可能渲染成 JSON 串或 Python repr,统一抠除关键字段
rid = re.search(r"prefect\.flow-run\.[0-9a-fA-F-]{36}", event)
ev = re.search(r"prefect\.flow-run\.(?:Failed|Crashed)", event)
name = re.search(r"prefect\.resource\.name['\"]?\s*[:=]\s*['\"]([^'\"]+)", event)
occurred = re.search(r"occurred['\"]?\s*[:=]\s*['\"]([^'\"]+)", event)
return {
"event": ev.group(0) if ev else "",
"resource": {
"prefect.resource.id": rid.group(0) if rid else "",
"prefect.resource.name": name.group(1) if name else "",
},
"occurred": occurred.group(1) if occurred else "",
}
return {}
def _flow_run_id(event: dict) -> str | None:
rid = (event.get("resource") or {}).get("prefect.resource.id", "")
m = re.match(r"^prefect\.flow-run\.(.+)$", rid)
return m.group(1) if m else None
def _fetch_run_detail(run_id: str) -> dict:
"""查 Prefect API 补全 flow run 权威信息,失败返回空 dict。"""
api_url = os.environ.get("PREFECT_API_URL", "http://localhost:4200/api")
try:
resp = httpx.get(f"{api_url}/flow_runs/{run_id}", timeout=10)
resp.raise_for_status()
return resp.json()
except Exception as err: # noqa: BLE001
print(f"[wecom-alert] 查询 flow run {run_id} 失败: {err}")
return {}
def _fmt_ts(value: Any) -> str:
if not value:
return "—"
try:
return datetime.fromisoformat(value.replace("Z", "+00:00")).astimezone().strftime("%m-%d %H:%M")
except (ValueError, TypeError):
return str(value)[:19]
@flow(name="wecom-alert", log_prints=True)
def wecom_alert_failure(event: dict | str | None = None) -> dict:
"""Prefect 事件触发:flow run 失败/崩溃 → 发企微群报警。永不抛异常。"""
result = {"sent": False, "flowName": None, "runId": None}
try:
ev = _parse_event(event)
event_name = ev.get("event", "prefect.flow-run.?")
resource_name = (ev.get("resource") or {}).get("prefect.resource.name", "")
run_id = _flow_run_id(ev)
flow_name, run_name, state_msg, occurred = "", resource_name, "", ev.get("occurred", "")
if run_id:
detail = _fetch_run_detail(run_id)
flow_name = detail.get("name", "") or resource_name
run_name = detail.get("name", "")
state = detail.get("state") or {}
state_msg = (state.get("message") or "").strip()
ended = detail.get("end_time") or detail.get("expected_start_time")
occurred = occurred or ended
lines = [
"🔴 **Prefect 流程失败报警**",
f"- 事件: `{event_name}`",
f"- Flow: {flow_name or resource_name or '未知'}",
]
if run_name and run_name != flow_name:
lines.append(f"- Run: {run_name}")
if state_msg:
lines.append(f"- 错误: {state_msg[:200].replace(chr(10), ' ')}")
lines.append(f"- 时间: {_fmt_ts(occurred)}")
if run_id:
lines.append(f"- 详情: [Prefect UI]({UI_BASE}/runs/flow-run/{run_id})")
else:
raw = json.dumps(ev, ensure_ascii=False)[:300] if ev else "(事件内容解析失败)"
lines.append(f"- 原始事件: {raw}")
result["sent"] = send_markdown("\n".join(lines))
result.update({"flowName": flow_name or None, "runId": run_id})
print(f"[wecom-alert] 已发送失败报警: {flow_name or resource_name} ({event_name})")
except Exception as err: # noqa: BLE001
print(f"[wecom-alert] 报警流程异常(已吞掉,避免自我触发): {err}")
return result
if __name__ == "__main__":
# 本地手测:模拟一条 Failed 事件
wecom_alert_failure({
"event": "prefect.flow-run.Failed",
"resource": {
"prefect.resource.id": "prefect.flow-run.00000000-0000-0000-0000-000000000000",
"prefect.resource.name": "riin-order-sync/manual",
},
"occurred": datetime.now().astimezone().isoformat(),
})