xlog学习记录

基础认识

mars 是微信官方的终端基础组件

  • C++ 编写(为了兼容多平台)
  • 业务性无关,平台性无关的基础组件
  • 支持接入 Android 或者 iOS/OS X 或者 Windows
    xlog学习记录_第1张图片
    mars架构
  • comm:可以独立使用的公共库,包括 socket、线程、消息队列、协程等;
  • xlog:高可靠性高性能的运行期日志组件;
  • SDT: 网络诊断组件;
  • STN: 信令分发网络模块,也是 Mars 最主要的部分。

优势

对于终端设备来说,打日志并不只是把日志信息写到文件里这么简单。除了流畅性,** 完整性 容错性,还有一个最重要的是安全性**。基于不怕被破解,但也不能任何人都能破解的原则, 对日志的规范比加密算法的选择更为重要,所以这里并没有讨论这一点。

一个优秀的终端日志模块无论怎么设计都必须做到:

  1. 不能把用户的隐私信息打印到日志文件里,不能把日志明文打到日志文件里。
  2. 不能影响程序的性能。最基本的保证是使用了日志不会导致程序卡顿。
  3. 不能因为程序被系统杀掉,或者发生了 crash,crash 捕捉模块没有捕捉到导致部分时间点没有日志, 要保证程序整个生命周期内都有日志。
  4. 不能因为部分数据损坏就影响了整个日志文件,应该最小化数据损坏对日志文件的影响。

上面这几点也即一直强调的 安全性 流畅性 完整性 容错性, 它们之间存在着矛盾关系

  • 如果直接写文件会卡顿,但如果使用内存做中间 buffer 又可能丢日志
  • 如果不对日志内容进行压缩会导致 IO 卡顿影响性能,但如果压缩,部分损坏可能会影响整个压缩块,而且为了增大压缩率集中压缩又可能导致 CPU 短时间飙高。

mars 的日志模块 xlog 就是在兼顾这四点的前提下做到:高性能高压缩率、不丢失任何一行日志、避免系统卡顿和 CPU 波峰。

使用(Android)

gradle 接入我们提供了两种接入方式:mars-wrapper 或者 mars-core。如果你只是想做个 sample 推荐使用 mars-wrapper,可以快速开发;但是如果你想把 mars 用到你的 app 中的话,推荐使用 mars-core,可定制性更高。

mars-wrapper

在 app/build.gradle 中添加 mars-wrapper 的依赖:

dependencies {
    compile 'com.tencent.mars:mars-wrapper:1.2.0'
}

或者

mars-core

在 app/build.gradle 中添加 mars-core 的依赖:

dependencies {
    compile 'com.tencent.mars:mars-core:1.2.2'
}

或者

mars-xlog

如果只想使用 xlog,可以只加 xlog 的依赖(mars-core,mars-wrapper 中都已经包括 xlog):

dependencies {
    compile 'com.tencent.mars:mars-xlog:1.0.7'
}

接着往下操作之前,请先确保你已经添加了 mars-wrapper 或者 mars-core 或者 mars-xlog 的依赖

Xlog Init

在程序启动加载 Xlog 后紧接着初始化 Xlog。但要注意如果你的程序使用了多进程,不要把多个进程的日志输出到同一个文件中,保证每个进程独享一个日志文件。而且保存 log 的目录请使用单独的目录,不要存放任何其他文件防止被 xlog 自动清理功能误删。

System.loadLibrary("c++_shared");
System.loadLibrary("marsxlog");

final String SDCARD = Environment.getExternalStorageDirectory().getAbsolutePath();
final String logPath = SDCARD + "/marssample/log";

// this is necessary, or may crash for SIGBUS
final String cachePath = this.getFilesDir() + "/xlog"

//init xlog
if (BuildConfig.DEBUG) {
    Xlog.appenderOpen(Xlog.LEVEL_DEBUG, Xlog.AppenderModeAsync, cachePath, logPath, "MarsSample", 0, "");
    Xlog.setConsoleLogOpen(true);

} else {
    Xlog.appenderOpen(Xlog.LEVEL_INFO, Xlog.AppenderModeAsync, cachePath, logPath, "MarsSample", 0, "");
    Xlog.setConsoleLogOpen(false);
}

Log.setLogImp(new Xlog());

程序退出时关闭日志:

Log.appenderClose();

如果你想修改 Xlog 的加密算法或者长短连的加解包部分甚至更改组件的其他部分,可以参考这里

Log生成完毕后,会在指定的路径下生成相应的日志文件:

shell@R7:/sdcard/marssample/log $ ll
-rw-rw---- root     sdcard_r   153600 2016-12-30 17:06 MarsSample.mmap2
-rw-rw---- root     sdcard_r    29633 2016-12-30 17:06 MarsSample_20161230.xlog

其中MarsSample.mmap2是缓存文件,不用关心,我们需要的是.xlog文件,我们把这个文件pull出来,使用Mars提供的Python脚本进行解密。
找到Mars源码log/crypt/decode_mars_log_file.py下的这个文件,执行:

➜  mars_xlog_sdk python decode_mars_log_file.py ~/Downloads/log/MarsSample_20161230.xlog

即可生成对应的log文件,用Sublime即可打开

常用 API 说明

Xlog.java

public static native void appenderOpen(int level, int mode, String cacheDir, String logDir, String nameprefix, int  cacheDays, String pubkey);

初始化日志需要调用的接口。

  • level: 日志级别,变量见 Xlog.java 里 LEVEL_XX, Debug版本推荐 LEVEL_DEBUG, Release 版本推荐 LEVEL_INFO
  • mode : 文件写入模式,分异步和同步,变量定义见 Xlog.java 里 AppednerModeXX, Release版本一定要用 AppednerModeAsync, Debug 版本两个都可以,但是使用 AppednerModeSync 可能会有卡顿。
  • cacheDir : 缓存目录,当 logDir 不可写时候会写进这个目录,可选项,不选用请给 "", 如若要给,建议给应用的 /data/data/packname/files/log 目录。
  • logDir : 日志写入目录,请给单独的目录,除了日志文件不要把其他文件放入该目录,不然可能会被日志的自动清理功能清理掉。
  • nameprefix : 日志文件名前缀,例如该值为TEST,生成的文件名为:TEST_20170102.xlog。
  • cacheDays : 一般情况下填0即可。非0表示会在 _cachedir 目录下存放几天的日志。
  • pubkey : 加密所用的 pub_key,具体可参考 Xlog 加密指引。

public static native void setConsoleLogOpen(boolean isOpen);
  • 是否会把日志打印到 logcat 中, 默认不打印。
  • isOpen : true 为打印,false为不打印。

Log.java

该文件包含打印日志最常调用的接口。

public static void setLogImp(LogImp imp)

设置 Log 的具体实现,这里必须调用 Log.setLogImp(new Xlog()); 日志才会写到 Xlog 中。

public static void appenderFlush(boolean isSync) 

当日志写入模式为异步时,调用该接口会把内存中的日志写入到文件。

  • isSync : true 为同步 flush,flush 结束后才会返回。 false 为异步 flush,不等待 flush 结束就返回。强制将缓存中的输出流(字节流,字符流等)强制输出.
public static void appenderClose()

关闭日志,在程序退出时调用。

public static void f/e/w/i/d(final String tag, final String msg)

写日志时调用的接口,对应不同级别的日志。

问题

常见问题

重要技术点

技术实现思路:使用流式方式对单行日志进行压缩,压缩加密后写进作为 log 中间 buffer的 mmap 中

技术水准有多牛逼,就看这个

mmap

如果大家对binder的实现有看过就知道,binder就是使用mmap来实现一次拷贝的跨进程的。

xlog学习记录_第2张图片

mmap 是使用逻辑内存对磁盘文件进行 映射,中间只是进行映射没有任何拷贝操作,避免了写文件的数据拷贝。操作内存就相当于在操作文件,避免了内核空间和用户空间的频繁切换。

等于写内存的速度:把512 Byte的数据分别写入150 kb大小的内存和 mmap,以及磁盘文件100w次并统计耗时

xlog学习记录_第3张图片

xlog实现方案: 先压缩再加密效率比较高,这个顺序不能改变。而且 在写入 mmap 之前先进行压缩,也会减少所占用的 mmap 的大小,进而减少 mmap 所占用内存的大小。就是其他模块每写一行日志日志模块就必须进行压缩。

压缩方案

xlog学习记录_第4张图片

第三种是把整个 app 生命周期作为一个压缩单位进行压缩, 如果这个压缩单位中有数据损坏,那么后面的日志也都解压不出来

但其实在短语式压缩过程中,滑动窗口并不是无限大的,一般是 32kb ,所以只需要把一定大小作为一个压缩单位就可以了。这也就是第四个方案, 这样的话即使压缩单位中有部分数据损坏,因为是流式压缩,并不影响这个单位中损坏数据之前的日志的解压,只会影响这个单位中这个损坏数据之后的日志

使用流式压缩后,和之前使用通用压缩的的日志方案进行了对比(耗时为单行日志的平均耗时):

xlog学习记录_第5张图片

多条日志同时压缩会导致 CPU 曲线短时间内极速升高,进而可能会导致程序卡顿,而流式压缩是把时间 分散在整个生命周期内, CPU 的曲线更平滑,相当于把压缩过程中使用的资源均分在整个 app 生命周期内。虽说时间上升了,但是分摊后CPU曲线更为平滑,实际上是性能的提升。

参考内容
github Mars 项目
mars Android 接入指南
聊聊微信 Xlog
【Dev Club 分享】微信mars 的高性能日志模块 xlog

你可能感兴趣的:(xlog学习记录)