基于微信XLog的日志框架&&对于XLog的分析
建议在 个人博客 中查看,阅读体验更佳
之前写过一个 日志框架LogHelper ,是基于 Logger 开源库封装的,当时的因为项目本身的日志不是很多,完全可以使用,最近和其他公司合作,在一个新的项目上反馈,说在 大量log 的情况下会影响到手机主体功能的使用。从而让我对之前的日志行为做了一个深刻的反省
随后在开发群中咨询了其他开发的小伙伴,如果追求性能,可以研究一下 微信的 xlog ,也是本篇博客的重点
xlog 是什么 这个问题 我这也是在【腾讯Bugly干货分享】微信mars 的高性能日志模块 xlog得到了答案
简单来说 ,就是腾讯团队分享的基于 c/c++ 高可靠性高性能的运行期日志组件
知道了他是什么,就要只要他是怎么用的,打开github 找到官网Tencent/mars
使用非常简单
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 实现了他的功能
不要高兴的太早,后续的问题都头大
知道了最简单的用法,就想看看他支持哪些功能
按照官网的demo 首先分析一下appenderOpen
appenderOpen(int level, int mode, String cacheDir, String logDir, String nameprefix, int cacheDays, String pubkey)
日志级别 没啥好说的 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);
方法设置
写入的模式
- public static final int AppednerModeAsync = 0;
异步写入
- public static final int AppednerModeSync = 1;
同步写入
同步写入,可以理解为实时的日志,异步则不是
Release版本一定要用 AppednerModeAsync, Debug 版本两个都可以,但是使用 AppednerModeSync 可能会有卡顿
这里也可以使用
public static native void setAppenderMode(int mode);
方法设置
缓存目录,当 logDir 不可写时候会写进这个目录,可选项,不选用请给 "", 如若要给,建议给应用的 /data/data/packname/files/log 目录。
会在目录下生成后缀为 .mmap3 的缓存文件,
真正的日志,后缀为 .xlog
日志写入目录,请给单独的目录,除了日志文件不要把其他文件放入该目录,不然可能会被日志的自动清理功能清理掉。
日志文件名的前缀,例如该值为TEST,生成的文件名为:TEST_20170102.xlog。
一般情况下填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 的意思是 在多少天以后 从缓存目录移到日志目录
这里涉及到了日志的加密与解密,下面会专门介绍
在 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;
···java public static native void setConsoleLogOpen(boolean isOpen); ···
设置是否在控制台答应日志
这个方法是没用的,一开始以为哪里继承的有问题,在查看源码的时候发现 他是一个空方法,没有应用
使用的话会导致程序异常,在自己编译的so 中我就把它给去掉了
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的方法
然而在实际情况中 Application 类的 onTerminate() 只有在模拟器中才会生效,在真机中无效的,
如果在程序退出的时候没有触发 appenderClose 那么在下一次启动的时候,xlog 也会把日志写入到文件中
所以如何触发呢?
建议尽可能的去触发他 例如用户双击back 退出的情况下 你肯定是知道的 如果放在后台被杀死了,这个时候也真的没办法刷新,也没关系,上面也说了,再次启动的时候会刷新到日志中,
当日志写入模式为异步时,调用该接口会把内存中的日志写入到文件。
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
上面已经知道如何加密了,现在了解一下如何解密
在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"
}
}
...
}
当前最新版本
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)
Tencent/mars Mars Android 接口详细说明 【腾讯Bugly干货分享】微信mars 的高性能日志模块 xlog 本地编译