f05dd1cf74
Co-authored-by: guke <guke@autohome.com.cn> Reviewed-on: #187
88 lines
4.9 KiB
Markdown
88 lines
4.9 KiB
Markdown
# POST /api/v1/applog/batch — 批量上报客户端运行日志
|
||
|
||
> 所属:客户端日志组(前缀 `/api/v1/applog`) | 鉴权:无(不强制登录,`user_id` 可选带上) | [← 返回 API 索引](../README.md)
|
||
|
||
批量接收客户端 App 运行日志(自动化步骤 / 网络 / 崩溃 / 调试等),**逐条**封装成单行 JSON 写入独立滚动文件 `logs/app-client.log`(与服务日志 `app-server.log` 分开),由阿里云 Logtail 采进**独立 SLS logstore**,**不落库**。服务端补 `client_ip`(X-Forwarded-For)与 `time`(接收时间,SLS 主时间)。`trace_id` 提到输出行顶层,便于在 SLS 里跨「客户端 / 服务端」两个 logstore 按 trace 拼出端到端链路。**fire-and-forget**:写失败也返回 2xx,不 500。
|
||
|
||
## 入参
|
||
|
||
批级公共字段发一次;`logs` 里每条只带日志本身。**每条只有 `client_ts/level/trace_id/tag/msg` 会提到输出顶层,其余自定义字段一律并入输出的 `data`**(防 SLS 索引列爆炸)。
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---|---|
|
||
| `device_id` | string | ✅(≤64) | 设备 ID(限流 / 分组键) |
|
||
| `user_id` | int \| null | ❌ | 登录用户 ID(未登录可空) |
|
||
| `app_ver` | string \| null | ❌(≤32) | App 版本 |
|
||
| `platform` | string \| null | ❌(≤16) | 平台(`android` / `ios` / `harmony`) |
|
||
| `sent_at` | int \| null | ❌ | 批次发送时间(epoch ms) |
|
||
| `logs` | list[object] | ✅(1-500 条) | 日志数组(每条为对象,内部字段**不强类型**) |
|
||
| `logs[].client_ts` | int | ❌ | 端侧日志时间(epoch ms) |
|
||
| `logs[].level` | string | ❌ | 级别(服务端归一化大写,截断 ≤16) |
|
||
| `logs[].trace_id` | string \| null | ❌ | 关联服务端比价链路的 trace(有服务端交互的日志带上,截断 ≤256) |
|
||
| `logs[].tag` | string \| null | ❌ | 模块 / 分类(截断 ≤128) |
|
||
| `logs[].msg` | string | ❌ | 消息主体(超 8KB **字节**截断,加 `msg_truncated` 标记) |
|
||
| `logs[].*` | any | ❌ | 其余任意自定义字段 → 一律并入输出的 `data`(客户端自带的 `data` 对象会被合并进来) |
|
||
|
||
Mock 入参:
|
||
```json
|
||
{
|
||
"device_id": "android_abc123def456",
|
||
"user_id": 42,
|
||
"app_ver": "0.1.5",
|
||
"platform": "android",
|
||
"sent_at": 1719993700000,
|
||
"logs": [
|
||
{
|
||
"client_ts": 1719993600000,
|
||
"level": "info",
|
||
"tag": "automation",
|
||
"msg": "compare flow start",
|
||
"trace_id": "t_ab12cd34"
|
||
},
|
||
{
|
||
"client_ts": 1719993615000,
|
||
"level": "error",
|
||
"tag": "network",
|
||
"msg": "timeout calling /price/step",
|
||
"trace_id": "t_ab12cd34",
|
||
"http_status": 504,
|
||
"retry": 2
|
||
}
|
||
]
|
||
}
|
||
```
|
||
> 上例第二条的 `http_status` / `retry` 不在白名单 → 会被并入落盘行的 `data`:`{"http_status":504,"retry":2}`。
|
||
|
||
## 出参
|
||
|
||
响应 `200`:`AppLogIngestOut`
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `ok` | bool | 固定 `true` |
|
||
| `received` | int | 成功写入文件的条数 |
|
||
| `dropped` | int | 跳过的条数(服务端处理 / 序列化失败;正常为 `0`) |
|
||
|
||
Mock 出参:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"received": 2,
|
||
"dropped": 0
|
||
}
|
||
```
|
||
|
||
## 错误码
|
||
- `413` 请求体超过上限(默认 2MB;服务端查 `Content-Length`,在 body 校验前拦截。缺该头时由 nginx `client_max_body_size` 兜底)
|
||
- `422` `logs` 为空或超过 500 条 / `device_id` 缺失 / 字段类型不符
|
||
- `429` 触发限流(同 IP 每分钟 > 120 次)
|
||
|
||
## 说明
|
||
- **落盘 → SLS**:逐条写独立文件 `logs/app-client.log`(单行 JSON,`propagate=False` 不污染 `app-server.log`),由 Logtail JSON 模式采进**独立 logstore**;不进数据库。落盘行除白名单字段外,服务端另补 `time`(接收时间)、`source="client"`、`service`、`client_ip` 及批级 `device_id/user_id/app_ver/platform/sent_at`。
|
||
- **`trace_id` 跨层检索**:字段名与服务端日志一致。客户端应给**有服务端交互**的日志带上当初 API 返回的 `trace_id`(如 `/api/v1/price/step` 等透传族返回的 trace),即可在 SLS 里 `trace_id: "xxx"` 一查拼出「客户端视角 + 服务端比价链路」;纯客户端日志不带即可。
|
||
- **白名单 + `data` 兜底**:只有 `client_ts/level/trace_id/tag/msg` 上顶层,其余键并入 `data`,把 SLS 索引列钉死在固定集合,防客户端任意 key 撑爆索引。索引字段另有长度上限(`level`≤16 / `trace_id`≤256 / `tag`≤128 / `msg`≤8KB)。
|
||
- **fire-and-forget**:写文件失败也返回 2xx(避免客户端重试风暴);网络重试可能在 SLS 造成重复条目,可接受。
|
||
- **量级建议**:客户端做等级过滤 / 采样,攒到一定量再批量上报;单批 ≤500 条、body ≤2MB。
|
||
- `client_ts` 是端侧时间(客户端时钟不可信),`time` 由服务端补(可靠时间轴)。
|
||
- `user_id` 不靠 JWT:未登录态也要采日志;带上便于按用户排查。
|