前言
对于移动开发者来说,针对一些用户反馈难以复现的线上问题,分析日志有时候是解决问题的必要手段。 但是日志的收集一直有个痛点,就是性能与日志完整性无法兼得。 要实现高性能的日志收集,势必要使用大量内存,先将日志写入内存中,然后在合适的时机将内存里的日志写入到文件系统中(flush), 如果在 flush 之前用户强杀了进程,那么内存里的内容会因此而丢失。 日志实时写入文件可以保证日志的完整性,但是写文件是 IO 操作,涉及到用户态与内核态的切换,而且这种开销是开启线程都无法避免的,也就是说即使开启一个新线程实时写入也是相对耗时的。
关于读写文件涉及到用户态和内核态切换推荐阅读《Linux探秘之用户态与内核态》,总之要减少读写文件的次数,类比线程上下文的切换所带来的开销。那么,如何才能既减少读写文件次数,又防止断电造成内存数据丢失?Log4a 正是为了解决这个问题而诞生的,使用 mmap 文件映射内存作为缓存,可以在不牺牲性能的前提下最大化的保证日志的完整性。
日志首先会写入到 mmap 文件映射内存中,基于 mmap 的特性,即使用户强杀了进程,日志文件也不会丢失,并且会在下次初始化 Log4a 的时候回写到日志文件中。当然这并不是首创,微信开源的 mars 框架中的 xlog 模块也是基于 mmap 特性实现的,考虑到 xlog 模块功能比较多,使用比较复杂,同时也是出于学习的目的而写了 Log4a。
PS :有兴趣的加入Android工程师交流QQ群:752016839 主要针对Android开发人员提升自己,突破瓶颈,相信你来学习,会有提升和收获。
mmap 是什么
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。(http://www.cnblogs.com/huxiao-tee/p/4660352.html)
mmap 内存映射文件之后,可以直接通过操作内存来读写文件,性能上接近直接读写内存。针对一次写文件,节省了用户态到内核态切换的开销,也减少了数据拷贝的次数。
Log4a 核心方法分析
对于 Android 端的日志框架,开源项目不少,大部分都是 Java 上层封装来实现更美观的输出,这并不是本文的重点,事实上,如何高效的写文件才是下面要讨论的。核心类就一个 me.pqpo.librarylog4a.LogBuffer , 可以接入到任意现有的日志框架中来实现高效的文件日志记录。LogBuffer 的代码不多就全部贴出了了:
public class LogBuffer {
private static final String TAG = "LogBuffer";
private long ptr = 0;
private String logPath;
private String bufferPath;
private int bufferSize;
public LogBuffer(String bufferPath, int capacity, String logPath) {
this.bufferPath = bufferPath;
this.bufferSize = capacity;
this.logPath = logPath;
try {
ptr = initNative(bufferPath, capacity, logPath);
}catch (Exception e) {
Log.e(TAG, Log4a.getStackTraceString(e));
}
}
public String getLogPath() {
return logPath;
}
public String getBufferPath() {
return bufferPath;
}
public int getBufferSize() {
return bufferSize;
}
public void write(String log) {
if (ptr != 0) {
try {
writeNative(ptr, log);
}catch (Exception e) {
Log.e(TAG, Log4a.getStackTraceString(e));
}
}
}
public void flushAsync() {
if (ptr != 0) {
try {
flushAsyncNative(ptr);
}catch (Exception e) {
Log.e(TAG, Log4a.getStackTraceString(e));
}
}
}
public void release() {