# Telemetry 设计 状态: Superseded 取代说明: 本文已被 `telemetry-minimum.md` 全面取代,仅保留历史设计参考;现状以 `telemetry-minimum.md` 与代码为准。 读者: 产品负责人、核心开发者、doclib server 开发者、数据分析与合规参与者 范围: 下一代 MinerU 的匿名使用统计、稳定性指标和粗粒度环境画像 非目标: 计费、审计、安全风控、精确故障追踪、上传文档内容或用户输入 ## 1. 目标 Telemetry 的首要目标是产品使用统计,用于评估 MinerU 的使用规模和产品影响力。 P0 同时兼顾三类问题: 1. 产品使用统计: 有多少安装实例在使用、每天使用多少次、处理了多少文件和页面。 2. 质量与稳定性: 哪些主链路失败、失败发生在哪个阶段、错误码分布如何。 3. 性能与环境画像: 不采集细粒度设备指纹,只用粗粒度字段理解运行环境和处理规模。 Telemetry 数据只用于趋势分析和产品改进,不用于计费、安全判断或强一致审计。 ## 2. 采集边界 只有 doclib server 上报 telemetry。 `mineru` CLI 依赖 doclib server,因此可以被间接统计。以下纯工具无 telemetry 能力: - parser SDK,包括 `mineru.parser.parse()` 和 `parse_async()` - `mineru-kit parse` - `mineru-kit api-server` - 其他不依赖 doclib server 的专家工具 这个边界避免 parser/tool 层在作为库或底层工具使用时包含 telemetry 相关配置、存储或网络行为。 ## 3. 默认策略 首次启动 doclib server 时,必须要求用户完成 telemetry 选择。 默认行为: - 首次选择界面不预选开启或关闭。 - 用户可以关闭。 - 用户选择结果记录为 `consent_state`。 - 用户关闭 telemetry 后,不再记录新的 telemetry metrics,也不再上报。 - 关闭时,DB 中未上报 telemetry 数据应删除,避免后续误发。 用户必须可以通过命令查看和修改状态: ```bash mineru telemetry status mineru telemetry enable mineru telemetry disable mineru telemetry preview mineru telemetry flush ``` `preview` 应展示将要上报的 JSON,但不得展示任何 token 或服务端内部凭据。 ## 4. 隐私边界 Telemetry 禁止采集: - 文档内容 - 文件名 - 文件路径 - 原始 URL - search query - prompt - sha256 - job_id - parse_id - traceback - exception message - hostname - 用户名 - 邮箱 - 账号 ID - CPU/GPU 具体型号 - GPU 显存、驱动版本、CUDA 版本 允许采集的数据必须来自白名单字段。不得把请求体、异常对象、配置对象、`kwargs` 或任意字典直接透传到 telemetry。 ## 5. 统计口径 P0 使用匿名 `installation_id` 作为主统计口径。 - `installation_id` 在 doclib server 首次启用 telemetry 时生成。 - `installation_id` 存储在 doclib DB 的 telemetry 内部状态中。 - 不依赖账号体系。 - 用户删除配置或重装后,会被视为新的安装实例。 服务端基于 `installation_id` 计算: - 安装量: 出现过的唯一 `installation_id` - DAU: 当日有 telemetry 上报的唯一 `installation_id` - WAU / MAU: 对应周期内有 telemetry 上报的唯一 `installation_id` 如果未来引入账号,账号只能作为可选维度,不替代 `installation_id`。 ## 6. 上报模型 P0 采用本地聚合 metrics 上报,不采用每个事件即时上报。 运行时行为: ```text doclib server runtime -> record metric delta -> update telemetry aggregate in doclib DB -> periodic flush -> POST telemetry endpoint -> delete flushed rows from doclib DB after success ``` 建议聚合窗口: - 本地 telemetry 配置和 telemetry 聚合数据都存储在 doclib DB 中。 - 本地按 1 小时 period 聚合。 - 上报时可以一次提交多个未 flush 的小时级 period。 - 启动后尝试 flush 一次。 - 运行中每 24 小时尝试 flush 一次。 - 网络失败或服务端 5xx 时保留本地聚合结果,后续重试。 - 服务端 4xx 表示 payload 不合法,对应 batch 应丢弃或进入本地失败记录,不应无限重试。 - flush 成功后,已被服务端确认接收的 telemetry 聚合数据应从 doclib DB 中删除。 - 同一个 period 内 context 变化可以接受;P0 flush 时使用当前 context 上报该 period 的聚合 metrics。 主流程不得等待 telemetry 上报。telemetry 失败不得影响 parse、watch、search 或 doclib API。 ## 7. Endpoint 建议使用独立 telemetry endpoint: ```http POST https://telemetry.mineru.net/v1/metrics Content-Type: application/json ``` 不建议与 `mineru.net/api` 的 parse API 混在一起,因为 telemetry 的认证、限流、存储和合规策略不同。 响应示例: ```json { "object": "telemetry_metrics_result", "accepted": 42, "rejected": 0, "server_time": "2026-06-10T12:00:00Z" } ``` 如果服务端支持部分拒绝,可以返回: ```json { "object": "telemetry_metrics_result", "accepted": 40, "rejected": 2, "errors": [ { "index": 5, "code": "invalid_dimension", "message": "dimension value is not allowed" } ], "server_time": "2026-06-10T12:00:00Z" } ``` 错误信息只用于解释 schema 问题,不应包含用户上报原文中的敏感字段。 ## 8. Payload 结构 一次上报包含一个 batch。环境信息放在 batch-level `context` 中,不重复放入每个 metric。 ```json { "batch_id": "tb_01HX...", "schema_version": "1", "installation_id": "inst_01HX...", "period_start": "2026-06-10T14:00:00Z", "period_end": "2026-06-10T15:00:00Z", "context": { "app": "mineru", "app_version": "3.0.0", "os": "macos", "arch": "arm64", "python_version": "3.11", "install_channel": "pip", "cpu_count_bucket": "9_16", "gpu_vendor": "apple" }, "metrics": [ { "name": "parse.completed.count", "value": 12, "dimensions": { "source": "cli", "caller": "agent", "tier": "basic(default)", "status": "succeeded" } }, { "name": "parse.processed_page_count", "value": 1240, "dimensions": { "source": "cli", "caller": "agent", "tier": "basic(default)" } } ] } ``` 规则: - `batch_id` 由本地 flush 任务生成,用于服务端短窗口幂等。 - `value` 必须是非负数。 - 字符串数据只能出现在 `context` 或 `dimensions`。 - `context` 描述安装实例、运行环境和版本。 - `metrics[].dimensions` 描述当前 metric 的业务分类。 - `metrics[].dimensions` 必须来自白名单,不能包含任意字符串。 - `context` 表示 flush 时 doclib server 的当前运行环境。同一 period 内 context 变化可以接受,不要求拆分 batch。 ## 9. Context 字段 P0 context 字段: | 字段 | 类型 | 枚举 / 格式 | 说明 | |------|------|-------------|------| | `app` | string | `mineru` | 固定应用名 | | `app_version` | string | semver 或发布版本 | MinerU 版本 | | `os` | string | `macos` / `windows` / `linux` / `other` / `unknown` | 操作系统 | | `arch` | string | `x86_64` / `arm64` / `other` / `unknown` | CPU 架构 | | `python_version` | string | `3.10` / `3.11` / `3.12` / `3.13` / `other` / `unknown` | Python 主次版本 | | `install_channel` | string | `pip` / `uv` / `docker` / `source` / `unknown` | 安装来源 | | `cpu_count_bucket` | string | `1_4` / `5_8` / `9_16` / `gt_16` / `unknown` | CPU 核心数分桶 | | `gpu_vendor` | string | `nvidia` / `apple` / `amd` / `none` / `unknown` | GPU 或常见加速器厂商 | `gpu_vendor` 规则: - `nvidia`: 识别到 NVIDIA GPU。 - `apple`: 识别到 Apple GPU。 - `amd`: 识别到 AMD GPU。 - `none`: 明确没有发现 GPU。 - `unknown`: 无法检测、检测失败,或发现了 P0 未分类的其他加速器。 P0 不采集 CPU/GPU family、具体型号、显存、驱动和 CUDA 版本。 ## 10. Metric 与 Dimension 规则 Metric 的 `name` 表示数值指标,`value` 表示计数或数值累加。 字符串型信息必须作为 dimension: ```json { "name": "parse.completed.count", "value": 1, "dimensions": { "tier": "basic", "status": "succeeded" } } ``` 不允许: ```json { "name": "parse.tier", "value": "basic" } ``` Dimension 只允许低基数字段: | 字段 | 枚举 / 说明 | |------|-------------| | `source` | `http_api` / `sdk` / `cli` / `mcp` / `web` / `app` / `watch` / `background` / `unknown` | | `caller` | `agent` / `user` / `http_client` / `sdk` / `web` / `app` / `system` / `unknown` | | `tier` | `default` / `flash` / `basic` / `standard` / `advanced` / `basic(default)` / `standard(default)` / `advanced(default)` / `unknown` | | `status` | `succeeded` / `failed` / `partial` / `canceled` | | `input_type` | `pdf` / `csv` / `tsv` / `rtf` / `docx` / `pptx` / `xlsx` / `odt` / `ods` / `odp` / `html` / `other` / `unknown` | | `output_format` | `middle_json` / `markdown` / `structured_content` / `other` | | `stage` | `enqueue` / `cache_lookup` / `parse_server_call` / `parsing` / `merge_pages` / `persist` / `index` / `unknown` | | `error_code` | 稳定错误码枚举,不允许 exception message | | `retryable` | `true` / `false` / `unknown` | | `bucket` | 当前 metric 定义的分桶值 | | `server` | `local(flash)` / `local(managed)` / `local(self-hosted)` / `remote(official)` / `remote(custom)` / `none` / `unknown` | | `rule_type` | `exclude` / `parsing` / `unknown` | | `remote` | `true` / `false` / `unknown` | | `command` | 当前 metric 定义的命令枚举 | | `wait_mode` | `sync` / `async` / `unknown` | | `wait_bucket` | `0` / `1_10s` / `11_60s` / `gt_60s` / `unknown` | | `page_range_mode` | `all` / `partial` / `default` / `unknown` | | `config_area` | `watch` / `exclude` / `parsing_rules` / `parse_server` / `telemetry` / `unknown` | | `cleanup_target` | `orphan_docs` / `deleted_files` / `temp` / `mixed` / `unknown` | | `dry_run` | `true` / `false` / `unknown` | `caller` 与 `source` 语义不同: - `source` 表示请求从哪个调用通道进入 doclib,例如 HTTP API、SDK、CLI、MCP、Web、App、watch 或后台任务。 - `caller` 表示发起行为的主体,例如 Agent、用户、HTTP client、SDK、Web、App 或系统后台任务。 Agent 时代的核心产品目标要求 telemetry 能区分 `agent` 与 `user`。因此,CLI 通道必须尽量识别真实调用主体。 调用通道与主体规则: | 使用方式 | `source` | `caller` | |----------|----------|---------------| | 直接调用 HTTP API,且未传 caller 信息 | `http_api` | `http_client` | | Doclib SDK | `sdk` | `sdk` | | `mineru` CLI,由 Agent 进程调用 | `cli` | `agent` | | `mineru` CLI,由用户 shell 调用 | `cli` | `user` | | `mineru` CLI,无法可靠判断父进程 | `cli` | `unknown` | | MCP Server | `mcp` | `agent` | | Web | `web` | `web` | | 桌面 App | `app` | `app` | | watch 自动发现 | `watch` | `system` | | compaction / cleanup / health check 等后台任务 | `background` | `system` | CLI 的 `caller` 可以通过父进程识别。若父进程是 Claude Code、Codex 等已知编程 Agent,则记为 `agent`;若父进程是普通 shell 或终端,则记为 `user`;无法可靠判断时记为 `unknown`。不得通过文件路径、命令文本、query、prompt、文档内容或具体会话内容推断。 `tier` 同时表达请求档位和实际解析档位: - 用户未指定 tier,且尚未解析到实体 tier 时,使用 `default`。 - 用户显式指定实体 tier,或任务已经完成且没有必要保留默认来源时,使用 `flash`、`basic`、`standard` 或 `advanced`。 - 用户未指定 tier,且已经解析到实体 tier,同时仍需要保留来源信息时,使用 `basic(default)`、`standard(default)` 或 `advanced(default)`。 - 默认选择不会解析为 `flash`,因此不定义 `flash(default)`。 - parse-server 相关 metric 也使用同一个 `tier` 维度,不再单独使用 `parse_server_tier`。 `server` 表达解析执行位置和服务形态: - `local(flash)`: 本地 flash 解析,不经过 parse-server。 - `local(managed)`: doclib 管理的本地 parse-server。 - `local(self-hosted)`: 用户自行启动的本地或自部署 parse-server。 - `remote(official)`: 官方 `mineru.net/api`。 - `remote(custom)`: 用户显式配置的其他远端兼容 parse-server。 - `none`: 当前行为没有解析服务,例如 cache hit、查询类操作或本地 parse-server disabled 配置状态。 - `unknown`: 无法可靠判断。 `parse_server.*` metric 不使用 `server=local(flash)`,因为 flash 不经过 parse-server。`parse.*` metric 可以使用 `server=local(flash)` 表示显式 flash 本地解析。 ## 11. Metric 候选全集 Telemetry 记录业务行为,不记录所有 HTTP 请求。状态查询、健康检查、列表读取和普通配置读取默认不计入 telemetry。 本节列出第一版候选全集。后续实现前可以按 P0 范围裁剪,但裁剪后的实现必须仍能回答产品使用规模、核心链路稳定性和主要性能瓶颈。 `parse.*` 与 `parse_server.*` 的语义边界: - `parse.*` 统计 doclib 解析业务生命周期,回答用户是否发起解析、缓存是否命中、任务是否完成、处理了多少文件和页面、最终状态如何。 - `parse_server.*` 统计 doclib 解析业务生命周期中调用 parse-server 的执行片段,回答是否实际使用 parse-server、使用 local 还是 remote、使用什么模式和 tier、该执行片段的请求数、处理量、失败率和耗时。 - 一次 parse 请求可以产生 `parse.*`,但不一定产生 `parse_server.*`。例如 cache hit、显式 `flash` 本地解析、复用已有完成批次时,通常不产生 parse-server 执行指标。 - 一次 parse 请求如果实际调用了 local 或 remote parse-server,则同时产生 `parse.*` 和 `parse_server.*`。两者不能互相替代。 - `parse.duration.bucket.count` 统计 doclib parse 业务生命周期耗时;`parse_server.duration.bucket.count` 只统计 doclib 调用 parse-server 的执行片段耗时。 产品使用统计: | Metric | 说明 | |--------|------| | `server.started.count` | doclib server 启动次数 | | `server.shutdown.count` | doclib server 正常关闭次数 | | `server.recovered_stale_lock.count` | doclib server 启动恢复 stale lock 的次数 | | `cli.command.count` | `mineru` CLI 子命令调用次数 | | `server.command.count` | `mineru server` 生命周期命令调用次数 | | `ingest.requested.count` | 入库请求次数 | | `ingest.completed.count` | 入库成功次数 | | `ingest.duplicate.count` | 入库时发现内容已存在的次数 | | `parse.requested.count` | parse 请求次数 | | `parse.completed.count` | parse 完成次数,按 `status` 区分成功、失败、部分成功和取消 | | `parse.files.count` | parse 处理文件数 | | `parse.processed_page_count` | parse 处理页数;数值统计,不是页面列表 | | `parse.output.requested.count` | parse 请求输出格式次数 | | `parse.output.to_stdout.count` | parse 输出到 STDOUT 的次数 | | `parse.output.to_file.count` | parse 输出到文件的次数 | | `parse.marker.disabled.count` | 用户通过 `--no-marker` 禁用 marker 的次数 | | `parse.wait_mode.count` | parse 同步或异步等待模式使用次数 | | `parse.page_range.requested.count` | parse 请求页码范围模式使用次数 | | `parse.force.count` | 用户通过 `--force` 请求重新解析的次数 | | `parse.created_batch.count` | 本次请求创建的新 parse batch 数 | | `parse.active_reused.count` | 本次请求复用 pending/parsing batch 的次数 | | `parse.waited_batch.count` | 本次请求等待的 parse batch 数 | | `watch.files.discovered.count` | watch 发现文件数 | | `watch.files.parsed.count` | watch 自动解析文件数 | | `rules.matched.count` | rule 命中次数 | | `rules.excluded.count` | exclude rule 排除文件次数 | | `rules.parse_rule_applied.count` | parsing-rule 被应用次数 | | `search.performed.count` | search 次数,不采集 query | | `find.performed.count` | find 文件名搜索次数,不采集 query | | `doclib.content.requested.count` | 请求读取或渲染文档内容的次数 | | `doclib.content.rendered.count` | 内容读取时成功渲染输出格式的次数 | | `doclib.content.truncated.count` | 内容输出因 token/page budget 被截断的次数 | | `doclib.content.continuation_requested.count` | 用户或 Agent 请求 continuation 的次数 | | `doclib.docs.added.count` | doclib 新增文档数 | | `search.index.updated.count` | 搜索索引更新次数 | | `search.index.source_tier.count` | 搜索索引来源 tier 计数 | | `parse.cache.hit.count` | parse cache 命中次数 | | `parse.cache.miss.count` | parse cache 未命中次数 | | `remote.requested.count` | 用户显式请求 remote 的次数 | | `remote.allowed.count` | remote 请求被隐私策略允许的次数 | | `config.watch.added.count` | watch 目录添加次数 | | `config.watch.removed.count` | watch 目录移除次数 | | `config.rule.added.count` | exclude 或 parsing rule 添加次数 | | `config.rule.removed.count` | exclude 或 parsing rule 移除次数 | | `config.parse_server.changed.count` | parse-server 配置变更次数 | | `parse_server.configured.count` | doclib server 启动或配置变化时记录当前 parse-server 配置 | | `parse_server.local.started.count` | managed 本地 parse-server 被 doclib 拉起的次数 | | `parse_server.local.restarted.count` | managed 本地 parse-server 被重启的次数 | | `parse_server.requested.count` | doclib 调用 parse-server 的请求次数 | | `parse_server.completed.count` | doclib 调用 parse-server 成功完成次数 | | `parse_server.files.count` | parse-server 处理文件数 | | `parse_server.processed_page_count` | parse-server 处理页数;数值统计,不是页面列表 | | `parse.compaction.completed.count` | parse 批次 compaction 成功次数 | | `cleanup.deleted_files.count` | cleanup 删除文件数 | | `cleanup.orphan_records.count` | cleanup 清理孤儿记录数 | 质量与稳定性: | Metric | 说明 | |--------|------| | `ingest.failed.count` | 入库失败次数 | | `parse.failed.count` | parse 失败次数 | | `parse.partial.count` | parse 部分成功次数 | | `parse.retry.count` | parse 重试次数 | | `parse.timeout.count` | parse 超时次数 | | `remote.not_allowed.count` | remote 因隐私策略未显式允许而被拒绝的次数 | | `remote.fallback_to_local.count` | remote 失败后 fallback 到 local 的次数 | | `parse_server.unavailable.count` | doclib 调用 parse-server 不可用次数 | | `parse_server.failed.count` | doclib 调用 parse-server 失败次数 | | `parse_server.local.start_failed.count` | managed 本地 parse-server 启动失败次数 | | `parse.fallback.count` | 发生 fallback 的次数 | | `parse.invalidate.count` | 用户或系统触发 invalidate 的次数 | | `watch.error.count` | watch 主链路错误次数 | | `parse.wait.timeout.count` | parse 同步等待超时次数 | | `search.index.failed.count` | 搜索索引更新失败次数 | | `doclib.content.render.failed.count` | 内容读取时渲染输出格式失败次数 | | `parse.compaction.failed.count` | parse 批次 compaction 失败次数 | | `cleanup.failed.count` | cleanup 失败次数 | 性能与规模分布: | Metric | 说明 | |--------|------| | `ingest.file_size.bucket.count` | 入库文件大小分布 | | `parse.duration.bucket.count` | parse 耗时分布 | | `parse.queue.wait.duration.bucket.count` | parse 请求排队等待耗时分布 | | `parse.page_count.bucket.count` | 单文件页数分布 | | `parse.file_size.bucket.count` | 文件大小分布 | | `parse_server.duration.bucket.count` | doclib 调用 parse-server 的耗时分布 | | `doclib.content.render.duration.bucket.count` | 内容读取时渲染输出格式耗时分布 | | `search.result_count.bucket.count` | search 返回结果数量分布,不采集 query 或结果内容 | | `find.result_count.bucket.count` | find 返回结果数量分布,不采集 query 或结果内容 | | `cleanup.requested.count` | cleanup 用户请求次数 | Parse-server 相关 metric 必须带以下维度,便于区分用户是否使用 parse-server、使用哪类解析服务、以及对应 tier: ```text server: local(managed) | local(self-hosted) | remote(official) | remote(custom) | unknown tier: basic | standard | advanced | basic(default) | standard(default) | advanced(default) | unknown status: succeeded | failed | partial | canceled ``` Parse-server 配置口径: - `parse_server.configured.count`: 记录 doclib server 启动或 parse-server 配置变化时的配置状态。 - 本地 disabled 时,使用 `server=none`、`tier=unknown`。 - 本地 managed 时,使用 `server=local(managed)`、`tier=`。 - 本地 self-hosted 时,使用 `server=local(self-hosted)`、`tier` 来自能力发现结果;发现失败时为 `unknown`。 - 官方远端使用 `server=remote(official)`。 - 用户显式配置的其他远端兼容 parse-server 使用 `server=remote(custom)`。 - 不记录 remote URL。 本地 parse-server 的解析数量和性能通过以下查询口径获得: - 数量: `parse_server.requested.count`、`parse_server.completed.count`、`parse_server.files.count`、`parse_server.processed_page_count`,且 `server=local(managed)` 或 `local(self-hosted)`。 - 性能: `parse_server.duration.bucket.count`,且 `server=local(managed)` 或 `local(self-hosted)`。 - 模式: 由 `server=local(managed)` 或 `local(self-hosted)` 区分。 - tier: `tier=basic`、`standard`、`advanced`、`basic(default)`、`standard(default)` 或 `advanced(default)`。 Telemetry 自身: | Metric | 说明 | |--------|------| | `telemetry.enabled.count` | 用户开启 telemetry 次数 | | `telemetry.disabled.count` | 用户关闭 telemetry 次数 | | `telemetry.flush.succeeded.count` | telemetry 上报成功次数 | | `telemetry.flush.failed.count` | telemetry 上报失败次数 | ## 12. P0 裁剪原则 P0 不一定实现所有候选 metric。裁剪时必须保留能回答以下问题的指标: 1. 使用规模: WAI、DAU、parse 次数、文件数、页数。 2. Agent 入口: `caller=agent` 与 `caller=user` 的使用差异。 3. 调用通道: HTTP API、SDK、CLI、MCP、Web、App、watch 和后台任务的来源分布。 4. 解析服务: 是否使用 parse-server,使用 `local(managed)`、`local(self-hosted)`、`remote(official)` 还是 `remote(custom)`,以及具体解析 tier。 5. 主链路质量: parse、ingest、render、search index 的成功和失败。 6. 关键性能: parse 耗时、parse-server 耗时、queue wait、render 耗时的 bucket。 7. 隐私边界: remote 是否被请求、允许、拒绝,以及 remote 失败后是否 fallback 到 local。 可优先裁剪或延后实现的指标: - cleanup 和 compaction 维护类指标。 - server shutdown 等生命周期补充指标。 - continuation / truncation 等尚未进入第一版输出能力的指标。 - 过细且暂时没有 dashboard 消费场景的 bucket 指标。 ## 13. Bucket 定义 耗时分桶: ```text lt_1s | 1_5s | 5_30s | 30_120s | 2_10m | gt_10m ``` 页数分桶: ```text 1 | 2_5 | 6_20 | 21_100 | 101_500 | gt_500 ``` 文件大小分桶: ```text lt_1mb | 1_10mb | 10_50mb | 50_200mb | gt_200mb ``` search 结果数分桶: ```text 0 | 1_5 | 6_20 | 21_100 | gt_100 ``` find 结果数分桶: ```text 0 | 1_5 | 6_20 | 21_100 | gt_100 ``` ## 14. 候选 Metric 口径 ### 14.1 Ingest Ingest 指文件进入 doclib 后计算 SHA256、提取基础 metadata、写入 `files` / `docs` / 文件名索引的阶段。 推荐维度: ```text source: http_api | sdk | cli | mcp | web | app | watch | background | unknown caller: agent | user | http_client | sdk | web | app | system | unknown input_type status error_code ``` `ingest.duplicate.count` 表示同一 `sha256` 已存在,只新增或更新文件路径实例,不代表失败。 ### 14.2 Search Index Search index 指文件名索引和内容全文索引更新,不记录索引文本、snippet 或命中文档。 推荐维度: ```text source: http_api | sdk | cli | mcp | web | app | watch | background | unknown caller: agent | user | http_client | sdk | web | app | system | unknown tier status error_code ``` `search.index.source_tier.count` 用于统计当前索引文本来自哪个 tier。搜索结果可以来自 `flash`,但用户主动阅读时不能默认停留在 `flash`。 ### 14.3 Render / Content Render 指 `GET /docs/{sha256}/content` 或等价 SDK/CLI 读取动作中,从 Middle JSON 转换为 Markdown、structured_content、HTML 等派生格式。 推荐维度: ```text source caller output_format tier status error_code bucket ``` `doclib.content.requested.count` 记录内容读取请求。`doclib.content.rendered.count` 记录实际完成渲染。请求 `middle_json` 时可以只记录 requested,不一定记录 rendered。 ### 14.4 Queue / Batch Queue 和 batch 指 doclib parse 请求在 DB 任务队列中的创建、复用和等待行为。 推荐维度: ```text source caller tier status ``` `parse.created_batch.count` 表示请求创建了新的 parse batch。`parse.active_reused.count` 表示请求复用了已经存在的 pending/parsing batch。`parse.waited_batch.count` 表示当前请求需要等待的 batch 数;一次请求可以等待多个 batch。 `parse.queue.wait.duration.bucket.count` 只统计从请求进入等待到相关 batch 完成或失败的等待耗时,不包含实际 parse-server 执行耗时。 ### 14.5 Remote Boundary Remote metrics 只记录用户是否显式允许远端和是否发生 local fallback,不记录 remote URL、API Key 或账号信息。 推荐维度: ```text source caller tier status error_code ``` `remote.requested.count` 表示当前请求显式带有 remote 语义。`remote.allowed.count` 表示隐私策略允许该请求使用 remote。`remote.not_allowed.count` 表示系统知道 remote 可能解决问题,但用户没有显式允许。`remote.fallback_to_local.count` 表示 remote 失败后按隐私边界 fallback 到 local。 ### 14.6 Rules Rules metrics 只记录规则是否命中,不记录 rule name、pattern、路径或文件名。 推荐维度: ```text rule_type: exclude | parsing | unknown tier remote: true | false | unknown ``` `rules.excluded.count` 表示 exclude rule 阻止文件进入后续链路。`rules.parse_rule_applied.count` 表示 parsing-rule 决定了 tier、page_range 或 remote 语义。 ### 14.7 Server Lifecycle Server lifecycle metrics 记录 doclib server 和 managed local parse-server 的生命周期。 推荐维度: ```text status error_code server tier ``` `server.recovered_stale_lock.count` 用于统计启动恢复能力。`parse_server.local.start_failed.count` 和 `parse_server.local.restarted.count` 用于观察 managed local parse-server 稳定性。 ### 14.8 Compaction / Cleanup Compaction 和 cleanup 属于维护链路,用于观察 JSON 批次增长、孤儿记录和文件清理效果。 推荐维度: ```text tier status error_code ``` 这些指标不记录 sha256、路径或文件名。 ### 14.9 CLI 行为 CLI 行为 metrics 只记录用户或 Agent 使用了哪些 `mineru` 子命令和关键模式,不记录命令参数中的路径、query、pattern、URL、API Key 或输出内容。 推荐维度: ```text source: cli caller command status ``` `cli.command.count` 的 `command` 取值: ```text parse | invalidate | search | find | show | cleanup | server | config | telemetry | unknown ``` `server.command.count` 的 `command` 取值: ```text start | stop | restart | status | unknown ``` `GET /server/status` 不计入 `server.command.count`,避免健康检查和轮询污染。只有用户或 Agent 通过 CLI 主动执行 `mineru server status` 时才记录。 Parse CLI 行为推荐维度: ```text source: cli caller tier server output_format wait_mode wait_bucket page_range_mode status ``` 口径: - `parse.output.requested.count`: 记录 `--format` 请求的输出格式。 - `parse.output.to_stdout.count`: 输出到 STDOUT。 - `parse.output.to_file.count`: 指定 `--output` 写入文件;不记录输出路径。 - `parse.marker.disabled.count`: 用户传入 `--no-marker`。 - `parse.wait_mode.count`: `--no-wait` 记为 `wait_mode=async`,否则记为 `sync`。 - `parse.wait.timeout.count`: 同步等待超时。 - `parse.page_range.requested.count`: 未传页码为 `default`,显式 `all` 为 `all`,其他页码范围为 `partial`;不记录具体页码字符串。 - `parse.force.count`: 用户传入 `--force`。 配置类 CLI 行为推荐维度: ```text source: cli caller config_area server tier remote status ``` 口径: - `config.watch.added.count` / `config.watch.removed.count`: 不记录 watch path、label 或 removable 设备标识。 - `config.rule.added.count` / `config.rule.removed.count`: 不记录 rule name、pattern 或 page_range。 - `config.parse_server.changed.count`: 不记录 URL 或 API Key,只通过 `server` 区分 `local(managed)`、`local(self-hosted)`、`remote(official)`、`remote(custom)`、`none`、`unknown`。 Cleanup CLI 行为推荐维度: ```text source: cli caller cleanup_target dry_run status ``` `cleanup_target` 取值: ```text orphan_docs | deleted_files | temp | mixed | unknown ``` ## 15. Doclib API 感知范围 Telemetry 不记录所有 doclib HTTP API 调用,只记录能够代表产品使用规模或核心行为的 API。 对用户行为类 API,调用方应尽量向 doclib 传递 `caller`。这使 telemetry 可以区分 Agent 调用 `mineru` 命令和用户直接调用 `mineru` 命令。不能可靠识别时使用 `unknown`,不得通过敏感内容推断。 直接调用 HTTP API 且未传 caller 信息时,`source=http_api`、`caller=http_client`。SDK、CLI、MCP、Web 和桌面 App 应由对应入口向 doclib 传递 `source` 与 `caller`。 应记录的 API: | API | Metric | 说明 | |-----|--------|------| | `POST /parses` | `parse.requested.count` 等 parse 指标;使用 parse-server 时记录 `parse_server.*` 指标 | 记录请求、cache hit/miss、完成/失败、文件数、页数、tier、耗时、大小分布和 parse-server 使用情况 | | `POST /invalidate` | `parse.invalidate.count` | 记录 invalidate 行为,不记录路径或 sha256 | | `GET /search` | `search.performed.count`、`search.result_count.bucket.count` | 不记录 query、命中文档或返回内容 | | `GET /find` | `find.performed.count`、`find.result_count.bucket.count` | 与 search 分开统计,不记录 query、命中文档或返回内容 | | `GET /docs/{sha256}/content` | `doclib.content.requested.count` | 记录内容读取或渲染次数,不记录 sha256 或内容 | 不应记录为 telemetry 的 API: | API | 原因 | |-----|------| | `GET /parses` | 状态查询和轮询噪音较大 | | `GET /parses/{id}` | 状态查询,不代表核心产品使用量 | | `GET /docs` | 列表浏览噪音较大 | | `GET /docs/{sha256}` | 元数据读取,P0 不作为核心指标 | | `GET /files/by-path` | 工具性查询 | | `GET /config` / `POST /config` | 普通配置读取和修改不计入,telemetry 开关变化除外 | | watch / rules 配置 API | 只记录后台 watch 实际发现和解析行为 | | cleanup API | 管理维护行为 | | `GET /server/status` | 健康检查和轮询会污染活跃度 | | `POST /shutdown` | 管理行为 | ## 16. 本地存储 Telemetry 用户状态、内部状态和聚合数据都存储在 doclib DB 中,不使用启动前文件配置。 Telemetry endpoint 不支持用户配置,应由代码内置。开发和测试环境如需覆盖 endpoint,应使用测试构建或专门的开发开关,不进入用户配置体系。 建议状态结构可以继续用 KV 表表达: ```toml [telemetry] consent_state = "enabled" installation_id = "inst_01HX..." last_flush_at = "2026-06-10T12:00:00Z" ``` 状态语义: - `consent_state`: 用户 telemetry 状态,取值为 `unset` / `enabled` / `disabled`。 - `installation_id`: 内部匿名安装实例 ID,用户不可配置,也不需要默认展示完整值。 - `last_flush_at`: 内部运行状态,表示最近一次成功 flush 时间,不是用户配置。 `consent_state` 语义: | 值 | 含义 | |----|------| | `unset` | 用户尚未完成首次选择,doclib server 应展示或触发首次选择流程 | | `enabled` | 用户允许记录并上报 telemetry | | `disabled` | 用户明确关闭 telemetry | 如果 `consent_state=disabled`,doclib server 不应写入新的 telemetry 聚合数据。 Telemetry 聚合数据应使用独立表保存,至少包含: | 字段 | 说明 | |------|------| | `id` | 本地聚合记录 ID | | `period_start` | 聚合窗口开始时间 | | `period_end` | 聚合窗口结束时间 | | `metric_name` | metric 名称 | | `metric_value` | 非负数值 | | `dimensions` | metric-level dimensions JSON | | `dimensions_hash` | dimensions JSON 的稳定 hash,用于聚合 upsert | | `created_at` | 创建时间 | | `updated_at` | 最近更新时间 | 聚合唯一键: ```text period_start + period_end + metric_name + dimensions_hash ``` 同一唯一键重复写入时,累加 `metric_value`。聚合 key 不包含 context;flush 时读取当前 context 并组装 telemetry batch。服务端确认成功接收后,删除对应聚合记录。网络失败或 5xx 时保留记录以便后续重试。 Flush 并发与幂等采用 P0 简单方案: 1. doclib 同一时间只允许一个 flush 任务执行。 2. flush 开始时获取本地 flush lock;已有 lock 且未过期时,本次 flush 直接跳过。 3. flush 从 DB 读取一批未上报聚合记录,生成 `batch_id` 并 POST 到 telemetry endpoint。 4. 服务端确认成功接收后,本地删除这些聚合记录。 5. 如果 POST 成功但本地删除前 doclib 崩溃,后续可能重复上报同一批 metrics。 6. P0 接受少量重复。服务端应基于 `batch_id` 在短窗口内做幂等去重;超过窗口的重复数据由 cleaned metrics 层处理。 Flush lock 可以放在 telemetry 内部状态中,例如: ```toml [telemetry] flush_locked_at = "2026-06-10T12:00:00Z" ``` ## 17. 服务端防污染 匿名 telemetry endpoint 无法完全避免伪造数据。服务端必须把本地 telemetry 视为半可信数据,只用于趋势分析。 服务端必须实现: - schema version 校验 - metric name 白名单 - context key/value 白名单 - dimension key/value 白名单 - `value` 非负数校验 - batch 大小限制 - period 时间范围限制 - `installation_id` 限流 - IP 限流 - 异常数据 quarantine - raw metrics 与 cleaned metrics 分层 Dashboard 默认展示 cleaned metrics。mineru.net API 服务端日志属于 trusted usage,本地 anonymous telemetry 属于 anonymous product analytics,两类数据必须分开标记和分析。 ## 18. P0 实现边界 P0 需要完成: - doclib server telemetry DB 配置读取与首次选择状态管理。 - 匿名 `installation_id` 生成并持久化到 doclib DB。 - telemetry metrics 聚合存储到 doclib DB。 - 小时级 period 聚合与 metric upsert。 - flush 成功后删除 DB 中已确认上报的 telemetry 聚合数据。 - `mineru` CLI、Doclib SDK、MCP、Web、桌面 App 和直接 HTTP API 到 doclib server 的 `source` / `caller` 透传。 - P0 metric 白名单与 dimension 白名单。 - 周期 flush 与失败重试。 - `mineru telemetry status|enable|disable|preview|flush`。 - 服务端 endpoint schema。 P0 不需要完成: - 每事件实时上报。 - 详细硬件画像。 - 账号级归因。 - GPU 型号、显存、驱动和 CUDA 版本采集。 - exception message / traceback 上报。 - telemetry 结果查询 API。