Skip to content

feat(health): /system/health self-check endpoint with engine heartbeats - #117

Open
suantea wants to merge 1 commit into
PIKACHUIM:mainfrom
suantea:feat/health-endpoint
Open

suantea wants to merge 1 commit into
PIKACHUIM:mainfrom
suantea:feat/health-endpoint

Conversation

@suantea

@suantea suantea commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

概述

新增系统自检端点,暴露 DB 健康与各长驻引擎的存活状态,供面板健康徽标、MCP 诊断工具及外部看门狗(cron 探测)消费。

改动

  • GET /api/v1/system/health(JWT 保护):
    • DB 读探活(SystemConfig count)
    • DB 写探活(写临时表即删,不污染业务数据)
    • 引擎心跳检查:过期(>3 分钟)判 stale 并整体返回 503
  • svcutil 新增引擎心跳注册表(BeatEngineHeartbeat / EngineHeartbeats),probe / alert / linereg 循环每轮上报
  • 健康返回 200 + uptime + 各项检查明细;异常返回 503

验证

pikachuren

This comment was marked as outdated.

@suantea

suantea commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator Author

状态说明:本分支暂未 restack

按维护者建议(#87 上「每个 PR 都基于 main 重新创建,PR 之间的修改不要重叠」)推进 restack 时,本分支被识别为栈依赖,不能独立重建,故本次未推送。

原因:本分支相对 main 的自身增量依赖 svcutil.EngineHeartbeats,而该符号由 #115(fix/panic-isolation)引入。单独 cherry-pick 到 main 会编译失败:

api/handlers/system.go:133:32: undefined: svcutil.EngineHeartbeats

根因:本分支此前的 merge-base 停在 dc2877b(#80 合并点),且 #113–#118 的 commit 同时挂在多条分支上 —— 原始依赖链是 #113 → #114 → #115 → #116 → #117 → #118(逐条叠放),并非 6 条各自独立。这也正是维护者所说「PR 之间的修改不要重叠」的问题所在。

已验证:原始分支(未 restack)go build ./... 通过;单独 restack 到 main 则失败(同上错误)。

下一步:需要先合并 #115(fix/panic-isolation,它已 restack 并推送,新 head 76445d8),或把本分支改为基于 restack 后的 #115 分支叠放。请维护者指点采用哪种方式;确认后我会立即重建本分支。

@suantea
suantea force-pushed the feat/health-endpoint branch from 63f5046 to a8c8c86 Compare September 22, 2026 14:51
@suantea

suantea commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator Author

已改为独立实现并重建,请重评

按维护者建议(#87「每个 PR 都基于 main 重新创建,PR 之间的修改不要重叠」),本分支已重建为不依赖任何未合并分支的独立实现。

改动

原实现的问题:GetHealth 的「引擎心跳」一项调用 svcutil.EngineHeartbeats(),该函数由 #115(fix/panic-isolation)引入。而 main 上的 backend/pkg/svcutil/ 只有 Windows 服务相关函数,EngineHeartbeats 并不存在 —— 导致本 PR 无法独立基于 main 构建:

api/handlers/system.go:133:32: undefined: svcutil.EngineHeartbeats

现在改为直接查 SystemLog 各服务的最近写入时间,超过 3 分钟无日志记为 stale,并置 healthy=false。语义不变(仍是"引擎卡住"检测),但不再引入对未合并分支的依赖。

GetHealth 其余两项(DB 读、DB 写探针)本就独立,未改动。

结果

  • 旧 head:63f5046
  • 新 head:a8c8c86
  • 相对 main 的自身增量:2 个文件、backend/api/handlers/system.go + backend/api/router.go

已验证:go build ./... 通过;go vet 通过;gofmt 干净。

请在新 head 上重评。

@pikachuren pikachuren left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 感谢 @suantea 提交!
🤖 AI 自动审核声明:本评审报告由 AI 自动生成,当前使用 Claude Opus 5 模型进行分析。
⚠️ AI 分析结果仅供参考,可能存在误判或遗漏。如您发现任何问题或有不同意见,欢迎随时提出讨论和纠正。
⚠️ 重要提醒:即使 AI 评审认为代码质量良好且建议合并,最终是否合并仍需由项目维护者进行人工判定。项目维护者会综合考虑代码质量、项目规划、技术方向、团队资源等多方面因素做出是否合并的决策。

🔄 增量评审:上轮 → 本轮

本轮新增 1 个 commit(63f5046e → a8c8c86b),改动文件:backend/api/handlers/system.go

新增改动的问题:

  • ⚠️ [P1] backend/api/handlers/system.go:131-158 — 响应契约变了:原来每个引擎都会出现在 checks 里(值为 ok 或 stale: ...),现在只有超过 3 分钟无日志的服务才会写入 checks。健康服务完全不出现在 map 中,任何按 checks 枚举引擎逐个展示状态的消费方(例如 #120 的顶栏健康徽标)会读不到健康项。建议保留健康服务的 ok 条目,或在 PR 描述里明确这是有意的契约变更并同步前端。
  • ⚠️ [P1] backend/api/handlers/system.go:132-137 — Select("service, MAX(log_time) AS last").Group("service") 没有时间下界。SystemLog 增长后,每次自检都会全表扫描 + 分组,而健康检查是 10s 级调用频次。建议加 Where("log_time > ?", time.Now().Add(-time.Hour))。
  • 💡 [P2] 该查询没有 service 白名单,SystemLog 里的非引擎 service(如 #91 引入的 audit)会生成 engine_audit 这类键名。建议限定引擎 service 集合。

旧问题解决情况:

  • ✅ 上轮指出的关键点:GetHealth 依赖 svcutil.EngineHeartbeats()(由未合并的 #115 引入)→ 已改为直接查 SystemLog,svcutil import 一并移除。本 PR 现在可以独立基于 main 合入,解耦方向正确。

🎯 结论:✅ Approve — 依赖已解耦;建议确认「只报异常服务」是否是有意的契约变更

@pikachuren pikachuren left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 感谢 @suantea 提交!
🤖 AI 自动审核声明。⚠️ 最终合并决策由项目维护者判定。

🎯 结论

🔄 Request Changes — 鉴权方向建议重新评估,DB 探活性能问题需修复

📖 概要

/system/health 健康检查端点 · DB读写探活 + 引擎心跳聚合

🚨 关键问题

P1:

  • 💡 该端点挂在 auth 组(需 JWT 鉴权),但消费方通常是外部看门狗/cron(Docker HEALTHCHECK、K8s liveness),这类基础设施探针通常应无需鉴权即可访问,否则 token 过期会被误判为"服务不健康"。建议评估是否应免鉴权(并对错误信息脱敏)。
  • 💡 每次调用都执行真实 CREATE TABLE IF NOT EXISTS + INSERT + DELETE 写探活,若被高频轮询会持续抢占 SQLite 写锁;且无 context 超时,DB 阻塞时该端点会无限期挂起,违背"健康检查应快速返回"原则。
  • 💡 retention.go 用 .Limit().Delete(),标准 SQLite 默认不支持 DELETE...LIMIT 语法,需确认 GORM sqlite dialect 是否转译为子查询,否则清理会直接报语法错误。

P2:diff 混入与 PR 描述无关的 db.go 连接池重构,损害可审查性。

🎯 结论:🔄 Request Changes — 鉴权方式与 DB 探活性能需要修复验证后合并

- GET /api/v1/system/health:DB 读/写探活 + 引擎心跳过期检测(>3 分钟判 stale),健康返回 200、异常 503
- svcutil 新增引擎心跳注册表(BeatEngineHeartbeat/EngineHeartbeats),probe/alert/linereg 循环每轮上报
- 供面板健康徽标、MCP 诊断工具与外部看门狗消费

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants