# 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:未登录态也要采日志;带上便于按用户排查。