Files
chorus/docs/06-troubleshooting.md
T

9.1 KiB
Raw Blame History

generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件) wiki_page: Troubleshooting wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Troubleshooting wiki_revision: 9803ea981876455054c5598a38b21fd030211d4c synchronized_at: 2026-08-26T01:08:16Z

故障排查

排查顺序

1. 环境与工作区

git status --short --branch
go version
mysql --version
migrate -version
node --version
pnpm --version

产品构建目标为 Go 1.26.5、MySQL 8.0、Node >=22、pnpm 9.15.1。工具缺失或版本不符先记录环境问题;不要通过降低文档版本或改代码绕过。

2. 判断同步还是异步

现象 首查
提交 4xx、没有卡片 portal 认证/CSRF/上传/幂等
卡片出现但一直 queued worker、MySQL 版本、取任务索引
running 超时 lease owner/token/until、上游和存储超时
succeeded 但无内容 output 记录、原子文件、鉴权/缩略图
内容不符 rendered_prompt 与模板
管理端 CRUD 不符 migrations、导表元数据和生成差异

3. 查库但不手工改状态

SELECT id, user_id, kind, status, provider_model_id, attempt_count,
       error_code, lease_owner, lease_token, lease_until,
       latency_ms, created_at, finished_at
FROM generations
ORDER BY id DESC
LIMIT 10;
  • pending 不动:worker 未启动、数据库不支持 SKIP LOCKED 或索引/事务错误。
  • running 且租约过期:新 worker 应直接原子替换 token,不应先改回 pending。
  • 同一任务出现互相覆盖:检查最终 UPDATE 是否匹配当前 lease_token;陈旧 worker 应影响 0 行。
  • 400/401/策略拒绝却有多 Provider attempts:retryable 写反。
  • 429/5xx/超时/连接错误没有故障转移(MVP-1):候选池、上限或熔断状态错误。
  • failed 没有 error,或 succeeded 没有 rendered_prompt/attempts:违反持久化红线。

终态 error_code 的含义必须区分清楚,不要凭它跳过 attempts:

终态码 含义 该看什么
route_unavailable 一次上游都没调过——成员被禁用、熔断打开或能力不匹配,选不出候选 路由池成员、Provider/Model 启停、熔断状态
failover_exhausted 调过上游且失败可重试,但已经没有候选可换 attempts 中每次尝试自身的 error_code,那才是真实原因
upstream_* 不可重试失败,直接终止 该次尝试的错误码与 attempts 长度是否为 1

attempts 中每条尝试记录的 error_code 是该次调用自身的结果,不会被终态码覆盖。若看到某条尝试同时出现 error_code=route_unavailable 与 retryable=true、且有实际 latency_ms,说明运行的是修复前的版本(见 #63),该记录不可信。

lease_token 属运行控制信息,不在对外响应或普通日志完整输出。排查记录使用任务 id 和脱敏摘要。

4. 用 mock 隔离上游

用 mock 构造成功、429、5xx、超时、连接错误、400、401、内容拒绝和恶意结果 URL。mock 正常而真实异常,再查 ProviderModel、网络和上游;禁止用真实额度循环试错。真实连通性只通过受审计且有冷却的按钮触发。

5. SSRF 与网络

依次检查:

  1. base_url scheme/host/port,并确认非标准公开端口已显式加入 CHORUS_PROVIDER_ALLOWED_PORTS;未设置时只允许 80/443;
  2. DNS 的每个 IPv4/IPv6 结果;
  3. DialContext 是否拒绝私网、回环、链路本地、组播和未指定地址;
  4. 每次 redirect 是否重新校验并限制次数;
  5. 环境代理是否绕开安全 Dial;
  6. 上游返回的结果 URL 是否复用相同客户端。

若 attempt latency 精确接近 Provider HTTP timeout,说明请求被本地切断,不能据此判断上游无响应。provider.http_timeout_seconds 作用于 http.Client.Timeout,是从发起请求到读完 body 的绝对总时长;上游可能先长时间不返回任何字节(生成阶段),再以低速流式传输大响应体,两段都计入其中。按以下顺序确认:

  1. portal/config/settings.yml 的 provider.http_timeout_seconds,以及 worker.lease_seconds 是否比它大 5 秒以上,否则 Portal 拒绝启动;
  2. 管理端的 ProviderModel timeout 不能突破更短的全局 HTTP timeout;
  3. 仍无法判断时,经授权对上游发一次不设客户端上限的受控请求,记录 ttfb、总时长和响应体大小,据此定值;不要靠反复上调超时试错。

修改任何一层后必须重启 Portal。

参考基线(2026-08-25 实测,自定义 Provider 的 gpt-image-2 文生图):生成阶段约 160 秒无任何数据,随后以 17–25 KB/s 流式传输,单次总时长 385–465 秒,响应体 3.4–4.9 MB。同类任务报超时前,先用 attempts 中的 latency_ms 与该基线比较。

拦截私网是正确行为,不能为联调关闭。需要访问内部 mock 时把 mock 与测试进程放在专用测试网络,并使用测试专用注入点,不在生产配置放行私网。

6. 文件和权限

  • 检查临时文件与最终目录是否同文件系统,rename 是否原子;
  • 检查失败/陈旧 worker 是否只清理自己创建的临时文件;
  • 检查数据库 output 与实际文件一致;
  • 403 多为用户归属校验,404 多为记录/文件不一致;
  • 不得临时把存储目录改成无鉴权静态目录。

7. go-admin 与代码生成

现象 处理
生成器看不到表 先确认隔离库已执行 migrations,再导入元数据
“迁移脚本”没有建表 它主要生成菜单/API 配置,不是业务 DDL
生成后菜单立刻变化 可能调用了副作用 /gen/todb;停止并恢复隔离库
生成文件落错目录 检查固定提交的 generator 配置,人工审查差异
生产能访问 dev-tools 高风险发布阻断;下线路由而非只隐藏菜单
启动时表结构变化 检查是否误接 AutoMigrate,立即停止部署

8. 文档与 Harness

症状 处理
strict 缺章节 恢复精确标题
sync --check 不一致 先判断 Wiki 是否更新;不要直接改镜像
sync 因本地镜像改动停止 保留用户改动,查清来源后处理
Wiki 无 revision 停止同步,从失败页面继续
API 401 从安全环境重新加载有效 PAT,不打印令牌
MCP 连接失败 在工单记录原因后回退 API

9. 仍未定位

在工单记录最小复现、任务 id、脱敏 attempts/error、租约时间线、相关日志和已排除项。不得粘贴 API Key、Cookie、真实 prompt、用户文件或生产数据。

必须停止的情况

  • 需要修改 retryable、SSRF、密钥、租约、迁移、身份或权限;
  • 需要手工改共享/生产库、回退状态、删除数据或文件;
  • 想通过 AutoMigrate、关闭 SSRF、公开存储或暴露 dev-tools 临时绕过;
  • 需要反复真实上游调用;
  • 日志疑似泄密或包含个人/生产数据;
  • 同一阻塞尝试两次仍不能确认根因。

管理端登录后无菜单或 Chorus API 返回 403

  1. 确认数据库已执行到最新 migration,sys_casbin_rule 中存在 chorus_operator 对应 /api/v1/chorus/* 权限。
  2. 当前实现只读取 sys_casbin_rule 并关闭 Casbin adapter AutoMigrate;若旧进程曾生成空的 casbin_rule,它不是权限事实来源,不要向其中补数据。
  3. 确认 bootstrap 账号绑定启用的 chorus_operator,重新登录取得新 JWT,再检查 /api/v1/menurole。
  4. settings.yml 出现 Unknown database 时先创建空库并执行版本化 migration;出现 Access denied 时修正本地凭据,不运行 AutoMigrate 绕过。

API Key 审计排查(#45)

  1. 先使用服务端 request_id、API Key 数据库编号或 generation 编号查询 api_audit_events,不要用完整 Key、Prompt、文件名或文件内容搜索日志和数据库。
  2. OpenAPI 提交没有事件时,先判断请求是否通过 API Key 认证。缺失、未知、过期、已撤销或停用用户的 Key 按设计不写数据库审计;这不是审计丢失。
  3. 已认证提交或限流拒绝没有事件时,检查 api_audit_events 写权限、外键目标、JSON CHECK 和应用日志中的 api audit write failed request_id=...。不要临时关闭审计或把请求正文写入日志。
  4. 管理员撤销应同时出现 admin_audit_events.action=api_key.revoke 和 api_audit_events.action=api_key.admin_revoked。只出现一类表示事务没有按设计提交,应停止重复操作并检查数据库错误。
  5. last_used_at 在一分钟内不变化是节流行为,不代表认证未发生。判断调用结果使用请求状态和对应提交审计。
  6. go-admin 鉴权失败可能使用 HTTP 200 包装 JSON code=401|403;排查权限时同时检查 JSON 业务码、JWT 和 Casbin,不把 HTTP 200 误判为已授权。
  7. 任何 summary 出现 Prompt、完整 Key、public_id、secret_hash、Authorization、Cookie、响应正文或原始 IP 都属于安全缺陷,应立即停止相关入口并建立缺陷工单,不直接清理审计数据。