一、现存问题
目前,业内移动端日志库大多都存在以下几个问题:
- 卡顿,影响性能
- 日志丢失
- 安全性
- 日志分散
首先,日志模块作为底层的基础库,对上层的性能影响必须尽量小,但是日志的写操作是非常高频的,频繁在Java堆里操作数据容易导致GC的发生,从而引起应用卡顿,而频繁的I/O操作也很容易导致CPU占用过高,甚至出现CPU峰值,从而影响应用性能。
其次,日志丢失的场景也很常见,例如当用户的App发生了崩溃,崩溃日志还来不及写入文件,程序就退出了,但本次崩溃产生的日志就会丢失。对于开发者来说,这种情况是非常致命的,因为这类日志丢失,意味着无法复现用户的崩溃场景,很多问题依然得不到解决。
第三点,日志的安全性也是至关重要的,绝对不能随意被破解成明文,也要防止网络被劫持导致的日志泄漏。
最后一点,对于移动应用来说,日志肯定不止一种,一般会包含端到端日志、代码日志、崩溃日志、埋点日志这几种,甚至会更多。不同种类的日志都具有各自的特点,会导致日志比较分散,查一个问题需要在各个不同的日志平台查不同的日志,例如端到端日志还存在日志采样,这无疑增加了开发者定位问题的成本。
二、设计
作为一款基础日志库,在设计之初就必须考虑如何解决日志系统现存的一些问题。
2.1 卡顿,影响性能
I/O是比较耗性能的操作,写日志需要大量的I/O操作,为了提升性能,首先要减少I/O操作,最有效的措施就是加缓存。先把日志缓存到内存中,达到一定大小的时候再写入文件。为了减少写入本地的日志大小,需要对数据进行压缩,为了增强日志的安全性,需要对日志进行加密。然而这样做的弊端是:
- 对Android来说,对日志加密压缩等操作全部在Java堆里面。由于日志写入是一个高频的动作,频繁地堆内存操作,容易引发Java的GC,导致应用卡顿;
- 集中压缩会导致CPU短时间飙高,出现峰值;
- 由于日志是内存缓存,在杀进程、Crash的时候,容易丢失内存数据,从而导致日志丢失。
Logan的解决方案是通过Native方式来实现日志底层的核心逻辑,也就是C编写底层库。这样做不光能解决Java GC问题,还做到了一份代码运行在Android和iOS两个平台上。同时在C层实现流式的压缩和加密数据,可以减少CPU峰值,使程序运行更加顺滑。而且先压缩再加密的方式压缩率比较高,整体效率较高,所以这个顺序不能变。
2.2 日志丢失
加缓存之后,异常退出丢失日志的问题就必须解决,Logan为此引入了MMAP机制。MMAP是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系。MMAP机制的优势是:
- MMAP使用逻辑内存对磁盘文件进行映射,操作内存就相当于操作文件;
- 经过测试发现,操作MMAP的速度和操作内存的速度一样快,可以用MMAP来做数据缓存;
- MMAP将日志回写时机交给操作系统控制。如内存不足,进程退出的时候操作系统会自动回写文件;
- MMAP对文件的读写操作不需要页缓存,只需要从磁盘到用户主存的一次数据拷贝过程,减少了数据的拷贝次数,提高了文件读写效率。
引入MMAP机制之后,日志丢失问题得到了有效解决,同时也提升了性能。不过这种方式也不能百分百解决日志丢失的问题,MMAP存在初始化失败的情况,这时候Logan会初始化堆内存来做日志缓存。根据我们统计的数据来看,MMAP初始化失败的情况仅占0.002%,已经是一个小概率事件了。
2.3 安全性
日志文件的安全性必须得到保障,不能随意被破解,更不能明文存储。Logan采用了流式加密的方式,使用对称密钥加密日志数据,存储到本地。同时在日志上传时,使用非对称密钥对对称密钥Key做加密上传,防止密钥Key被破解,从而在网络层保证日志安全。
2.4 日志分散
针对日志分散的情况,为了保证日志全面,需要做本地聚合存储。Logan采用了自研的日志协议,对于不同种类的日志都会按照Logan日志协议进行格式化处理,存储到本地。当需要上报的时候进行集中上报,通过Logan日志协议进行反解,还原出不同日志的原本面貌。同时Logan后台提供了聚合展示的能力,全面展示日志内容,根据协议综合各种日志进行分析,使用时间轴等方式展示不同种日志的重要信息,使得开发者只需要通过Logan平台就可以查询到某一段时间App到底产生了哪些日志,可以快速复现问题场景,定位问题并处理。
关于Logan平台是如何展示日志的,下文会再进行说明。
小结
采集工具设计:
- 性能:避免了CPU峰值,同时减少了CPU使用,避免卡顿情况发生。
- 丢失率:跨平台C库提供了日志协议数据的格式化处理,针对大日志的分片处理,引入了MMAP机制解决了日志丢失问题
- 安全性:使用AES进行日志加密确保日志安全性
- 侵入性
- 存储空间:
- 采用“先压缩再加密”的顺序,使用流式的加密和压缩。
- 为了节约用户手机空间大小,日志文件只保留最近7天的日志,过期会自动删除。在Android设备上Logan将日志保存在沙盒中,保证了日志文件的安全性。
- 用户流量:由于日志上报网络请求的频次相对较高,为了节省用户流量,日志通常不会太大。尤其是网络日志等这种实时性较高的日志。
采集时机设计:
- 主动上报:
- 进行预埋,在一些场景下上传,由于日志上报需要网络请求,对于移动App来说频繁网络请求会比较耗电,所以需要注意频率:
- 在特定行为发生时进行上报(例如用户投诉、前后台切换的时候可以上传、从无网到有网切换的时候可以上传)
- 在日志积累到一定程度
- 一定时间上报一次
- 客服引导:通过客服引导用户上报。
- 进行预埋,在一些场景下上传,由于日志上报需要网络请求,对于移动App来说频繁网络请求会比较耗电,所以需要注意频率:
- 回捞上报:是由后端使用PushSDK向客户端发起回捞指令
- 日志回捞平台需要有着严格的审核机制,确保开发者不会侵犯用户隐私,只关注问题场景。
- 日志回捞,依赖于Push。客户端被唤醒接收Push消息,受到一些条件影响:
- Android想要后台唤醒App,需要确保Push进程在后台存活;
- iOS想要后台唤醒APP,需要确保用户开启后台刷新开关;
- 网络环境太差,Android上Push长连建立不成功。