基于微信XLog的日志框架&&对于XLog的分析

基于微信XLog的日志框架&&对于XLog的分析java

前言

以前写过一个 日志框架LogHelper ,是基于 Logger 开源库封装的,当时的由于项目自己的日志不是不少,彻底可使用,最近和其余公司合做,在一个新的项目上反馈,说在 大量log 的状况下会影响到手机主体功能的使用。从而让我对以前的日志行为作了一个深入的检讨python

随后在开发群中咨询了其余开发的小伙伴,若是追求性能,能够研究一下 微信的 xlog ,也是本篇博客的重点android

xlog 是什么

xlog 是什么 这个问题 我这也是在【腾讯Bugly干货分享】微信mars 的高性能日志模块 xlog获得了答案c++

简单来讲 ,就是腾讯团队分享的基于 c/c++ 高可靠性高性能的运行期日志组件git

官网的 sample

知道了他是什么,就要只要他是怎么用的,打开github 找到官网Tencent/marsgithub

使用很是简单缓存

下载库

dependencies {
    compile 'com.tencent.mars:mars-xlog:1.2.3'
}
复制代码

使用

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());
复制代码

OK 实现了他的功能bash

不要高兴的太早,后续的问题都头大微信

分析各个方法的做用

知道了最简单的用法,就想看看他支持哪些功能app

按照官网的demo 首先分析一下appenderOpen

appenderOpen(int level, int mode, String cacheDir, String logDir, String nameprefix, int cacheDays, String pubkey)

level

日志级别 没啥好说的 XLog 中已经写得很清楚了

public static final int LEVEL_ALL = 0;
public static final int LEVEL_VERBOSE = 0;
public static final int LEVEL_DEBUG = 1;
public static final int LEVEL_INFO = 2;
public static final int LEVEL_WARNING = 3;
public static final int LEVEL_ERROR = 4;
public static final int LEVEL_FATAL = 5;
public static final int LEVEL_NONE = 6;
复制代码

值得注意的地方 debug 版本下建议把控制台日志打开,日志级别设为 Verbose 或者 Debug, release 版本建议把控制台日志关闭,日志级别使用 Info.

这个在官网的 接入指南

这里也可使用

public static native void setLogLevel(int logLevel);
复制代码

方法设置

mode

写入的模式

  • public static final int AppednerModeAsync = 0;

异步写入

  • public static final int AppednerModeSync = 1;

同步写入

同步写入,能够理解为实时的日志,异步则不是

Release版本必定要用 AppednerModeAsync, Debug 版本两个均可以,可是使用 AppednerModeSync 可能会有卡顿

这里也可使用

public static native void setAppenderMode(int mode);
复制代码

方法设置

cacheDir 设置缓存目录

缓存目录,当 logDir 不可写时候会写进这个目录,可选项,不选用请给 "", 如若要给,建议给应用的 /data/data/packname/files/log 目录。

会在目录下生成后缀为 .mmap3 的缓存文件,

logDir 设置写入的文件目录

真正的日志,后缀为 .xlog

日志写入目录,请给单独的目录,除了日志文件不要把其余文件放入该目录,否则可能会被日志的自动清理功能清理掉。

nameprefix 设置日志文件名的前缀

日志文件名的前缀,例如该值为TEST,生成的文件名为:TEST_20170102.xlog。

cacheDays

通常状况下填0便可。非0表示会在 _cachedir 目录下存放几天的日志。

这里的描述比较晦涩难懂,当我设置这个参数非0 的时候 会发现 本来设置在 logDir 目录下的文件 出如今了 cacheDir

例如 正常应该是

文件结构

- cacheDir
   - log.mmap3
- logDir
   - log_20200710.xlog
   - log_20200711.xlog
复制代码

变成这样

- cacheDir
   - log.mmap3
   - log_20200710.xlog
   - log_20200711.xlog
- logDir

复制代码

所有到了 cacheDir 下面

cacheDays 的意思是 在多少天之后 从缓存目录移到日志目录

pubkey 设置加密的 pubkey

这里涉及到了日志的加密与解密,下面会专门介绍

setMaxFileSize 设置文件大小

在 Xlog 下有一个 native 方法

public static native void setMaxFileSize(long size);
复制代码

他表示 最大文件大小,这里须要说一下,本来的默认设置 是一天一个日志文件在 appender.h 描述的很清楚

/* * By default, all logs will write to one file everyday. You can split logs to multi-file by changing max_file_size. * * @param _max_byte_size Max byte size of single log file, default is 0, meaning do not split. */
void appender_set_max_file_size(uint64_t _max_byte_size);
复制代码

默认状况下,全部日志天天都写入一个文件。能够经过更改max_file_size将日志分割为多个文件。单个日志文件的最大字节大小,默认为0,表示不分割

当超过设置的文件大小之后。文件会变成以下目录结构

- cacheDir
   - log.mmap3
- logDir
   - log_20200710.xlog
   - log_20200710_1.xlog
   - log_20200710_2.xlog
复制代码

appender.cc 对应的有以下逻辑,

static long __get_next_fileindex(const std::string& _fileprefix, const std::string& _fileext) {
    ...
    return (filesize > sg_max_file_size) ? index + 1 : index;

复制代码

setConsoleLogOpen 设置是否在控制台答应日志

···java public static native void setConsoleLogOpen(boolean isOpen); ···

设置是否在控制台答应日志

setErrLogOpen

这个方法是没用的,一开始觉得哪里继承的有问题,在查看源码的时候发现 他是一个空方法,没有应用

使用的话会致使程序异常,在本身编译的so 中我就把它给去掉了

setMaxAliveTime 设置单个文件最大保留时间

public static native void setMaxAliveTime(long duration);
复制代码

置单个文件最大保留时间 单位是秒,这个方法有3个须要注意的地方,

  • 必须在 appenderOpen 方法以前才有效
  • 最小的时间是 一天
  • 默认的时间是10天

appender.cc 中能够看到

static const long kMaxLogAliveTime = 10 * 24 * 60 * 60;    // 10 days in second
static const long kMinLogAliveTime = 24 * 60 * 60;    // 1 days in second
static long sg_max_alive_time = kMaxLogAliveTime;

....


void appender_set_max_alive_duration(long _max_time) {
	if (_max_time >= kMinLogAliveTime) {
		sg_max_alive_time = _max_time;
	}
}
复制代码

默认的时间是10天

appenderClose

文档中介绍说是在 程序退出时关闭日志 调用appenderClose的方法

然而在实际状况中 Application 类的 onTerminate() 只有在模拟器中才会生效,在真机中无效的,

若是在程序退出的时候没有触发 appenderClose 那么在下一次启动的时候,xlog 也会把日志写入到文件中

因此如何触发呢?

建议尽量的去触发他 例如用户双击back 退出的状况下 你确定是知道的 若是放在后台被杀死了,这个时候也真的没办法刷新,也不要紧,上面也说了,再次启动的时候会刷新到日志中,

appenderFlush

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

isSync : true 为同步 flush,flush 结束后才会返回。 isSync : false 为异步 flush,不等待 flush 结束就返回。

日志文件的加密

这一块单独拿出来讲明,是由于以前使用上遇到了坑

首先是这个 入参 PUB_KEY,一脸懵,是个啥,

mars/blob/master/mars/log/crypt/gen_key.py 这个就是可以获取到 PUB_KEY 的方法

运行以下

$ python gen_key.py
WARNING: Executing a script that is loading libcrypto in an unsafe way. This will fail in a future version of macOS. Set the LIBRESSL_REDIRECT_STUB_ABORT=1 in the environment to force this into an error.
save private key
471e607b1bb3760205f74a5e53d2764f795601e241ebc780c849e7fde1b4ce40

appender_open's parameter:
300330b09d9e771d6163bc53a4e23b188ac9b2f5c7150366835bce3a12b0c8d9c5ecb0b15274f12b2dffae7f4b11c3b3d340e0521e8690578f51813c93190e1e
复制代码

上面的 private key 本身保存好

appender_open's parameter: 就是须要的 PUB_KEY

日志文件的解密

上面已经知道如何加密了,如今了解一下如何解密

下载pyelliptic1

Xlog 加密使用指引中可以看到

须要下载 pyelliptic1.5.7 而后编译 不然下面的命令会失败

直接解密脚本

xlog 很贴心的给咱们提供了两个脚本

使用 decode_mars_nocrypt_log_file.py 解压没有加密的

python decode_mars_nocrypt_log_file [path]
复制代码

使用 decode_mars_crypt_log_file.py 加密的文件

在使用以前须要将 脚本中的

PRIV_KEY = "145aa7717bf9745b91e9569b80bbf1eedaa6cc6cd0e26317d810e35710f44cf8"
PUB_KEY = "572d1e2710ae5fbca54c76a382fdd44050b3a675cb2bf39feebe85ef63d947aff0fa4943f1112e8b6af34bebebbaefa1a0aae055d9259b89a1858f7cc9af9df1"
复制代码

改为上面本身获取到的 key 不然是解压不出来的

python decode_mars_crypt_log_file.py ~/Desktop/log/log_20200710.xlog
复制代码

直接生成一个

- cacheDir
    - log.mmap3
- logDir
    - log_20200710.xlog
    - log_20200710.xlog.log
复制代码

也能够自定义名字

python decode_mars_crypt_log_file.py ~/Desktop/log/log_20200710.xlog ~/Desktop/log/1.log
复制代码
- cacheDir
    - log.mmap3
- logDir
    - log_20200710.xlog
    - 1.log
复制代码

修改日志的格式

打开咱们解压好的日志查看

^^^^^^^^^^Oct 14 2019^^^20:27:59^^^^^^^^^^[17223,17223][2020-07-24 +0800 09:49:19]
get mmap time: 3
MARS_URL: 
MARS_PATH: master
MARS_REVISION: 85b19f92
MARS_BUILD_TIME: 2019-10-14 20:27:57
MARS_BUILD_JOB: 
log appender mode:0, use mmap:1
cache dir space info, capacity:57926635520 free:52452691968 available:52452691968
log dir space info, capacity:57926635520 free:52452691968 available:52452691968
[I][2020-07-24 +8.0 09:49:21.179][17223, 17223][TAG][, , 0][======================> 1
[I][2020-07-24 +8.0 09:49:21.180][17223, 17223][TAG][, , 0][======================> 2
[I][2020-07-24 +8.0 09:49:21.180][17223, 17223][TAG][, , 0][======================> 3
[I][2020-07-24 +8.0 09:49:21.180][17223, 17223][TAG][, , 0][======================> 4
[I][2020-07-24 +8.0 09:49:21.181][17223, 17223][TAG][, , 0][======================> 5
[I][2020-07-24 +8.0 09:49:21.181][17223, 17223][TAG][, , 0][======================> 6
[I][2020-07-24 +8.0 09:49:21.182][17223, 17223][TAG][, , 0][======================> 7
[I][2020-07-24 +8.0 09:49:21.182][17223, 17223][TAG][, , 0][======================> 8
[I][2020-07-24 +8.0 09:49:21.182][17223, 17223][TAG][, , 0][======================> 9
[I][2020-07-24 +8.0 09:49:21.183][17223, 17223][TAG][, , 0][======================> 10
[I][2020-07-24 +8.0 09:49:21.183][17223, 17223][TAG][, , 0][======================> 11
[I][2020-07-24 +8.0 09:49:21.183][17223, 17223][TAG][, , 0][======================> 12
[I][2020-07-24 +8.0 09:49:21.184][17223, 17223][TAG][, , 0][======================> 13
[I][2020-07-24 +8.0 09:49:21.184][17223, 17223][TAG][, , 0][======================> 14
[I][2020-07-24 +8.0 09:49:21.185][17223, 17223][TAG][, , 0][======================> 15
[I][2020-07-24 +8.0 09:49:21.185][17223, 17223][TAG][, , 0][======================> 16
[I][2020-07-24 +8.0 09:49:21.185][17223, 17223][TAG][, , 0][======================> 17
复制代码

我擦泪 除了咱们须要的信息之外,还有这么多杂七杂八的信息,如何去掉,而且本身定义一下格式

这里就须要本身去编译 so 了,好在 xlog 已经给咱们提供了很好的编译代码

对应的文档 本地编译

对于编译这块按照文档来就行了 须要注意的是

  • 必定要用 ndk-r20 不要用最新版本的 21
  • 必定用 Python2.7 mac 自带 不用要 Python3

去掉头文件

首先咱们去到这个头文件,对于一个日志框架来着,这个没啥用

^^^^^^^^^^Oct 14 2019^^^20:27:59^^^^^^^^^^[17223,17223][2020-07-24 +0800 09:49:19]
get mmap time: 3
MARS_URL: 
MARS_PATH: master
MARS_REVISION: 85b19f92
MARS_BUILD_TIME: 2019-10-14 20:27:57
MARS_BUILD_JOB: 
log appender mode:0, use mmap:1
cache dir space info, capacity:57926635520 free:52452691968 available:52452691968
log dir space info, capacity:57926635520 free:52452691968 available:52452691968
复制代码

在本机下载好的 mars 下,找到 appender.cc 将头文件去掉

修改日志格式

默认的格式很长

[I][2020-07-24 +8.0 09:49:21.179][17223, 17223][TAG][, , 0][======================> 1
复制代码

[日志级别][时间][pid,tid][tag][filename,strFuncName,line][日志内容

是一个这样结构

比较乱,咱们想要的日志 就时间,级别,日志内容 就好了

找到 formater.cc

将本来的

int ret = snprintf((char*)_log.PosPtr(), 1024, "[%s][%s][%" PRIdMAX ", %" PRIdMAX "%s][%s][%s, %s, %d][",  // **CPPLINT SKIP**
                           _logbody ? levelStrings[_info->level] : levelStrings[kLevelFatal], temp_time,
                           _info->pid, _info->tid, _info->tid == _info->maintid ? "*" : "", _info->tag ? _info->tag : "",
                           filename, strFuncName, _info->line);
复制代码

改为

int ret = snprintf((char*)_log.PosPtr(), 1024,     "[%s][%s]",  // **CPPLINT SKIP**
                        temp_time,   _logbody ? levelStrings[_info->level] : levelStrings[kLevelFatal] );
复制代码

就好了

而后重新编译,将so 翻入项目 在看一下如今的效果

[2020-07-24 +8.0 11:47:42.597][I]======================>9
复制代码

ok 打完收工

简单的封装一下

基本上分析和实现了咱们须要的功能,那么把这部分简单的封装一下

放上核心的 Builder 源码可在下面自行查看

package com.allens.xlog

import android.content.Context
import com.tencent.mars.xlog.Log
import com.tencent.mars.xlog.Xlog class Builder(context: Context) {

    companion object {
        //日志的tag
        var tag = "log_tag"
    }

    //是不是debug 模式
    private var debug = true


    //是否打印控制台日志
    private var consoleLogOpen = true


    //是否天天一个日志文件
    private var oneFileEveryday = true

    //默认的位置
    private val defCachePath = context.getExternalFilesDir(null)?.path + "/mmap"

    // mmap 位置 默认缓存的位置
    private var cachePath = defCachePath

    //实际保存的log 位置
    private var logPath = context.getExternalFilesDir(null)?.path + "/logDir"

    //文件名称前缀 例如该值为TEST,生成的文件名为:TEST_20170102.xlog
    private var namePreFix = "log"

    //写入文件的模式
    private var model = LogModel.Async

    //最大文件大小
    //默认状况下,全部日志天天都写入一个文件。能够经过更改max_file_size将日志分割为多个文件。
    //单个日志文件的最大字节大小,默认为0,表示不分割
    // 最大 当文件不能超过 10M
    private var maxFileSize = 0L

    //日志级别
    //debug 版本下建议把控制台日志打开,日志级别设为 Verbose 或者 Debug, release 版本建议把控制台日志关闭,日志级别使用 Info.
    private var logLevel = LogLevel.LEVEL_INFO

    //经过 python gen_key.py 获取到的公钥
    private var pubKey = ""

    //单个文件最大保留时间 最小 1天 默认时间 10天
    private var maxAliveTime = 10

    //缓存的天数 通常状况下填0便可。非0表示会在 _cachedir 目录下存放几天的日志。
    //原来缓存日期的意思是几天后从缓存目录移到日志目录
    private var cacheDays = 0

    fun setCachePath(cachePath: String): Builder {
        this.cachePath = cachePath
        return this
    }

    fun setLogPath(logPath: String): Builder {
        this.logPath = logPath
        return this
    }


    fun setNamePreFix(namePreFix: String): Builder {
        this.namePreFix = namePreFix
        return this
    }

    fun setModel(model: LogModel): Builder {
        this.model = model
        return this
    }

    fun setPubKey(key: String): Builder {
        this.pubKey = key
        return this
    }

    //原来缓存日期的意思是几天后从缓存目录移到日志目录 默认 0 便可
    //若是想让文件保留多少天 用 [setMaxAliveTime] 方法便可
    //大于 0 的时候 默认会放在缓存的位置上 [cachePath]
    fun setCacheDays(days: Int): Builder {
        if (days < 0) {
            this.cacheDays = 0
        } else {
            this.cacheDays = days
        }
        return this
    }

    fun setDebug(debug: Boolean): Builder {
        this.debug = debug
        return this
    }

    fun setLogLevel(level: LogLevel): Builder {
        this.logLevel = level
        return this
    }

    fun setConsoleLogOpen(consoleLogOpen: Boolean): Builder {
        this.consoleLogOpen = consoleLogOpen
        return this
    }


    fun setTag(logTag: String): Builder {
        tag = logTag
        return this
    }


    /** * [isOpen] true 设置天天一个日志文件 * false 那么 [setMaxFileSize] 生效 */
    fun setOneFileEveryday(isOpen: Boolean): Builder {
        this.oneFileEveryday = isOpen
        return this
    }

    fun setMaxFileSize(maxFileSize: Float): Builder {
        when {
            maxFileSize < 0 -> {
                this.maxFileSize = 0L
            }
            maxFileSize > 10 -> {
                this.maxFileSize = (10 * 1024 * 1024).toLong()
            }
            else -> {
                this.maxFileSize = (maxFileSize * 1024 * 1024).toLong()
            }
        }
        return this
    }

    /** * [day] 设置单个文件的过时时间 默认10天 在程序启动30S 之后会检查过时文件 * 过时时间依据 当前系统时间 - 文件最后修改时间计算 * 默认 单个文件保存 10天 */
    fun setMaxAliveTime(day: Int): Builder {
        when {
            day < 0 -> {
                this.maxAliveTime = 0
            }
            day > 10 -> {
                this.maxAliveTime = 10
            }
            else -> {
                this.maxAliveTime = day
            }
        }
        return this
    }

    fun init() {

        if (!debug) {
            //判断若是是release 就强制使用 异步
            model = LogModel.Async
            //日志级别使用 Info
            logLevel = LogLevel.LEVEL_INFO
        }

        if (cachePath.isEmpty()) {
            //cachePath这个参数必传,并且要data下的私有文件目录,例如 /data/data/packagename/files/xlog, mmap文件会放在这个目录,若是传空串,可能会发生 SIGBUS 的crash。
            cachePath = defCachePath
        }


        android.util.Log.i(tag, "Xlog=========================================>")
        android.util.Log.i(
            tag,
            "info" + "\n"
                    + "level:" + logLevel.level + "\n"
                    + "model:" + model.model + "\n"
                    + "cachePath:" + cachePath + "\n"
                    + "logPath:" + logPath + "\n"
                    + "namePreFix:" + namePreFix + "\n"
                    + "cacheDays:" + cacheDays + "\n"
                    + "pubKey:" + pubKey + "\n"
                    + "consoleLogOpen:" + consoleLogOpen + "\n"
                    + "maxFileSize:" + maxFileSize + "\n"
        )

        android.util.Log.i(tag, "Xlog=========================================<")
        Xlog.setConsoleLogOpen(consoleLogOpen)
        //天天一个日志文件
        if (oneFileEveryday) {
            Xlog.setMaxFileSize(0)
        } else {
            Xlog.setMaxFileSize(maxFileSize)
        }

        Xlog.setMaxAliveTime((maxAliveTime * 24 * 60 * 60).toLong())

        Xlog.appenderOpen(
            logLevel.level,
            model.model,
            cachePath,
            logPath,
            namePreFix,
            cacheDays,
            pubKey
        )
        Log.setLogImp(Xlog())
    }


}
复制代码

下载

Step 1. Add the JitPack repository to your build file Add it in your root build.gradle at the end of repositories:

allprojects {
		repositories {
			...
			maven { url 'https://www.jitpack.io' }
		}
	}

复制代码

Step 2. Add the dependency

dependencies {
	        implementation 'com.github.JiangHaiYang01:XLogHelper:Tag'
	}
复制代码

添加 abiFilter

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.1"

    defaultConfig {
        ...
        ndk {
            abiFilter "armeabi-v7a"
        }
    }

    ...
}
复制代码

当前最新版本

使用

初始化,建议放在 Application 中

XLogHelper.create(this)
            .setModel(LogModel.Async)
            .setTag("TAG")
            .setConsoleLogOpen(true)
            .setLogLevel(LogLevel.LEVEL_INFO)
            .setNamePreFix("log")
            .setPubKey("572d1e2710ae5fbca54c76a382fdd44050b3a675cb2bf39feebe85ef63d947aff0fa4943f1112e8b6af34bebebbaefa1a0aae055d9259b89a1858f7cc9af9df1")
            .setMaxFileSize(1f)
            .setOneFileEveryday(true)
            .setCacheDays(0)
            .setMaxAliveTime(2)
            .init()
复制代码

使用

XLogHelper.i("======================> %s", i)
XLogHelper.e("======================> %s", i)
复制代码

github

写了那么多 双手奉上代码 XLogHelper

参考

Tencent/mars Mars Android 接口详细说明 【腾讯Bugly干货分享】微信mars 的高性能日志模块 xlog 本地编译

相关文章
相关标签/搜索