第4章 日志上报触发机制:主动上报、被动上报、采样策略、重试与幂等处理
4.1 适用场景
本章适用于Crash治理团队在客户端SDK层设计日志上报链路时的决策与配置。当应用发生Crash或采集到异常事件后,需要确定何时、以何种方式将日志传输到服务端。主要场景包括:
- 主动上报:用户触发(如点击“反馈”)、关键业务节点完成时主动发起日志上传。
- 被动上报:应用启动时检测到上次Crash日志、系统定时唤醒、网络状态恢复后自动补传。
- 采样策略:海量用户场景下按比例、按用户ID、按设备ID等维度控制上报量,避免服务端压力。
- 重试与幂等处理:网络不稳定或服务端返回错误时,保证日志最终成功上报且不产生重复记录。
4.2 前置条件
| 条件 | 说明 |
|---|---|
| 日志已持久化 | Crash发生时日志已写入本地文件(参见第3章)。 |
| 网络可用性检测 | SDK具备检测当前网络类型(WiFi/蜂窝/无网)的能力。 |
| 服务端API端点 | 已知日志上报的URL、鉴权方式(/AppKey)。 |
| 设备标识与时间戳 | 用于去重与幂等检查的UniqueID和事件发生时间。 |
| 采样开关配置 | 远端配置中心下发的采样比例、白名单设备列表。 |
4.3 操作步骤
- 触发决策:Crash事件入库后,SDK根据事件类型(崩溃/ANR/自定义异常)判断是否立即触发主动上报。
- 采样过滤:若当前事件通过采样黑/白名单检查且随机数在采样比例内,才允许进入上报队列;否则丢弃。
- 队列入队:通过条件的事件进入本地持久化队列(SQLite或文件序列),标记状态为
pending。 - 网络检测与被动触发:若当前网络可用(WiFi优先),立即启动上报;否则等待网络恢复后自动触发被动上报。
- 发起HTTP请求:使用POST方法将日志JSON包发送至服务端,请求头携带
X-Request-Id(唯一幂等键)。 - 处理响应:服务端返回200则标记状态为
success;若返回409(重复)则也可视为成功;其他错误码(5xx/4xx)进入重试逻辑。 - 重试策略:采用指数退避(初始1秒,最大64秒,最多3次)。每次重试前检查本地队列状态以避免重复。
- 幂等清理:上报成功后根据
X-Request-Id清除本地待上传文件;若10分钟后仍没有收到服务端确认,允许重新上报但携带相同幂等ID。
4.4 输入输出物
| 类型 | 名称 | 说明 |
|---|---|---|
| 输入 | 本地Crash日志文件 | 已序列化的事件数据(JSON/Protobuf)。 |
| 输入 | 远端配置 | 采样比例(default=0.1)、白名单、重试参数。 |
| 输出 | HTTP请求包 | 携带幂等ID的POST请求体。 |
| 输出 | 本地状态文件 | 记录每条日志的上报状态(pending/success/failed)。 |
| 输出 | 监控指标 | 上报成功率、重试率、平均首报延迟。 |
4.5 责任边界
- 客户端SDK组:实现上报逻辑、采样算法、重试机制、幂等去重;不得遗漏用户隐私数据。
- 服务端组:提供幂等校验接口(基于X-Request-Id)、返回准确的HTTP状态码、保证日志最终存储不丢不重。
- QA组:构造网络异常、并发上报、重复请求、采样率边界等场景进行验证。
- 运维组:监控上报流量,配置采样阈值告警。
4.6 记录材料
- 上报触发日志(客户端本地logcat):
[CrashSDK][2025-02-20 10:00:01] Trigger: active, eventId=abc123 [CrashSDK][2025-02-20 10:00:02] Sampling: pass (ratio=0.1, rand=0.05) [CrashSDK][2025-02-20 10:00:03] Queued: status=pending [CrashSDK][2025-02-20 10:00:04] HTTP POST: url=https://api.example.com/v1/crash, X-Request-Id=uuid-xxx [CrashSDK][2025-02-20 10:00:05] Response: 200 OK, removed local - 服务端幂等日志:记录每个X-Request-Id首次到达时间及重复请求被拒绝次数。
4.7 异常处理
| 异常场景 | 处理方式 |
|---|---|
| 网络不可用 | 标记为pending,注册广播监听网络恢复后触发被动上报。 |
| 服务端5xx错误 | 指数退避重试,最多3次;仍失败则标记为failed并报警。 |
| 服务端409冲突 | 视为成功,删除本地待上报文件。 |
| 本地存储写满 | 按FIFO策略删除最早的上报状态文件,保留日志原文。 |
| 采样配置解析失败 | 使用兜底采样率(如0.1),并记录错误到本地日志。 |
4.8 验收标准
- ✅ 主动上报:用户在WiFi下点击反馈,日志在5秒内到达服务端。
- ✅ 被动上报:模拟飞机模式开启→写入Crash→关闭飞机模式,日志在60秒内补传完成。
- ✅ 采样策略:配置采样率0.01,在10000次模拟Crash中,服务端接收量在80~120之间。
- ✅ 重试与幂等:服务端故意返回500两次,第三次返回200,客户端仅发送3次请求且最终成功;同一X-Request-Id被重复发送时服务端返回409,客户端不删除本地文件。
- ✅ 并发安全:同时写入10个Crash日志,上报时无乱序、无重复、状态文件一致。
避坑提示:采样策略必须同时考虑设备ID和随机数双重因子,避免某个特定设备永远不被采样。幂等键建议使用
UUID + 时间戳 + 设备ID的SHA256哈希,确保全局唯一。
重要:不要在上报线程中执行耗时磁盘IO操作,防止影响UI线程。建议使用独立的HandlerThread或协程调度。
最佳实践:采样策略建议支持动态调整。线上可通过远端配置中心下发
sample_rate 和 whitelist_devices,无需发版即可控制流量。重试指数退避的伪代码示例:
fun retryWithExponentialBackoff(request: Request, maxRetries: Int = 3) {
var delay = 1000L
for (i in 1..maxRetries) {
val response = httpClient.execute(request)
if (response.isSuccessful || response.code == 409) return
Thread.sleep(delay)
delay = minOf(delay * 2, 64000L)
}
logError("Max retries exceeded for request ${request.id}")
}