Files
MinerU/docs/next/telemetry-implementation-plan.md

362 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Telemetry Implementation Plan
状态: Draft
日期: 2026-06-22
依据: `docs/next/telemetry-minimum.md`
本文档记录 doclib 第一版 telemetry 的实现计划。指标定义、隐私边界和协议语义以 `telemetry-minimum.md` 为准;本文只描述开发顺序、代码挂点和实现约束。
## 1. 已确认实现口径
第一版只实现 doclib telemetry:
- doclib server 负责本地记录、聚合和 flush。
- CLI / `DoclibClient` 只传递 context 或 observation,不直接外发 telemetry。
- parser SDK、`mineru-kit parse`、`mineru-kit api-server` 不纳入第一版。
- `parse.wait.*` 不带 `error_code`。
- `error_code` 只出现在失败类结果 count metric:
- `parse.finished.count`
- `parse_task.finished.count`
- `parse_task.execute.count`
- `parse_task.write.count`
- duration bucket 不带 `error_code`。
- 成功状态不带 `error_code`,不上传 `none`。
第一版外部 metrics API 按正式接口实现:
```text
POST https://mineru.net/metrics/v2/metrics
```
请求规则:
- body 使用 compact JSON raw bytes,不能使用 HTTP client 的 `json=...` 自动序列化。
- body 包含 `api_version="v2"`。
- `schema_version` 使用字符串 `"1"`。
- header 包含 `Content-Type: application/json`、`X-Track-App-Key`、`X-Track-Ts`、`X-Track-Sign`。
- `X-Track-Sign = hmac_sha256(APP_SECRET, APP_KEY + ts + raw_body)`。
- `APP_KEY` / `APP_SECRET` 第一版内置在代码常量中,值从 `aaa.py` 同步。
- `preview` 只返回 request body,不返回签名 header。
- flush 不解析 response body,只看 HTTP status。
flush response 处理:
- `2xx`: batch accepted,删除对应 aggregate rows,计入 `succeeded`。
- `4xx`: 删除对应 batch rows,计入 `discarded`;与 2xx 一样更新 `last_flush_at`。
- `5xx` / network / timeout: 保留 rows,该 batch 留待下次 flush。
- 同一次 flush 中既有成功 / discard batch,又有失败 batch,返回 `partial_success`;全部失败返回 `failed`。
- response body 不写 DB、不进入 preview、不进入 CLI 输出、不进入日志。
official remote 判定:
- host 等于或后缀匹配 `mineru.net`、`mineru.org.cn` 时为 official。
- 不使用裸 substring 匹配,避免误判。
CLI unset prompt 覆盖:
- 触发: `parse`、`read`、`scan`、`watch`、`search`、`find`、`list`、`show`、`invalidate`、`forget`、`cleanup`。
- 不触发: `server`、`config`、`telemetry`。
- `--json`、CI、agent caller、非 TTY、server 不可访问时不提示。
## 2. 开发阶段
### Phase 1: Core 模块与 DB
新增模块:
```text
mineru/doclib/telemetry/
__init__.py
constants.py
buckets.py
context.py
caller.py
store.py
payload.py
observation.py
service.py
```
实现内容:
- `constants.py`: metric name、dimension whitelist、`METRIC_SPECS`、consent state、endpoint、API version、schema version、正式环境 app key/secret。
- `buckets.py`: duration、pages、file size、results bucket。
- `context.py`: `contextvars`、HTTP header parse、request context set/reset。
- `caller.py`: parent process tree inference,最大深度 8,只读进程名;无 Agent 命中时以 `stdin.isatty()` 判定 `user`。
- `store.py`: `telemetry_state` 和 `telemetry_aggregates` 读写。
- `payload.py`: preview / flush payload 组装,metrics 稳定排序。
- `service.py`: `record_*`、`status`、`preview`、`enable`、`disable`、`flush`。
DB 改动:
- 修改 `mineru/doclib/migrations/001_init.sql`,增加 `telemetry_state`、`telemetry_aggregates`。
- 修改 `mineru/doclib/app.py` 的 `REQUIRED_SCHEMA_TABLES`。
- 不做单独 migration,现阶段直接改 `001_init.sql`。
测试重点:
- bucket 边界。
- dimension normalize / whitelist / hash。
- state 初始化: `installation_id`、`consent_state=unset`、`last_flush_at=0`、`flush_locked_at=0`。
- disabled 后 record no-op,disable 清空 aggregates 但保留 `installation_id`。
- preview payload 不包含 forbidden fields。
### Phase 2: Context 传递与管理 API
接口模型:
- 在 `mineru/doclib/types.py` 增加 telemetry response / request models。
- 在 `mineru/doclib/base.py` 和 `AsyncDoclibInterface` 增加抽象方法。
- 在 `mineru/doclib/client.py` 增加 sync client 方法。
- 在 `mineru/doclib/server.py` 增加 route 实现。
新增 API:
```text
GET /telemetry/status
GET /telemetry/preview
POST /telemetry/actions/{action}
POST /observations
```
context 传递:
- `DoclibClient._request_model` 读取 contextvars,并发送:
- `X-MinerU-Telemetry-Source`
- `X-MinerU-Telemetry-Caller`
- server middleware 解析 header,缺失时默认 `source=http_api`、`caller=http_client`。
- request 结束后 reset context token。
测试重点:
- missing / invalid header 降级。
- SDK 默认 `source=sdk`,caller best-effort 推断。
- telemetry status / preview / enable / disable / flush 行为。
- `/observations` schema 校验和 disabled/unset 行为。
### Phase 3: Flush Worker
新增:
```text
mineru/doclib/background/telemetry_flush.py
```
`AppState` 增加:
```text
state.telemetry_store
state.telemetry_svc
state.telemetry_flush_worker
```
startup 顺序:
```text
DatabaseManager.initialize()
-> TelemetryStore(db)
-> TelemetryService(store, context collector)
-> business services with telemetry_svc
-> background workers
-> TelemetryFlushWorker
```
flush 行为:
- 启动后先尝试一次 flush。
- 每 2 小时尝试一次 flush。
- 只有 `consent_state=enabled` 才外发。
- 一次 flush 最多 10 个 period。
- 每个 HTTP request 只发送一个 period。
- period 按 `period_start ASC, period_end ASC`。
- lock TTL 1800 秒。
- 发送使用 `httpx.AsyncClient`,timeout 10 秒。
- 使用 compact JSON raw bytes + HMAC header。
测试重点:
- unset / disabled 不外发。
- no pending metrics 不外发。
- 2xx 删除 rows。
- 4xx 删除 rows,并计入 discard、更新 `last_flush_at`(`telemetry/service.py:138-144`)。
- 5xx/network 保留 rows。
- partial success。
- lock 存在时跳过。
- 一次最多 10 个 period。
### Phase 4: Route-level Metrics
先接入口体验层,不碰后台 worker 内部步骤。
parse route:
- `parse.request.count`
- `parse.finished.count`
- `parse.duration_bucket.count`
- `parse.files.count`
- `parse.pages.count`
- `parse.file_size_bucket.count`
- `parse.pages_bucket.count`
- `parse.invalidate.count`
search / find / content:
- `search.request.count`
- `search.finished.count`
- `search.duration_bucket.count`
- `search.results_bucket.count`
- `find.request.count`
- `find.finished.count`
- `find.duration_bucket.count`
- `find.results_bucket.count`
- `content.request.count`
- `content.finished.count`
- `content.duration_bucket.count`
scan / watch route:
- `scan.request.count` route 部分。
- `watch.add.count`
- `watch.add.finished.count`
- `watch.remove.count`
- `watch.remove.finished.count`
实现约束:
- route-level request metric 在进入业务 handler 后立即记录。
- FastAPI / Pydantic 进入 handler 前拒绝的请求不计。
- 失败路径可以计算 telemetry status / error_code,但必须 re-raise 原异常。
- telemetry 写入失败不能影响业务返回。
### Phase 5: Service / Worker Metrics
`ParseService`:
- 构造函数显式接收 `telemetry_svc`。
- 接 `parse_task.created.count`、`parse_task.started.count`。
- 在 `process_doc` 周围接:
- `parse_task.finished.count`
- `parse_task.duration_bucket.count`
- `parse_task.files.count`
- `parse_task.pages.count`
- 在 `_parse_via_local` / `_parse_via_api` 周围接:
- `parse_task.execute.count`
- `parse_task.execute_duration_bucket.count`
- JSON 写入、FTS 更新、docs metadata 更新作为 write 步骤:
- `parse_task.write.count`
- `parse_task.write_duration_bucket.count`
`ParseWorkerPool`:
- fallback exception 路径也必须记录 `parse_task.finished.count {status=failed}`。
`ScanService`:
- 构造函数显式接收 `telemetry_svc`。
- 接:
- `scan.reuse.count`
- `scan.finished.count`
- `scan.duration_bucket.count`
- `scan.files.count`
- internal watch-triggered scan 创建时记录 `scan.request.count {source=watch, caller=system}`。
Ingest:
- `refresh_file` / `ensure_ingested` / `ingest_file` 增加 keyword-only `trigger`,默认 `unknown`。
- 触发来源: `parse | scan | watch | show | background | unknown`。
- 记录:
- `ingest.finished.count`
- `ingest.duration_bucket.count`
测试重点:
- parse task created / started / finished。
- execute succeeded / failed。
- write succeeded / failed。
- write failed 同时导致 task finished failed。
- scan counters 映射到 `scan.files.count`。
- ingest trigger 和 skipped 规则。
### Phase 6: Observations 与 CLI Wait
`POST /observations`:
- 第一版只支持 `type=parse_wait`。
- 根据 `parse_ids` 查询 task,补齐 `tier` 和 `pages_bucket`。
- 不补齐、不上报 `error_code`。
- 记录:
- `parse.wait.count`
- `parse.wait_duration_bucket.count`
`DoclibClient`:
- 增加 `submit_observation()`。
CLI parse wait:
- wait 前记录 `wait_start = time.monotonic()`。
- done -> `status=succeeded`。
- failed -> `status=failed`。
- timeout -> `status=timeout`。
- Ctrl-C / cancel -> 尽量 `status=canceled`。
- observation 失败静默忽略。
### Phase 7: CLI Telemetry 命令与 Unset Prompt
新增:
```text
mineru/cli/commands/telemetry.py
mineru/cli/telemetry.py
```
`mineru` 顶层命令:
```text
mineru telemetry status
mineru telemetry enable
mineru telemetry disable
mineru telemetry preview
mineru telemetry flush
```
`TOP_LEVEL_COMMAND_ORDER`:
```text
telemetry
server
config
```
prompt helper:
- `is_interactive_cli()`
- `is_ci_environment()`
- `maybe_prompt_telemetry_consent(client, *, json_mode, command_name)`
触发范围:
- 触发: `parse`、`read`、`scan`、`watch`、`search`、`find`、`list`、`show`、`invalidate`、`forget`、`cleanup`。
- 不触发: `server`、`config`、`telemetry`。
- `--json`、CI、agent caller、非 TTY、server 不可访问时不提示。
## 3. 建议提交边界
建议拆成 5 个开发包:
1. `telemetry core`: DB、store、service、payload、buckets、context。
2. `telemetry api`: management API、observations API、client/base/types。
3. `telemetry flush`: worker、flush lock、正式 metrics API。
4. `telemetry metrics`: parse/search/content/scan/watch/ingest/parse_task 打点。
5. `telemetry cli`: commands、prompt、parse wait observation。
## 4. 高风险点
- `DoclibClient` 是 sync client,server 侧 telemetry service 是 async,不要混用。
- route decorator 要求 `base.py`、`client.py`、`server.py` 接口保持一致。
- `DatabaseManager` 当前每次操作新建连接,aggregate upsert 和 flush lock 要依赖 SQL 原子性。
- `error_code` 只在四个失败 count metric 上出现,不得加到 duration bucket。
- telemetry record 失败必须吞掉,业务异常必须原样返回。
- HMAC 签名必须使用发送出去的 exact raw body。
- preview 只展示 body,不展示 app key、secret、签名或 response。