|
| 1 | +--- |
| 2 | +title: |
| 3 | + en: Telemetry backend |
| 4 | + zh-CN: 遥测后端 |
| 5 | +--- |
| 6 | + |
| 7 | +## Scope{lang="en"} |
| 8 | + |
| 9 | +::: en |
| 10 | + |
| 11 | +RitsuLib only handles consent, local queueing, routing, and payload assembly. Each applicant owns its fixed endpoint. A backend may be a Cloudflare Worker, FastAPI service, ASP.NET service, PostHog proxy, S3 writer, or any other service that accepts the same batch contract. |
| 12 | + |
| 13 | +The public contract lives in: |
| 14 | + |
| 15 | +- `schemas/telemetry/v1/openapi.yaml` |
| 16 | +- `schemas/telemetry/v1/telemetry-batch.schema.json` |
| 17 | +- `schemas/telemetry/v1/telemetry-event.schema.json` |
| 18 | + |
| 19 | +Use the OpenAPI file with tools such as OpenAPI Generator, Kiota, NSwag, or Swagger Codegen. Use the JSON Schema files for runtime validation in workers, FastAPI, ASP.NET, Node, Rust, Go, or Java. |
| 20 | + |
| 21 | +::: |
| 22 | + |
| 23 | +## 范围{lang="zh-CN"} |
| 24 | + |
| 25 | +::: zh-CN |
| 26 | + |
| 27 | +RitsuLib 只负责授权、本地队列、路由和 payload 组装。每个申请方拥有自己的固定端点。后端可以是 Cloudflare Worker、FastAPI、ASP.NET、PostHog 转发器、S3 写入器,或任何接受同一 batch contract 的服务。 |
| 28 | + |
| 29 | +公共契约文件位于: |
| 30 | + |
| 31 | +- `schemas/telemetry/v1/openapi.yaml` |
| 32 | +- `schemas/telemetry/v1/telemetry-batch.schema.json` |
| 33 | +- `schemas/telemetry/v1/telemetry-event.schema.json` |
| 34 | + |
| 35 | +`openapi.yaml` 可用于 OpenAPI Generator、Kiota、NSwag、Swagger Codegen 等工具生成代码。JSON Schema 可用于 Worker、FastAPI、ASP.NET、Node、Rust、Go、Java 等运行时校验。 |
| 36 | + |
| 37 | +::: |
| 38 | + |
| 39 | +## Endpoint{lang="en"} |
| 40 | + |
| 41 | +::: en |
| 42 | + |
| 43 | +The recommended endpoint is: |
| 44 | + |
| 45 | +```text |
| 46 | +POST /v1/ingest |
| 47 | +Content-Type: application/json |
| 48 | +``` |
| 49 | + |
| 50 | +Successful responses should return `200` or `202`: |
| 51 | + |
| 52 | +```json |
| 53 | +{ |
| 54 | + "ok": true, |
| 55 | + "accepted": 2, |
| 56 | + "rejected": 0, |
| 57 | + "request_id": "optional-log-correlation-id" |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +Error responses should use a stable machine-readable `error` string: |
| 62 | + |
| 63 | +```json |
| 64 | +{ |
| 65 | + "error": "invalid_schema", |
| 66 | + "message": "schema must be ritsulib.telemetry.batch.v1" |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +::: |
| 71 | + |
| 72 | +## 端点{lang="zh-CN"} |
| 73 | + |
| 74 | +::: zh-CN |
| 75 | + |
| 76 | +推荐端点: |
| 77 | + |
| 78 | +```text |
| 79 | +POST /v1/ingest |
| 80 | +Content-Type: application/json |
| 81 | +``` |
| 82 | + |
| 83 | +成功响应建议返回 `200` 或 `202`: |
| 84 | + |
| 85 | +```json |
| 86 | +{ |
| 87 | + "ok": true, |
| 88 | + "accepted": 2, |
| 89 | + "rejected": 0, |
| 90 | + "request_id": "optional-log-correlation-id" |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +错误响应应使用稳定的机器可读 `error` 字符串: |
| 95 | + |
| 96 | +```json |
| 97 | +{ |
| 98 | + "error": "invalid_schema", |
| 99 | + "message": "schema must be ritsulib.telemetry.batch.v1" |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +::: |
| 104 | + |
| 105 | +## Payload{lang="en"} |
| 106 | + |
| 107 | +::: en |
| 108 | + |
| 109 | +A batch has a batch schema id, one applicant id, and one or more events: |
| 110 | + |
| 111 | +```json |
| 112 | +{ |
| 113 | + "schema": "ritsulib.telemetry.batch.v1", |
| 114 | + "applicant_id": "author.some-mod", |
| 115 | + "events": [ |
| 116 | + { |
| 117 | + "schema": "ritsulib.telemetry.v1", |
| 118 | + "applicantId": "author.some-mod", |
| 119 | + "eventName": "exception", |
| 120 | + "requestId": "diagnostics", |
| 121 | + "category": "Diagnostics", |
| 122 | + "timestampUtc": "2026-05-19T00:00:00Z", |
| 123 | + "properties": { |
| 124 | + "anonymous_install_id": "stable-anonymous-id", |
| 125 | + "session_id": "process-session-id", |
| 126 | + "ritsulib_version": "0.0.0", |
| 127 | + "applicant_id": "author.some-mod", |
| 128 | + "owner_mod_id": "author.some-mod", |
| 129 | + "payload_kind": "exception", |
| 130 | + "exception_type": "System.Exception" |
| 131 | + }, |
| 132 | + "payload": { |
| 133 | + "applicant_payload": { |
| 134 | + "exception": { |
| 135 | + "type": "System.Exception", |
| 136 | + "message": "example", |
| 137 | + "stack_trace": "..." |
| 138 | + } |
| 139 | + } |
| 140 | + } |
| 141 | + } |
| 142 | + ] |
| 143 | +} |
| 144 | +``` |
| 145 | + |
| 146 | +Backends should index `properties` first. Full `payload` should be stored as JSON/blob. Promote only the fields needed for dashboards or search. |
| 147 | + |
| 148 | +::: |
| 149 | + |
| 150 | +## 数据{lang="zh-CN"} |
| 151 | + |
| 152 | +::: zh-CN |
| 153 | + |
| 154 | +一个 batch 包含 batch schema id、一个申请方 ID,以及一个或多个事件: |
| 155 | + |
| 156 | +```json |
| 157 | +{ |
| 158 | + "schema": "ritsulib.telemetry.batch.v1", |
| 159 | + "applicant_id": "author.some-mod", |
| 160 | + "events": [ |
| 161 | + { |
| 162 | + "schema": "ritsulib.telemetry.v1", |
| 163 | + "applicantId": "author.some-mod", |
| 164 | + "eventName": "exception", |
| 165 | + "requestId": "diagnostics", |
| 166 | + "category": "Diagnostics", |
| 167 | + "timestampUtc": "2026-05-19T00:00:00Z", |
| 168 | + "properties": { |
| 169 | + "anonymous_install_id": "stable-anonymous-id", |
| 170 | + "session_id": "process-session-id", |
| 171 | + "ritsulib_version": "0.0.0", |
| 172 | + "applicant_id": "author.some-mod", |
| 173 | + "owner_mod_id": "author.some-mod", |
| 174 | + "payload_kind": "exception", |
| 175 | + "exception_type": "System.Exception" |
| 176 | + }, |
| 177 | + "payload": { |
| 178 | + "applicant_payload": { |
| 179 | + "exception": { |
| 180 | + "type": "System.Exception", |
| 181 | + "message": "example", |
| 182 | + "stack_trace": "..." |
| 183 | + } |
| 184 | + } |
| 185 | + } |
| 186 | + } |
| 187 | + ] |
| 188 | +} |
| 189 | +``` |
| 190 | + |
| 191 | +后端应优先索引 `properties`。完整 `payload` 建议按 JSON/blob 存储,只把看板或搜索所需字段提升为索引字段。 |
| 192 | + |
| 193 | +::: |
| 194 | + |
| 195 | +## Backend Checklist{lang="en"} |
| 196 | + |
| 197 | +::: en |
| 198 | + |
| 199 | +- Validate `schema` and `event.schema`. |
| 200 | +- Validate `applicant_id` and every `event.applicantId` against the endpoint owner. |
| 201 | +- Enforce request body size and event count limits. |
| 202 | +- Reject or quarantine unknown schema versions instead of silently reshaping them. |
| 203 | +- Store raw events before forwarding to analytics if durability matters. |
| 204 | +- Use server-side secrets for analytics keys. Do not embed PostHog or warehouse write keys in mods. |
| 205 | +- Keep an append-only raw table or object store for later reprocessing. |
| 206 | +- Promote query-critical fields from `properties` and selected payload paths into indexed columns. |
| 207 | + |
| 208 | +::: |
| 209 | + |
| 210 | +## 后端检查项{lang="zh-CN"} |
| 211 | + |
| 212 | +::: zh-CN |
| 213 | + |
| 214 | +- 校验 `schema` 和 `event.schema`。 |
| 215 | +- 校验 `applicant_id` 与每个 `event.applicantId` 是否属于该端点所有者。 |
| 216 | +- 限制请求体大小和事件数量。 |
| 217 | +- 对未知 schema 版本应拒绝或隔离,不要静默改写。 |
| 218 | +- 如果需要可靠性,先持久化 raw event,再转发到分析平台。 |
| 219 | +- 分析平台 key 必须保存在服务端,不要写进 mod。 |
| 220 | +- 保留 append-only raw table 或对象存储,方便以后重放处理。 |
| 221 | +- 将 `properties` 和少量重要 payload 路径提升为索引字段。 |
| 222 | + |
| 223 | +::: |
0 commit comments