主流 SDK 日志采集差异:Android、iOS、Web 与后端
做异常访问检测,第一步就是搞清楚数据从哪来。不同端上的 SDK,采集日志的方式、内容、频率甚至格式都差不少。
我最早接手这个项目时,以为不就是调用个日志接口嘛。结果一上生产环境——Android 日志全挤在一起,iOS 莫名其妙的请求丢了,Web 端跨域搞丢一半数据,后端服务直接刷爆磁盘。嗯,今天就把这几个平台的差异掰开聊透。
Android SDK 日志采集特点
Android 的日志采集,说白了就是套在 Java/Kotlin 里的一个上报模块。核心差异在于异步队列和持久化缓存。
- 采集入口:通过拦截 OkHttp 或 UrlConnection 的请求/响应回调,或 AOP 切入 Activity.onCreate 等生命周期。
- 日志结构:包含时间戳、设备 ID、网络类型、请求 URL、响应码、耗时等。
- 上报策略:先写本地 SQLite,凑满 50 条或每隔 5 分钟批量 POST 到后端。
- 坑点:我曾经遇到过,某些国产手机在后台杀掉进程后,本地 SQLite 文件未及时刷新导致日志丢失。后来加了 WAL 模式才解决。
Android SDK 日志典型示例(伪代码)
{
"platform": "android",
"event": "request",
"timestamp": 1692384000123,
"device_id": "A1B2C3D4",
"url": "https://api.example.com/v1/user",
"method": "GET",
"status_code": 200,
"duration_ms": 235,
"network_type": "wifi"
}iOS SDK 日志采集特点
iOS 这边就谨慎多了。隐私政策严,而且 App 退后台后几乎不能做任何网络请求。日志采集必须考虑应用状态限制。
- 采集入口:NSURLProtocol 或 URLSessionDelegate 拦截网络请求。更现代的方式是 swizzle 掉 URLSession 的代理方法。
- 日志缓存:使用 Core Data 或 SQLite。但 iOS 沙盒清理机制可能导致日志文件被系统自动清除——你想想看,手机空间不足时,系统会直接删掉你的缓存目录。
- 上报策略:app 在前台时实时上报,或每次启动时合并上报。background fetch 只能短时间处理。
- 避坑:我遇到过跨域问题?不,iOS 本身不存在,而是某些第三方 SDK 在 iOS 16+ 上请求 body 无法通过 NSURLProtocol 获取——导致日志里 request body 字段永远为空。后来改用无侵入的 block 回调才搞定。
iOS 日志格式如:
{"platform":"ios","event":"request","url":"https://...","body":""}——注意 body 可能为空。Web SDK 日志采集特点
Web 端是最灵活的,也是最“脏”的。跨域、广告拦截器、JS 错误、页面关闭——哪个都能把日志弄丢。
- 采集入口:重写 XMLHttpRequest.open/send、覆盖 fetch 方法、监听 Performance API、监听 window.onerror。
- 日志格式:通常直接用 JSON,包含 User-Agent、referrer、页面 URL、屏幕分辨率、时间戳。
- 上报方式:常采用 Navigator.sendBeacon() 做页面关闭时的最后一口上报,防止丢失。
- 重点:sendBeacon 虽然可靠,但 payload 有大小限制(64KB)。我曾见过一个业务方日志里夹杂了全量 localStorage,直接超出了限制——日志被静默丢弃。
Web SDK 日志一定要考虑广告拦截器。许多拦截器会阻断含有 “analytics” “log” 等关键词的请求。我建议把上报端点伪装成
/collect 或 /pixel.gif。后端服务 SDK 日志采集差异
后端服务的日志采集和前端完全不同。你的服务跑在可控环境里,不需要担心手机权限或浏览器兼容性。但面临的是高并发和结构化的挑战。
- 采集入口:在网关层(如 Nginx、Envoy)记录请求日志,或在微服务框架(如 Spring Boot、Gin)里用拦截器记录。
- 日志特征:每条日志会包含 traceId、spanId、上游 IP、耗时、请求体大小等。经常直接输出为 JSON 行格式(如
{"level":"INFO","service":"user","latency":45})。 - 存储方式:通常是日志文件 + 外部的日志收集系统(比如 ELK、Loki)。我建议不要写数据库,而是集中到消息队列里再异步落盘。
- 坑点:曾经有个服务打印了 debug 级别的 SQL 日志,每条查询都要输几十 KB 的 SQL 语句——生产环境流量一上来,磁盘 IO 直接打满。后来我把日志级别统一调到 INFO,并加了采样率 1/100。
四大平台日志采集差异对比
| 维度 | Android | iOS | Web | 后端服务 |
|---|---|---|---|---|
| 主要编程语言 | Java / Kotlin | Swift / ObjC | JavaScript | Go / Java / Python |
| 拦截方式 | OkHttp 拦截器 | NSURLProtocol | XHR / fetch 重写 | 网关或中间件 |
| 本地缓存 | SQLite(必须) | Core Data | IndexedDB / localStorage | 日志文件 |
| 上报触发 | 定时 / 数量阈值 | 前台实时 + 启动合并 | 事件触发 + 页面卸载 | 实时流式输出 |
| 最大问题 | 进程被杀丢日志 | body 字段缺失 | 跨域 / 拦截器屏蔽 | 磁盘爆满或性能瓶颈 |
统一日志模型有多重要
看到这你可能已经意识到了:不同端的日志字段名、时间格式、设备标识都不同。如果把原样日志直接丢进异常检测模型——字段一会叫 device_id,一会叫 deviceId,甚至 web 端根本没有——那模型根本没法统一处理。
我的做法是:在日志收集中间层做一次标准化转换。所有端的日志都转成统一的 Schema,比如:
{
"platform": "android/ios/web/backend",
"event_time": 1692384000123,
"user_id": "xxx",
"request_url": "https://...",
"response_code": 200,
"latency_ms": 235,
"body_size": 1024,
"extra": {} // 保留各端独有的字段
}
这样异常检测模块只需要处理一套格式。你也可以在后端直接按这个结构落盘,省去后续 ETL 的麻烦。
你看,这张图把四个平台各自的特征和最终汇入统一模型的关系画得很清楚。后面我们做日志采集代码实现时,就会围绕这个统一模型来设计——这样无论你接入的是什么端,核心的异常检测逻辑都不需要改动。
小技巧:在做多端日志接入时,可以先定义好统一的 Protobuf 或 JSON Schema,然后各端 SDK 按这个 Schema 输出。这样后续的数据清洗、特征提取都省心很多。