专为Android高频业务设计的本地日志库StatLog,采用异步写入、MMAP和索引机制实现高吞吐与快速查询,支持轮转后上传避免影响写入。内置AI日志分析模块,基于Agent与ToolCalling通过本地工具召回真实证据,大模型据此生成准确回答,适用于金融、支付等场景。
在 Android 客户端开发中,与后端进行接口联调是常见工作环节。当遇到问题,后端反馈“客户端传参不对”,而客户端认为“接口返回如此”,最有效的解决方式并非猜测或争论,而是立即调取当时的请求参数、返回数据、状态流转及错误堆栈。
为此,我们专门设计了一款 Android 本地日志库:StatLog。其核心目标非常明确:快。日志库不应成为业务链路中的性能负担,尤其是在金融、支付、交易、风控等 App 中,日志既要能够持续开启,又要在问题发生时快速查询,同时还要能配合 AI 帮助开发人员理解日志内容。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
本文将重点介绍 StatLog 的性能、架构设计,以及 AI LogChat 如何围绕本地日志进行分析。完整接入代码与参数说明已在项目 README 中提供,本文不再展开。
普通日志方案在小日志量场景下表现尚可,但进入高频业务场景后,会面临几个实际问题:
StatLog 围绕这些目标进行设计:高吞吐写入、快速本地查询、可靠上传,并叠加一层 AI 排障能力。
AI 使用方面,采用 deepseek 接口,成本较低。
以下数据来自项目中的性能控制台及本地压力测试记录,并非通用 benchmark。实际线上表现受机型、存储状态、日志长度、索引开关、上传策略等因素影响。
| 场景 | 测试结果 |
|---|---|
| 基础写入 | 20,000 条,136 ms,约 147k 条/秒 |
| 多线程写入 | 4 线程,20,000 条,100 ms,约 200k 条/秒 |
| 内容校验 | 200,000 条,737 ms,丢失 0 条 |
| 完整压力测试 | 400,000 条,10 线程混合场景,约 1 秒级 |
| 1GB 大文件验证 | 5,360,000 条写入后完整读回,数量匹配 |
| 1GB 读取内存 | 流式读取,记录中读取后内存仅增长约 132 KB |
40 万条日志这一场景值得关注。这已不是“随便打几条 log”的量级,而是接近高频业务、压测、异常追踪时的日志密度。在这种场景下,日志库必须将业务线程上的操作做到足够轻量,真正繁重的任务交由后台处理。
StatLog 的写入链路如下:
业务代码→ QuantumLogger.log()→ logChannel→ processorLoop(s)→ LogInterceptorChain→ serializeBatch()→ batchChannel→ writerLoop→ LogWriter→ .bin→ LogIndexWriter→ .qidx / .qmeta
其中包含几个关键设计:
第一,业务线程仅负责将日志提交到队列,不在业务线程中进行序列化、写文件、维护索引等重操作。
第二,后台采用批量处理机制。日志先进入 logChannel,由多个 processor 收集、过滤、序列化,再统一交给 writer 写入。批量写入相比逐条写入更加稳定。
第三,底层写入优先使用 DoubleBufferMmapWriter。MMAP 适合高吞吐连续写入,双缓冲可减少写入阻塞。若 MMAP 不可用或出现异常,会自动降级到普通文件写入。
第四,热路径采用对象池复用,减少高频日志场景下的对象分配和 GC 压力。
第五,紧急日志单独处理。例如 crash、支付失败、风控命中等关键日志,可走 urgent 路径,绕过普通队列,直接执行 write + flush + index flush,确保关键证据尽量落盘。
这里插入日志核心架构图:
整个项目并非单文件 logger,而是拆分为多个模块:
| 模块 | 作用 |
|---|---|
logcore | 核心写入引擎,负责异步管线、二进制编码、MMAP/File 写入、轮转和索引 |
logview | 日志查看页面,支持文件列表、倒序分页、过滤和详情 |
upload | 上传模块,支持轮转后上传、批量上传、重试、网络策略和上传状态管理 |
monitor | 性能监控模块,记录吞吐、延迟、队列、批次、丢弃和内存指标 |
logchat | AI 日志问答模块,负责工具调用、向量检索、证据清洗和最终回答 |
sentence_embeddings | 本地 embedding 模型运行时,为 logchat 提供日志切片向量化能力 |
模块化拆分的好处是各功能独立清晰:写入、查看、上传、监控、AI 分析互不干扰,不需要的模块可以不接入。
StatLog 的主日志文件为 .bin,是唯一的事实来源。围绕 .bin,还包含以下几类辅助文件:
| 文件 | 作用 |
|---|---|
.bin | 主日志文件,保存真实日志内容 |
.qidx | 定长索引,保存 offset、length、timestamp、priority、tagId、urgent 等信息 |
.qmeta | 聚合元数据,保存总数、tag 字典、level 计数、tag + level 组合计数 |
.upload_state.json | 上传状态,配合 .uploaded 位图避免重复上传 |
| ObjectBox 向量库 | logchat 使用,一个日志文件加一个 embedding 模型对应一个向量库目录 |
其中 .qidx 和 .qmeta 最为重要。若仅依赖 .bin,查询最近日志需要倒序扫描,统计数量也需要重新遍历。文件较小时尚可接受,文件增大后体验会明显下降。有了 .qidx,日志查看页面可根据 offset 和 length 快速定位具体日志内容;有了 .qmeta,总数、level 分布、tag 分布等统计信息可直接读取元数据,无需每次全文件扫描。因此,StatLog 不仅写入速度快,查看和统计也同样高效。
日志上传最需避免的是影响当前写入操作。StatLog 推荐默认采用“文件轮转后上传”策略:当前活跃文件继续写入,已轮转出去的旧文件进入上传流程。这样,上传失败、网络波动、服务端响应慢等问题,都不会直接压到当前写日志的路径上。
若业务需要更高实时性,也可按条数进行 batch 上传;若希望完全自主控制,也可仅登记状态,由业务手动触发上传。该设计的核心原则始终是:日志系统服务业务,但不能反过来拖累业务。
这里插入 AI LogChat 架构图:
AI 部分的设计初期也走过一些弯路。最容易想到的方式是:将用户问题向量化,然后到向量库中检索相似日志,再交由大模型回答。但实际排查日志时,该方案并不足够。因为许多问题并非纯语义相似:
因此,目前的 LogChat 采用 Agent + Tool Calling + 本地工具 + RAG 的闭环架构:
用户问题→ LogChatClient→ LogToolCallingAgent→ 远程大模型 Tool Calling→ ToolRegistry 执行本地工具→ 读取 .bin/.qidx/.qmeta/ObjectBox→ EvidenceCleaner 清洗证据→ EvidenceReranker 重排证据→ 大模型基于证据生成最终回答
大模型并非直接“猜测答案”,也不直接操作数据库。它负责理解用户意图,并通过 Tool Calling 选择合适的语义工具;真正读取日志、查询索引、检索向量库的操作均在本地执行。本地工具执行完毕后,将结果作为证据提交给大模型,使其基于真实日志输出最终答案。
原因很简单:不安全,也不现实。日志中可能包含设备信息、接口数据、业务字段甚至敏感内容。完整日志文件动辄几十 MB、几百 MB,不适合直接塞入大模型上下文。
更合理的方式是:
这样既能节省 token,也能降低大模型产生幻觉的概率。
当前本地工具覆盖以下方向:
例如可以提问:
这些问题背后所使用的查询方式各不相同,因此并未将其设计为固定的 when 判断,而是尽量让大模型通过 Tool Calling 输出语义计划,再由本地执行真实工具。
从设计定位来看,StatLog 适合以下几类 App:
若仅为 Demo 项目,或每天仅输出几十条日志,普通日志方案已足够。但如果 App 中日志量确实很大,且日志是排查问题的核心依据,那么日志库本身值得认真设计。
StatLog 的核心设计思路可归纳为三点:
第一,写入要快。业务线程仅做轻量提交,后台批量处理,底层采用 MMAP 并辅以降级文件写入兜底。
第二,查询要快。.bin 保存事实,.qidx/.qmeta 提供快速索引和统计能力,大文件读取采用流式扫描。
第三,AI 要基于证据。大模型负责理解问题和生成答案,本地工具负责查询真实日志,中间进行证据清洗和重排。
如此一来,日志库不再仅仅是“打 log”的工具,而是从写入、查看、上传、监控到 AI 排障的一整套完整链路。完整接入方式、参数说明及架构图均已在 README 中提供,本文不再重复贴代码。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述