对移动开发者来说,最头疼的莫过于线上出现问题,本地无法复现又没有任何日志的场景。但是考虑到应用性能和安全性,无法打印和保存过多的日志。颇有一种书到用时方恨少的感觉。

一、xlog介绍

xlog 是腾讯 Mars 终端基础组件中的通用日志模块,它有下面几个优点:

  • 使用mmap的方案进行日志写入,mmap 是使用逻辑内存对磁盘文件进行映射,中间只是进行映射没有任何拷贝操作,避免了写文件的数据拷贝。操作内存就相当于在操作文件,避免了内核空间和用户空间的频繁切换。提升了效率同时也保证了日志的完整性。下图是官方提供的写入效率对比图
  • 日志支持加密,提高日志信息的安全性
  • 底层使用c++实现,支持Android和iOS平台
  • 支持设置单个日志文件的保存天数和大小,基本满足日常的开发需求

二、xlog 使用

参考官方提供的 demo

2.1 引入xlog

configurations {
 	//定义一个新的依赖方式
    cmake_depends
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    implementation 'com.android.support:appcompat-v7:26.1.0'
    testImplementation 'junit:junit:4.12'
    implementation "com.tencent.mars:mars-xlog:${VERSION_NAME}${VERSION_NAME_SUFFIX}"
    cmake_depends "com.tencent.mars:mars-xlog:${VERSION_NAME}${VERSION_NAME_SUFFIX}"
}

//获取xlog中的.so文件,用于ndk编译使用,如果项目中不包括ndk代码,可以不需要
task resolveDependencies {
    project.configurations.each { configuration ->
        if ("cmake_depends".equalsIgnoreCase(configuration.name)) {
            def lib = configuration.resolve()[0]
            copy {
                from zipTree(lib)
                into "${project.rootDir}/${project.name}/src/main/jniLibs/"
                include "jni/**/*.so"
            }
        }
    }
}

//build 依赖 resolveDependencies task
build.dependsOn resolveDependencies

导入xlog头文件

android xlog文件打开方法 手机xlog文件打开方法_android xlog文件打开方法


cmake 中 查找并链接 xlog 动态库

cmake_minimum_required(VERSION 3.4.1)

include_directories(export_include)

add_library(native-lib SHARED native-lib.cpp)

find_library(log-lib log)

set(XLOG_PATH ../jniLibs/jni/${ANDROID_ABI}/)

find_library(XLOG_LIB marsxlog PATHS ${XLOG_PATH} NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)

target_link_libraries(native-lib ${log-lib} ${XLOG_LIB})

2.2 使用xlog

初始化xlog
static {
        System.loadLibrary("c++_shared");
        System.loadLibrary("marsxlog");
        System.loadLibrary("native-lib");
    }

public void initLog(boolean isDebug) {
    String logPath = getExternalFilesDir(null).getPath() + "/logsample/xlog";
    Xlog xlog = new Xlog();
//    xlog.setMaxFileSize(0, 1024*1024);
//    xlog.setMaxAliveTime(0, 1);
    Log.setLogImp(xlog);
    Log.setConsoleLogOpen(true);
    Log.appenderOpen(isDebug ? Xlog.LEVEL_DEBUG : Xlog.LEVEL_INFO, Xlog.AppednerModeAsync, "",
        logPath, "LOGSAMPLE", 0);
  }

主要是appenderOpen 方法:

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

level:日志等级,如果使用xlog,日志等级就比较好控制,不需要再传递给openvpn debug开关
mode: 写入的模式,支持同步和异步写入,同步写入可能会导致卡顿,release版本一定要异步写入
cacheDir:设置缓存目录
logDir:设置写入的文件目录,
nameprefix:设置日志文件名的前缀,
cacheDays:在多少天以后 从缓存目录移到日志目录 一般情况下填0即可

xlog 使用

直接使用xlog相关的api进行打印日志,日志会自动写入文件中。

com.tencent.mars.xlog.Log.d(TAG, "test");
        com.tencent.mars.xlog.Log.e(TAG, "test");
#include "xlogger/android_xlog.h"

	LOGI("test", "111111111111");
	LOGD("test", "111111111111");
  • 生成的日志文件

    .xlog 文件是无法直接查看的,需要使用xlog对应的python脚本进行处理。
python decode_mars_nocrypt_log_file.py 日志文件

android xlog文件打开方法 手机xlog文件打开方法_日志_02


转换后的日志内容:

android xlog文件打开方法 手机xlog文件打开方法_android xlog文件打开方法_03

xlog加解密
  • 生成加密秘钥,执行 python gen_key.py 命令
  • android xlog文件打开方法 手机xlog文件打开方法_android_04

    android xlog文件打开方法 手机xlog文件打开方法_android xlog文件打开方法_05

  • 其中 private key 是私钥,appender_open's parameter 是公钥。
  • 代码中设置公钥,初始化方式和上面有些差异
public void initLog(boolean isDebug) {
        String logPath = getExternalFilesDir(null).getPath() + "/logsample/xlog";


        Xlog xlog = new Xlog();
        xlog.setMaxAliveTime(0, 24 * 60 * 60);

        Xlog.open(false, isDebug ? Xlog.LEVEL_DEBUG : Xlog.LEVEL_INFO, Xlog.AppednerModeAsync, "",
            logPath, "LOGSAMPLE", LOG_PUB_KEY);

        xlog.setMaxFileSize(0, 1024 * 120);
        xlog.setConsoleLogOpen(0, true);
        Log.setLogImp(xlog);
    }

Log.appenderOpen() 方法没有 pubKey 参数,需要使用 Xlog.open() 方法初始化。

  • 修改解密脚本 decode_mars_crypt_log_file.py,替换刚才生成的私钥和公钥。
其他使用细节
  1. 由于使用异步模式,所以日志并不会实时同步到文件中,当执行日志上报操作时,需要调用 appenderFlush 方法将内存中的日志写入文件中,避免日志不完整。
  2. 单条日志大小限制16kb,超过16kb会被截取,目前没有其他解决方法,需要自行判断日志大小进行拆分写入。