基於微信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 本地編譯

相關文章
相關標籤/搜索