12 KiB
Context
当前实现的配置、执行、存储、API 和 Dashboard 都以 HTTP 请求为中心:target.url 是必填字段,执行器直接 fetch(url),结果存储包含 status_code 与 latency_ms,前端展示 URL、method 和 HTTP 状态码。这种模型无法承载本地命令等非 HTTP checker,也让 expect 只能表达 HTTP response 的 status/header/body。
项目尚未上线,不需要兼容旧 YAML、旧数据库 schema 或旧 API 契约,因此本次设计选择直接建立 typed target 与领域专用 expect,而不是添加兼容分支。目标是让 HTTP 变成 runner 的一种实现,同时新增 command runner,并为未来其他 checker 类型保留清晰扩展点。
YAML target
│
▼
ResolvedTarget(type)
│
▼
ProbeEngine + concurrency limit
│
├─ http runner
│ └─ HTTP expect pipeline
│
└─ command runner
└─ command expect pipeline
│
▼
CheckResult(success, matched, durationMs, statusDetail, failure)
│
▼
SQLite + API + Dashboard
Goals / Non-Goals
Goals:
- 使用
target.type建模不同 checker 类型,v1 支持http与command。 - 将 HTTP 配置放入
target.http,将命令配置放入target.command,移除顶层 HTTP 字段。 - 为各 checker 类型定义领域专用 expect 名称,HTTP 使用
status、headers、body,command 使用exitCode、stdout、stderr。 - 为不同 checker 类型提供默认成功语义:HTTP 默认
status: [200],command 默认exitCode: [0]。 - 将可排序内容检查表达为数组,保证
body、stdout、stderr按配置顺序执行。 - 在 runner 和 expect pipeline 层共同实现快速失败,避免 status/header 已失败时仍读取或解析 body。
- 使用
durationMs表达 checker 执行耗时,替代 HTTP-only 的latencyMs。 - 引入结构化失败信息并入库,区分执行错误和 expect 不匹配,耗时阈值字段统一为
maxDurationMs。 - 引入全局并发限制和 100MB 默认读取上限,避免 HTTP body 或 command 输出造成资源失控。
Non-Goals:
- 不兼容旧的顶层
url、method、headers、body配置。 - 不做旧 SQLite schema 迁移;实现阶段可以按新 schema 初始化和测试。
- 不支持 shell 字符串命令;command v1 仅支持
exec + args。 - 不持久化完整 HTTP body、stdout 或 stderr,只持久化结构化失败摘要。
- 不引入新的解析或执行依赖。
- 不在本次实现告警通知、认证鉴权或动态增删目标。
Decisions
1. 使用判别联合建模 Target
配置和解析后的目标都使用 type 判别:
targets:
- name: "HTTP 健康检查"
type: http
http:
url: "https://example.com/health"
method: GET
- name: "Nginx 进程检查"
type: command
command:
exec: "pgrep"
args: ["nginx"]
理由:HTTP 与 command 的领域字段差异明显,强行把 URL、exec、status、exitCode 抽成统一字段会降低语义清晰度。判别联合可以让 TypeScript 在执行器选择、配置校验和 expect 校验中获得更明确的类型约束。
替代方案:保留顶层 url 并通过字段存在性推断 HTTP。该方案兼容性更好,但会继续让 HTTP 成为隐式默认类型,不符合当前无兼容包袱下的最佳模型。
2. defaults 分为通用和领域分组
建议配置形态:
runtime:
maxConcurrentChecks: 20
defaults:
interval: "30s"
timeout: "10s"
http:
method: GET
maxBodyBytes: "100MB"
command:
cwd: "."
maxOutputBytes: "100MB"
通用默认值只覆盖所有 checker 都共享的调度与超时字段,领域默认值只覆盖对应 target type。target 自身配置优先级高于 defaults。
替代方案:继续使用 defaults.method、defaults.headers 等 HTTP 字段。该方案会在 command target 中产生无意义字段,因此不采用。
3. 默认 expect 是逻辑默认值
当用户未显式配置对应状态类 expect 时,runner 在校验阶段应用领域默认值,而不是把默认值写回用户配置。
HTTP 默认:status: [200]。
Command 默认:exitCode: [0]。
示例:
expect:
body:
- contains: "ok"
该 HTTP target 仍然先检查 status == 200,再检查 body。这样用户只写内容检查时不会把 HTTP 500 错误响应误判为 UP。
替代方案:只有完全不写 expect 时才应用默认值。该方案会让“只写 body”绕过 status 检查,不符合默认成功语义,因此不采用。
4. Expect pipeline 使用固定阶段顺序和有序规则数组
HTTP 顺序:
status -> duration -> headers -> body[0] -> body[1] -> ...
Command 顺序:
exitCode -> duration -> stdout[0] -> stdout[1] -> ... -> stderr[0] -> stderr[1] -> ...
body、stdout、stderr 使用数组表达配置顺序:
expect:
body:
- contains: "healthy"
- json:
path: "$.status"
equals: "ok"
- regex: '"version":"\\d+\\.\\d+"'
理由:对象字段天然更像无序集合,不适合表达用户指定的检查顺序。数组规则可以直接生成 path,例如 expect.body[1].json($.status),方便失败定位。
替代方案:保留对象结构并约定 contains/regex/json/css/xpath 固定顺序。该方案无法满足“按配置文件中的配置顺序依次检查”的要求,因此不采用。
5. 复用通用值操作符,但保持领域 expect 名称
保留并扩展现有操作符:equals、contains、match、empty、exists、gte、lte、gt、lt。这些操作符可用于 HTTP header、HTTP body 提取值、command stdout/stderr 文本等。
领域名称保持专用:HTTP 使用 status,command 使用 exitCode;HTTP body 可使用 json/css/xpath,command 输出只使用文本规则和通用操作符。
替代方案:把所有值统一抽象成 status、metadata、payload。该方案过度泛化,会让 YAML 对使用者不直观,因此不采用。
6. Runner 负责按需产生 Observation
HTTP runner 不应总是读取完整 response body。它先发起请求并取得 status、headers 和 duration,再运行 status/duration/headers 阶段;只有配置中存在 body 规则且前置阶段通过时,才读取 body,并受 maxBodyBytes 限制。
Command runner 需要执行命令并收集 exitCode、duration、stdout、stderr。stdout 和 stderr 合计受 maxOutputBytes 限制,默认 100MB。命令超时或输出超限时,runner 产生 success=false 和 failure.kind=error。
替代方案:runner 总是完整产生所有字段,再交给 expect。该方案实现简单,但无法真正快速失败,也无法避免不必要的资源读取,因此不采用。
7. Command 执行不经过 shell
command target 使用 exec + args,实现阶段优先使用 Bun 可用的子进程 API,并禁止默认 shell 展开。
command:
exec: "pgrep"
args: ["nginx"]
cwd: "."
env:
LANG: "C"
cwd 相对配置文件所在目录解析。env 默认继承当前进程环境并允许覆盖指定键。v1 不支持 stdin,避免命令阻塞。
替代方案:允许 shell: "pgrep nginx | wc -l"。该方案更灵活,但引入转义、注入和跨平台 shell 差异,不适合作为第一版默认能力。
8. 全局并发限制由 ProbeEngine 统一执行
runtime.maxConcurrentChecks 默认 20。调度仍按 interval 分组触发,但每个目标进入全局并发池后再执行,避免同一 tick 或多个 tick 同时启动过多 HTTP 请求和本地进程。
理由:command target 可能启动本地进程,继续无限 Promise.allSettled 会有资源风险。全局限制比按组限制更容易理解,也能覆盖不同 interval 组同时触发的情况。
替代方案:为 HTTP 和 command 分别设置并发上限。该方案更精细,但增加配置复杂度,当前需求只要求全局默认值。
9. CheckResult 使用结构化 failure
结果模型区分 runner 执行失败和 expect 不匹配:
interface CheckFailure {
kind: "error" | "mismatch";
phase: "status" | "duration" | "headers" | "body" | "exitCode" | "stdout" | "stderr";
path: string;
expected?: unknown;
actual?: unknown;
message: string;
}
success=false 表示 runner 未能正常产生可校验结果,例如网络错误、超时、命令启动失败、输出超限。matched=false 表示 runner 执行成功但 expect 不匹配。failure 字段存储首个失败原因,实际值需要截断,避免超长内容或敏感内容进入数据库和 API。
替代方案:继续只存 error 字符串。该方案无法区分执行失败与规则不匹配,也不能准确定位失败 path,因此不采用。
10. 存储、API、Dashboard 改为 checker 通用语义
SQLite schema 建议从 HTTP-only 字段调整为:
targets:
id, name, type, target, config, interval_ms, timeout_ms, expect
check_results:
id, target_id, timestamp, success, matched, duration_ms, status_detail, failure
target 是用于展示和搜索的目标摘要,例如 HTTP URL 或 command 命令行摘要;config 持久化解析后的领域配置 JSON;status_detail 存储领域状态摘要,例如 HTTP 200 或 exitCode=1。
API 共享类型使用 durationMs、statusDetail、failure,Dashboard 表格展示“类型、目标、状态、耗时、最近失败原因、趋势”。HTTP 详情可显示 status code,command 详情可显示 exit code,但列表层不使用 HTTP-only 列名。
替代方案:继续保留 url、method、status_code、latency_ms 并为 command 填空。该方案会把领域语义混在一起,后续扩展成本高,因此不采用。
11. Size 字符串解析
新增 size 解析支持 B、KB、MB、GB,默认 100MB 等于 104857600 bytes。HTTP maxBodyBytes 限制单次 body 读取,command maxOutputBytes 限制 stdout 和 stderr 合计读取。
理由:YAML 直接写字节数可读性差,二进制单位更适合内存和 buffer 限制。
替代方案:复用 duration 解析或只接受 number。前者语义不匹配,后者配置可读性差。
Risks / Trade-offs
- [Risk]
maxConcurrentChecks=20且单次读取上限为100MB时理论内存峰值较高 → [Mitigation] 提供全局并发限制和 per-target/per-default 读取上限,文档明确资源上限由用户配置共同决定。 - [Risk] 结构化失败信息可能包含敏感响应片段或命令输出 → [Mitigation] 只存首个失败原因,
actual做长度截断,默认不持久化完整 body/stdout/stderr。 - [Risk] command checker 允许执行本地命令,有误配置或高开销命令风险 → [Mitigation] 不支持 shell,强制 timeout,限制输出大小,使用全局并发限制。
- [Risk] 不兼容旧配置会导致现有样例和测试全部失效 → [Mitigation] 项目未上线,实施时同步更新 README、示例配置、单元测试和 smoke test。
- [Risk] SQLite schema 重建会丢失旧数据 → [Mitigation] 当前无上线数据,不做迁移;若后续需要升级已部署实例,应另起兼容迁移 change。
Migration Plan
- 更新类型定义、配置解析和 README 示例,先让新 YAML 契约成为唯一入口。
- 重构存储 schema 和共享 API 类型,再更新 Dashboard 使用新字段。
- 引入 expect 规则数组和结构化 failure,迁移 HTTP runner 到新 pipeline。
- 添加 command runner,并接入 ProbeEngine 的 runner 选择与全局并发限制。
- 更新测试覆盖配置、HTTP expect、command expect、存储、API、Dashboard 和 smoke test。
- 运行
bun run check和bun run verify,确保完整质量门禁通过。
Open Questions
无。当前讨论已确认默认 HTTP status 使用 [200]、默认并发限制使用全局配置、HTTP body 与 command 输出默认上限均为 100MB。