OnecardDemo 用户全量同步分阶段耗时日志设计
日期:2026-08-24
状态:已实施,待真机与现场全量同步验证
1. 目标
为“获取全部用户信息(分段)”增加可追溯的分阶段耗时日志,用来判断现场全量同步的主要耗时发生在服务器请求、XML 解析还是 SQLite 保存。
本功能只写现有应用本地日志,不新增数据库表,不把耗时明细写入 onecard.db,不增加新的页面控件,也不改变当前用户数据分页、解析和保存语义。
2. 已确认方案
复用现有 ReaderLog 和 ReaderLogStore:
- 日志写入应用私有目录
files/reader_logs/。 - 继续使用当前按天文件、单文件最大5MB、保留最近7天和自动滚动规则。
- 用户可从现有“查看读卡日志”页面查看和复制日志。
ServerOperationsActivity主动初始化日志,避免直接进入服务器页面时日志组件尚未初始化。- 所有用户全量同步事件统一使用
CUSTOMER_SYNC_前缀,便于在混合日志中检索。
不采用以下方案:
- 不单独创建服务器同步日志目录和查看页面,避免重复实现文件轮转和查看功能。
- 不只写 Logcat,因为 Logcat 不适合作为现场持久追溯记录。
3. 计时口径
耗时使用 ReaderLog.mark() 和 ReaderLog.elapsedMs(),底层为 System.nanoTime() 单调时钟。系统时间被人工修改或网络校时不会影响已开始阶段的耗时。
每批计时边界如下:
| 指标 | 开始点 | 结束点 | 是否包含后续阶段 |
|---|---|---|---|
请求耗时 requestMs |
调用 getCustomers 之前 |
完整 SOAP 响应返回或抛出异常 | 否 |
解析耗时 parseMs |
调用 CustomerBatchParser.parse 之前 |
返回全部 CustomerRecord 或抛出异常 |
否 |
保存耗时 saveMs |
调用 repository.saveBatch 之前 |
SQLite 批事务提交完成或抛出异常 | 否 |
单批总耗时 batchMs |
本批网络请求之前 | 保存事务提交并完成进度计算 | 是 |
同步总耗时 elapsedMs |
获取服务器用户总数之前 | 全量完成或失败处理结束 | 是 |
毫秒结果允许为 0,表示该阶段在当前时钟精度下不足1毫秒,不表示阶段没有执行。
4. 日志事件
4.1 同步级事件
| 事件 | 时机 | 记录字段 |
|---|---|---|
CUSTOMER_SYNC_START |
用户开始全量同步 | syncId、batchSize |
CUSTOMER_SYNC_COUNT_SUCCESS |
用户总数请求成功 | syncId、expectedCount、requestMs |
CUSTOMER_SYNC_SUCCESS |
全部批次保存并完成旧数据清理 | syncId、expectedCount、savedCount、batches、databaseCount、elapsedMs |
CUSTOMER_SYNC_FAILURE |
任一阶段失败并完成失败标记 | syncId、stage、batch、savedCount、successfulBatches、stageMs、elapsedMs、异常类型和消息 |
GetCustomerCount 也是一次服务器请求,因此单独记录 requestMs。
4.2 批次级事件
| 事件 | 时机 | 记录字段 |
|---|---|---|
CUSTOMER_SYNC_BATCH_START |
本批开始 | syncId、batch、startingCustomerId、requestedCount |
CUSTOMER_SYNC_REQUEST_SUCCESS |
SOAP 响应成功返回 | 上述定位字段、requestMs、responseBytes |
CUSTOMER_SYNC_PARSE_SUCCESS |
XML 解析成功 | syncId、batch、records、parseMs |
CUSTOMER_SYNC_SAVE_SUCCESS |
SQLite 事务提交成功 | syncId、batch、batchSaved、totalSaved、saveMs、batchMs |
失败不为每个阶段再建立独立事件名,统一写 CUSTOMER_SYNC_FAILURE,通过 stage=count/request/parse/save/complete 区分失败位置,stageMs 记录失败阶段从开始到抛出异常的耗时,避免失败请求丢失耗时证据。
5. 响应大小和安全边界
responseBytes 使用原始 SOAP XML 的 UTF-8 字节数计算,只记录长度,不保存响应内容。
允许记录的定位信息:
- 同步 ID、批次号。
- 起始
CUSTOMERID、请求数量、返回记录数和累计保存数。 - 响应字节数、阶段耗时和异常类型。
禁止写入日志的内容:
- SOAP 原文和用户节点原文。
- 姓名、卡号、证件号。
PWD、QUERYPWD及任何其他用户字段值。
异常消息沿用现有错误传播,但日志实现不得主动拼接或附加原始 SOAP 内容。日志写入失败继续按 ReaderLog 的既有规则降级到 Logcat,不影响同步业务。
6. 代码边界
CustomerSyncService:负责阶段边界、计时、事件字段和成功/失败日志;不依赖 Android 页面。ReaderLog:继续提供单调计时和本地日志写入,不修改轮转机制。ServerOperationsActivity:初始化ReaderLog,保留当前页面进度和最终摘要,不增加耗时明细控件。SQLiteCustomerRepository:不增加表或字段,不负责测量调用方看到的保存耗时。
为保持 JVM 单元测试可运行,CustomerSyncService 不直接硬编码静态日志调用;增加小型 SyncLogger 接口,生产实现委托给 ReaderLog,测试使用内存 fake 验证事件和耗时字段。该接口只承担同步日志,不扩展为通用日志框架。
7. 错误处理
- 用户总数请求失败:
stage=count,批次号记为0。 - 分页请求失败:
stage=request,记录当前批次和已经完成的批次数。 - XML 解析失败:
stage=parse,不记录响应原文。 - SQLite 保存失败:
stage=save,当前批次事务按现有规则回滚。 - 全量完成或旧数据清理失败:
stage=complete,按现有同步失败语义处理。 - 写日志自身失败:不得改变同步结果或抛给页面。
8. 测试与验收
8.1 自动测试
- JVM 测试使用可控单调时间验证
requestMs、parseMs、saveMs、batchMs和总耗时边界。 - JVM 测试验证成功流程事件顺序、批次号、数量和响应字节数。
- JVM 测试分别验证请求、解析、保存失败时的
stage,并确认不改变原有前批保留语义。 - 真机页面测试确认直接打开服务器页面后日志已经初始化,现有查询和同步按钮不受影响。
- 完整执行 JVM 测试、Debug/AndroidTest 构建、Lint 和本次相关真机测试。
8.2 现场验收
执行一次全量同步后,在“查看读卡日志”中搜索 CUSTOMER_SYNC_,每个成功批次应依次出现开始、请求成功、解析成功和保存成功事件;最终出现同步成功或失败事件。所有耗时必须为非负毫秒值,日志中不得出现用户原始字段值。
9. 非目标
- 不把批次耗时保存到 SQLite。
- 不增加历史同步统计、图表、导出或单独的服务器日志页面。
- 不根据耗时自动调整每批数量。
- 不增加自动重试,不改变当前失败停止策略。
- 不在本次修改中重命名“查看读卡日志”页面。