Hermes Agent 实战 16|给 AI Agent 平台做可观测:多 profile 服务的指标、日志与告警设计原创
# 给 AI Agent 平台做可观测:多 profile 服务的指标、日志与告警设计
系列第 16 篇。一次真实故障做引子:2026-07-27 09:11:36,tradex gateway 的 Telegram 平台 宣布
Aborting,进程却一直活着,systemd 全程绿灯,没有任何告警。这篇从这 41 秒讲起, 把多 profile Agent 平台的可观测拆成三层信号(进程 / 引擎 / 日志), 说清楚哪些信号是噪声、哪些才是真信号,最后给出日志汇聚方案 A/B 的对比与我的取舍。
# 1. Aborting 的 Telegram 与沉默的 systemd
gateway.log, line 5412:
2026-07-27 09:11:57,210 INFO ... Telegram polling restarted after network error
gateway.log, line 5407-5411:
2026-07-27 09:10:55,612 WARNING ... Telegram network error: Bad Gateway
...
2026-07-27 09:11:36,164 WARNING ... Telegram polling reconnect failed: Timed out
2
3
errors.log, line 1:
2026-07-27 09:11:36,092 ERROR telegram.ext: Network Retry Loop ... Aborting.
2026-07-27 09:11:36,tradex gateway 的 Telegram 平台宣布 Aborting。从 09:10:55 收到第一个 Bad Gateway,到 09:11:36 彻底放弃重连,窗口期 41 秒。
gateway 进程呢?继续跑着。09:11:57 它甚至还打印了「polling restarted」——只是 Telegram 平台礼貌地通知了 Aborting,然后自己恢复。
systemd 在整个过程中毫无反应。systemctl status hermes-gateway-tradex 一直是绿的,因为进程从未退出。没有告警,没有短信,没有 Slack。直到用户发现消息发出去没反应,我们才知道出事了。
这就是 AI Agent 平台的可观测陷阱:进程活着,不代表业务活着。
# 2. 为什么多 Profile 架构让可观测更难
# 2.1 服务形态碎片
10 个 gateway 服务分布在各自的 profiles:
| 维度 | 现状(来源:运行时取数) |
|---|---|
| 进程类型 | Python (8) + Node.js (2) |
| 运行时长 | 2 days ~ 3 weeks(截至 2026-08-24) |
| 日志分散 | 10 个目录 / 271M / 36,786 行 errors |
没有一个全局视图能回答「整个平台健康吗」。
运行命令验证:
$ systemctl --user list-units --type=service --state=running | grep gateway | wc -l
7
$ du -sh ~/.hermes/profiles/*/logs/ 2>/dev/null | awk '{sum+=$1} END {print sum}'
271M
$ wc -l ~/.hermes/profiles/*/logs/errors.log 2>/dev/null | tail -1
36786 total
2
3
4
5
6
7
8
# 2.2 故障模式的多样性
从 errors.log 提取的 7 类真实故障:
| 类型 | 症状 | 进程状态 | systemd 感知 |
|---|---|---|---|
| Telegram 网络中断(07-27) | 平台 Aborting 后自恢复 | 存活 | ❌ 无 |
| API 兼容崩溃 | 工具调用失败 | 存活 | ❌ 无 |
| Token 冲突 | 无法连接 | 崩溃重启 | ⚠️ 重启计数++ |
| 端口占用 | API server 起不来 | 退出 | ✅ 有 |
| SIGTERM | 正常停止 | 停止 | ✅ 有 |
| WebUI 慢查询 | 请求 5s+ | 存活 | ❌ 无 |
| Memory 溢出 | 记忆写入失败 | 存活 | ❌ 无 |
4 类故障(占 57%)发生时 systemd 完全无感知。
# 2.3 心跳文件的局限性
ticker 心跳文件 (cron/ticker_heartbeat + ticker_last_success) 由 cron/scheduler 每 60 秒刷新一次(来源:cron/jobs.py:99)。
它能检测:gateway 进程是否存活、cron 循环是否卡住。
它不能检测:Telegram 平台 Aborting、平台恢复、消息队列积压。07-27 的故障期间,只要 gateway 进程在,心跳文件就会继续刷新——完全无法感知 Telegram 平台的 41 秒中断。
验证命令:
$ cat ~/.hermes/profiles/tradex/cron/ticker_last_success | xargs -I{} date -d @{} "+%Y-%m-%d %H:%M:%S"
2026-08-24 11:33:35
2
# 3. 指标体系:三层信号模型
# 3.1 进程层(systemd 提供)
运行命令获取的当前状态:
$ systemctl --user status hermes-gateway-tradex --no-pager | grep -E "(Active:|Main PID:|Memory:)"
Active: active (running) since Fri 2026-08-21 16:13:08 CST; 2 days ago
Main PID: 1346054 (hermes)
Memory: 386.4M
2
3
4
- NRestarts:重启次数(全部为 0)
- Memory:RSS(当前观测值)
- Uptime:运行时长
- 局限:只能检测崩溃,无法检测业务僵死或平台中断
# 3.2 引擎层(Gateway 自身提供)
| 信号 | 当前状态 | 源码出处 |
|---|---|---|
| event-loop 心跳 | state/gateway.heartbeat 30s 刷新 | shutdown_watchdog.py:16-17 |
| shutdown-diag | SIGTERM 时抓进程树+loadavg | shutdown_forensics.py:8-12 |
| /v1/models 探活 | 缺失 | - |
| 平台级健康 | 缺失 | - |
07-27 的故障暴露了关键缺口:Telegram 平台 Aborting 后 gateway 仍能写心跳,但没有任何信号表明「Telegram 不可达」。
# 3.3 日志层(行为信号)
gateway.log 中的模式本身就是指标:
Network Retry Loop→ Telegram 健康度polling restarted→ 平台自恢复Slow WebUI request→ 会话索引性能
验证(errors.log 行数):
$ wc -l ~/.hermes/profiles/*/logs/errors.log 2>/dev/null
default: 3027
superdba: 6923
tradex: 6434
2
3
4
# 4. 日志与追踪现状
# 4.1 分散的 271M 日志
| Profile | errors.log | gateway.log | 总大小(截至 2026-08-24) |
|---|---|---|---|
| superdba | 6,923 行 | 4.3M | 84M |
| tradex | 6,434 行 | 900K | 24M |
| aws-us | 3,027 行 | 2M | 62M |
没有跨 profile 的 trace_id,无法回答「一个用户的请求经过了哪些 profiles」。
# 4.2 shutdown-diag 的设计决策
shutdown_forensics.py 的设计原则(来源:文件 docstring):
"Anything that needs to wait (e.g. shelling out to
ps aux) belongs in the async helper, never in the synchronous probe."
解决方案:
snapshot_shutdown_context():同步快速抓取 /proc 基础信息(<10ms)spawn_async_diagnostic():fire-and-forget 子进程抓 ps aux + dmesg
07-25 14:55:56 的 shutdown-diag 抓取了当时的进程树和负载(gateway.log 中 SIGTERM 记录可见)。
# 5. 告警设计:从噪声中提取信号
# 5.1 无效信号(已验证)
- NRestarts:无法检测「平台 Aborting 但进程存活」类型故障
- 心跳文件:07-27 故障期间 tradex 的心跳持续刷新
验证:
$ systemctl --user show hermes-gateway-tradex -p NRestarts
NRestarts=0
2
# 5.2 有效信号
| 信号 | 来源 | 阈值 |
|---|---|---|
| ticker_last_success 滞后 | cron/jobs.py | > 5min Warning, > 15min Critical |
| Telegram Network Retry Loop | gateway.log | 出现即记录 |
| WebUI 慢查询 | gateway.log | P99 > 3s |
# 5.3 为什么没上 Prometheus
Q9 决策点:真实决策回溯
现状验证(源码证据):
gateway/platforms/api_server.py:152:DEFAULT_PORT=8642shutdown_watchdog.py:16:已存在 event-loop 心跳文件cron/jobs.py:91-94:已存在 ticker 心跳文件
决策逻辑:
- 已有:进程级监控(systemd)、循环级监控(ticker 心跳)、信号级监控(shutdown-diag)
- 缺失:平台级监控(Telegram/Discord 健康)、业务级监控(消息队列深度、SSE 连接数)
- 现实:07-27 的 41 秒 Telegram 中断被日志记录,但未被任何人实时消费
真实选择:混合方案
- 基础层:systemd + ticker 心跳(已部署,成本已支付)
- Signal 层:shutdown-diag SIGTERM 现场捕获
- 业务层:日志后置分析,未部署实时消费
- 未选:Prometheus + /metrics 端点(新增运维成本,1 人维护 10 profiles)
代价:07-27 式故障依赖用户反馈发现,MTTD 不可控。
# 6. 方案 A vs 方案 B:日志汇聚的取舍
# 方案 A:原地标准化(journald 统一)
做法:
- gateway 写入 journald 而非文本文件
- journald 作为本地日志聚合点
- 查询:
journalctl --user -u hermes-gateway-*
优点:
- 零新增依赖
- 时序统一、结构化输出
- 与 systemd 原生集成
缺点:
- 仍需登录机器查询
- journald 在容器/多机场景下仍是单点
- 跨 profile 联合查询需要额外工具
# 方案 B:旁路汇聚(fluent-bit → Loki/ES)
做法:
- 每个 profile 部署 fluent-bit/vector 本地 agent
- 监听
logs/*.log变化 - 转发到中央 Loki 或 Elasticsearch
优点:
- 跨 profile 联合查询
- 实时检索(Loki 标签匹配)
- 与 Grafana 原生集成
缺点:
- 需维护 Loki/ES 集群
- 新增网络出口(日志传输)
- profile 扩容时需同步部署 agent
# 我的取舍:分阶段走
第一阶段(当前):原地标准化
- 将 gateway 日志输出改为 journald
- 统一日志格式为结构化 JSON
- 保留 ticker 心跳作为 backup
理由:
- 成本优先:1 人维护 10 profiles,无法承担 Loki 集群运维
- 渐进可行:journald 零新增依赖,即刻可用
- 风险可控:日志不离开本机,无网络传输风险
第二阶段(未来):当 profile 数量 > 20 或团队规模 > 3 人时
- 引入集中化日志汇聚
- 优先评估 Loki(比 ES 轻量)
一句话结论:现阶段选方案 A(journald 原地标准化),因为运维人力不足;日志分散问题用统一 journald 格式缓解,而非引入新的基础设施负担。
# 7. 坑与对策
# 坑 1:平台级故障的无感知
07-27 09:11:36 Telegram Aborting,gateway.log 记录了,但:
- systemd 状态:绿(进程在)
- 心跳文件:正常刷新(loop 在)
- 人工感知:0(直到用户反馈)
对策:平台适配器暴露健康端点(如 Telegram getUpdates 连通性)。
# 坑 2:日志分散的查询成本
07-27 故障的完整时间线需要 grep 2 个文件(gateway.log + errors.log),tradex 只是 10 个 profiles 之一。
对策:日志聚合(journald 统一)或至少是 profile 级别的日志目录统一。
# 坑 3:数值的时间绑定
把「今天的内存」当成「07-27 当时的内存」——时间线错乱。
对策:历史场景只用当时的日志时间戳,现状盘点用今天的观测值。
# 8. 可复用要点
- 进程心跳 ≠ 平台健康:07-27 Telegram Aborting 期间,gateway 进程心跳正常
- 日志是最后的黑匣子:shutdown-diag 设计捕获了 SIGTERM 时的完整现场
- 平台级监控是缺失的环节:systemd 和 ticker 都看不到 Telegram 平台的 Aborting
- 多 profile = 多份日志 = N 倍查询成本:10 profiles 的日志分散问题
# 9. 下一步缺口
- [ ] Telegram 平台健康探活(getUpdates 连通性检查)
- [ ] 日志 journald 统一输出(原地标准化)
- [ ] 日志聚合查询(当 profile > 20 时评估 Loki)
🤖 Agent 可直接解析的元数据块(点击展开)
{
"_meta": {
"doc_version": "2026-08-24",
"article_id": "hermes-agent-16-observability",
"profile_context": "blog",
"estimated_setup_time": "2h"
},
"evidence_sources": {
"gateway_log": "~/.hermes/profiles/tradex/logs/gateway.log:5407-5412",
"errors_log": "~/.hermes/profiles/tradex/logs/errors.log:1",
"shutdown_forensics": "~/.hermes/hermes-agent/gateway/shutdown_forensics.py:8-12",
"cron_jobs": "~/.hermes/hermes-agent/cron/jobs.py:91-94",
"shutdown_watchdog": "~/.hermes/hermes-agent/gateway/shutdown_watchdog.py:16-17",
"api_server": "~/.hermes/hermes-agent/gateway/platforms/api_server.py:152"
},
"key_metrics": {
"profiles_count": 10,
"logs_total_mb": 271,
"errors_lines": 36786,
"telegram_outage_window_seconds": 41
},
"q9_decision": {
"choice": "方案 A(journald 原地标准化)",
"reason": "运维人力不足(1人×10 profiles),无法承担 Loki 集群运维成本",
"trade_off": "日志仍分散在各机,但格式统一、零新增依赖",
"future_trigger": "profile > 20 或团队规模 > 3 人时评估方案 B"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- 01
- 关于这个博客08-24
- 03
- ORDER BY 配合 LIMIT 触发的索引选择陷阱 原创08-07