故障排查
按「先看日志、再定位子系统」的顺序排查问题。常见问题的快速解答见常见问题。
第一步:看日志
如何调整日志详细程度?
日志级别与过滤由 RUST_LOG 环境变量控制(默认 info)。常用取值:
# 完整调试输出(更细可用 trace)
RUST_LOG=debug thurgio serve
# 只看订阅抓取流程(更新 + HTTP 客户端)
RUST_LOG="thurgio_app_update=debug,thurgio_request=debug" thurgio serve
# 只看数据库操作 / 模板渲染 / 配置热重载
RUST_LOG=thurgio_db=debug thurgio serve
RUST_LOG=thurgio_renderer=debug thurgio serve
RUST_LOG=thurgio_infra=debug thurgio serve
# 生产排障:安静模式
RUST_LOG="thurgio=info,warn" thurgio serve
日志在哪里看?
thurgio 把日志输出到 stdout/stderr:
- systemd 部署:由 journald 收集,journald 自带容量与轮转管理,无需额外配置:
journalctl -u thurgio -f # 跟随最新日志 journalctl -u thurgio -n 200 # 最近 200 行 journalctl -u thurgio --since "1 hour ago" - 仅当你自行把日志重定向到文件时才需要配置 logrotate,示例见部署指南。
需要结构化日志时设置 RUST_LOG_FORMAT=json,便于用 jq 过滤:
RUST_LOG_FORMAT=json thurgio serve | jq .
如何用 trace_id 追踪一次请求?
HTTP 错误响应的 JSON 体中带有 trace_id 字段。文本日志中直接搜索 trace_id= 即可定位同一次更新流程的全部日志;JSON 日志用 jq 过滤:
RUST_LOG_FORMAT=json thurgio serve | jq 'select(.fields.trace_id == 42)'
服务无法启动
报端口被占用怎么办?
默认监听 0.0.0.0:10100(setting.address / setting.port)。找出占用端口的进程:
ss -ltnp 'sport = :10100'
然后停掉占用进程,或换端口启动(命令行参数优先于配置):
thurgio serve --address 127.0.0.1 --port 10200
# 等价环境变量:ADDRESS=127.0.0.1 PORT=10200 thurgio serve
配置解析错误起不来怎么办?
thurgio check
check 会解析完整配置并打印逐项检查结果,不访问网络、不写任何文件。如果是改配置改坏的,直接回滚:
thurgio config list
thurgio config restore <备份名>
config 命令不加载配置,坏配置下也能运行。更多配置陷阱(规则名拼错被静默忽略等)见常见问题。
TLS 启动失败怎么办?
TLS 通过 --tls-cert / --tls-cert-key(或环境变量 TLS_CERT / TLS_CERT_KEY)启用。逐一排查:
# 1. 证书与私钥文件存在且进程用户可读
ls -l cert.pem key.pem
sudo -u thurgio head -c 64 /etc/thurgio/cert.pem
# 2. 同时提供明文与 HTTPS 时,--tls-port 必须与 --port 不同
thurgio serve --tls-cert cert.pem --tls-cert-key key.pem --tls-port 10443
# 3. --tls-only 要求证书与私钥同时提供,缺一会报错
thurgio serve --tls-only --tls-cert cert.pem --tls-cert-key key.pem
生产环境更常见的做法是让反向代理(nginx/Caddy)终止 TLS,thurgio 只监听 127.0.0.1,参见部署指南。
提示数据库打不开或权限不足怎么办?
数据库位置由 DATABASE_URL 指定(默认相对数据根目录 -r/--root 的 thurgio.db),所在目录必须对运行用户可写:
ls -ld /var/lib/thurgio
sudo -u thurgio test -w /var/lib/thurgio && echo writable
可用公开端点确认数据库状态:GET /ready 在数据库可读时返回 200 {"status":"ready",...},不可读时返回 503 {"status":"not_ready","database":"disconnected"}。
HTTP 错误码速查表
所有 API 错误以 JSON 返回,形如 {"error":"circuit_open","message":"...","kind":"CircuitOpen","status":503,"trace_id":42}。完整端点语义见 API 参考。
| 状态码 | 原因 | 排查动作 |
|---|---|---|
401 | 令牌无效/缺失,或作用域不足(Read 令牌访问 /app/admin/*);上游订阅源返回 401/403 时也会归入此类 | 核对用的是 Read 还是 Admin 令牌组、请求的是哪个命名空间;用 POST /app/login 校验令牌作用域 |
403 | 通常并非 thurgio 返回——多来自反向代理的访问控制(如限制 /metrics 的内网白名单) | 检查 nginx 的 allow/deny 或 Caddy 的 respond 403 规则 |
404 | 资源不存在:provider/snippet 名字拼错,或该 provider 响应中没有可解析的流量元数据 | 用 GET /app/read/subs 列出全部订阅名核对拼写 |
429 | 超过速率限制(默认每 IP 每 60 秒 600 次)或并发限制,响应带 Retry-After 头 | 按 Retry-After 等待后重试;降低客户端轮询频率;必要时用 THURGIO_RATE_LIMIT_REQUESTS / THURGIO_RATE_LIMIT_WINDOW_SECONDS 调整限额 |
500 | 服务器内部错误(数据库/存储、模板渲染、内部异常) | 取响应体中的 trace_id 到日志中定位;检查模板语法与磁盘空间 |
502 | 从上游订阅源抓取失败或超时(上游请求超时默认 9 秒,REQUEST_TIMEOUT 可调)且无缓存可回退 | 直接 curl 上游 URL 验证可达性(DNS、网络、上游凭证) |
503 | 两种情况:渲染时熔断器已打开且无缓存可回退(kind: "CircuitOpen");或 GET /ready 报告数据库不可读 | 前者见下节「订阅抓取失败」;后者检查数据库路径与磁盘 |
订阅抓取失败
上游挂了构建会失败吗?
不会立即失败。抓取失败时回退到数据库中的 last-known-good 缓存继续渲染;只有连缓存都没有时才会向上传播错误。
熔断器 open 是什么意思?
连续失败达到阈值(默认 5 次)后熔断器进入 open:一段时间内(基础 30 秒,带随机抖动)跳过该源不再发起请求,避免无效重试风暴。超时后进入 half-open 放行探测请求,探测成功即回到 closed。熔断器状态持久化到数据库,重启后恢复。
日志中搜索 circuit breaker 可以看到状态切换(如 circuit breaker opened after N failures、错误 circuit breaker open for <name>)。
如何观察各订阅源的健康状况?
curl "http://localhost:10100/app/read/health?token=my-token"
为每个 provider/snippet 报告熔断器状态(closed/open/half-open)、失败计数、最后失败时间与最后成功更新时间,并汇总为总体状态:healthy(无 open/half-open)、degraded(部分熔断)、unhealthy(全部 open)。
上游恢复了,如何让它立刻重试?
- 等待半开探测自动恢复(无需干预);或
- Admin 令牌强制刷新一次:
GET /app/admin/refresh;或 - 经
GET /app/admin/s(prov/url请求头)更新源 URL——会同时重置该 provider 的熔断器并触发强制更新。
输出不更新
为什么改了上游但输出没变?
更新按 TTL 判断:缓存未过期时直接复用,不重新抓取。TTL 在 setting.ttl 中配置,默认 provider 600 秒(最小 60 秒)、snippet 86400 秒(最小 300 秒):
setting:
ttl:
provider: 600
snippet: 86400
想立即刷新:
thurgio build --force # 忽略缓存状态强制重新获取所有订阅源
服务运行时等价的 Admin 端点:GET /app/admin/refresh(仅更新)或 GET /app/admin/write(更新 + 渲染 + 写盘)。
为什么 --watch 下改动没有立即重建?
监听模式的变更事件经 500ms 防抖合并后才触发重建,属正常设计。另外它忽略构建自身的写入(输出目录与 SQLite 数据库文件),避免自触发循环。
为什么 serve 下改模板没生效?
serve 只监听主配置文件本身,模板变更不在监听范围内。两种解决方式:
# 方式一:开发模式启动(每次渲染前重新加载模板)
thurgio serve --dev
# 方式二:不重启,手动触发模板引擎重载
curl "http://localhost:10100/app/admin/reload?token=my-admin-token"
输出文件缺失、规则静默失效等其他「不生效」场景见常见问题。
数据库问题
目录下的 thurgio.db-wal / thurgio.db-shm 是什么?
thurgio 以 SQLite WAL 模式运行数据库(支持多进程共享同一文件)。-wal 与 -shm 是 WAL 模式的伴生文件,属于正常现象。不要在服务运行期间手动删除或移动它们——连同主库文件一起由 SQLite 管理,删除可能导致数据丢失。
怀疑磁盘空间不足怎么排查?
磁盘写满时数据库写入与输出文件发布都会失败,典型表现是请求返回 500(Persistence 类错误)或 GET /ready 变为 503。排查:
df -h /var/lib/thurgio # 检查数据库与输出所在分区
du -sh /var/lib/thurgio/* # 定位大文件
清理出空间后,确认 /ready 恢复 200 即可;无需迁移数据。