xlog学习记录

作者: 墨染书 | 来源:发表于2019-06-03 23:48 被阅读24次

    基础认识

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

    • C++ 编写(为了兼容多平台)
    • 业务性无关,平台性无关的基础组件
    • 支持接入 Android 或者 iOS/OS X 或者 Windows
      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来实现一次拷贝的跨进程的。


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

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


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

    压缩方案


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

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

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


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

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

    相关文章

      网友评论

        本文标题:xlog学习记录

        本文链接:https://www.haomeiwen.com/subject/ejxkxctx.html