腾讯 XLog 日志框架 Android 端接入

为什么需要线上日志打印

对于大多数应用来说,线上日志确实没有太大必要,只要接入了友盟或是 Bugly,其提供的信息也足以解决出现的异常信息。但是问题就在于不是所有的问题都会导致崩溃,特别是强业务的应用,一个金额显示的不对是不会造成应用崩溃,但肯定会造成用户崩溃。这种问题,你无法通过应用统计平台查找信息来解决,只能通过线上日志框架来打印相关的业务流程看问题出在哪里。

因此,当你意识到 Android 系统提供的 android.util.Log 已经无法满足你使用的时候,那么恭喜你,你的应用已经有足够多的用户或业务。这些用户和业务能带给你大量的利润,但是也会给你带来技术上的挑战,也驱使着你做线上环境日志打印的基建。

目前可选的线上日志打印方案

在 Android 上进行线上环境的日志方案其实并没有太多好的选择,这主要是由于一点:性能。

如果不考虑性能问题,我们可以用任何方式来写入日志到磁盘中,标准文件IO或是直接IO。但是在线上环境,这是不可行,大量的IO必然造成性能上的消耗,轻则卡顿,重则ANR。因此我们需要选择一个在性能上说得过去的方案。

如果搜索一下,你能看到有好几个可用的方案:Timber、Logger、Logan、XLog。其中 Timber 和 Logger 只是 android.util.Log 的包装,不考虑持久化,真正的选择是在 XLog 和 Logan 之间。

XLog vs Logan

这两个日志方案都是 mmap 存储 + AES 加密,性能上都没有问题。

其中 XLog 是由腾讯发布的日志方案:腾讯 Mars-XLog Github ,不过自公开之后,腾讯也没有再维护这个库。当然,也只是对外提供的没有再更新和支持,其内部使用的版本肯定一直保持活跃的状态。你可以观察到 Github 上最新的日期也是在9年前了,这也是大公司的特点:为了KPI或影响力,公布一些技术方案,吸引一波流量,当热度过去,这些原本在聚光灯下的代码就都被扔进了垃圾桶。

而反观 Logan:美团点评日志系统-Github,这个由美团提供的线上日志方案,其 SDK 质量更好、维护也更活跃。但是它不仅仅是一个线上打日志的框架,而是一整套包括端上打印、日志回捞、前端服务等的日志系统。当你使用这个日志的时候,你会发现你需要搭建和使用它的一整套系统,你的工作流必须要与美团的这个日志系统强绑定。否则你连日志都无法解密。

这里要说一下细节,像 XLog 和 Logan 这种基于 mmap 的线上日志,基本不可能直接明文输出,都必须经过压缩和加密,生成的日志文件都是二进制格式,而 XLog 提供了解码脚本,Logan 则只有搭建其提供的日志服务才能解密。两者的区别如下:

对比维度 XLog Logan
官方解密工具 ✅ 提供 Python 解密脚本 ❌ 只有搭 Logan Server 才能解密
加密可关闭 ✅ 可选明文模式,调试方便 ❌ 强制加密,不设 Key/IV 直接初始化失败
多端支持 iOS/Android/Mac iOS/Android/Web/Flutter/小程序
日志回捞 ❌ 需自建 ✅ 内置
Maven Central ❌ 未发布 ✅ 已发布
维护状态 实质弃养(最后 Release 2018 年) 活跃

为什么要选择 XLog

上面可以知道目前线上日志打印基本就是 XLog 和 Logan,那么为什么这里说要选择 XLog 而不是 Logan 呢?其实原因只有一个:美团没有提供官方的解密工具。

而相比于 Logan, XLog 的问题就不算是什么问题了:虽然不更新,但是能用;虽然 so 未做 16K 对齐,但是能用;虽然没有发布到 mavenCentral,但是 aar 能用。总而言之,就是又不是不能用。再多的问题,也比没有官方解密工具好啊。

XLog 的引入和使用

XLog 的接入文档在其 Github 中也做了说明,但是时间久远,很多 API 已经发生了变化,里面所述内容基本没什么可参考的。看了不仅浪费时间还耽误事。

引入

原本 XLog 是被腾讯上传到 jcenter 的,但是后来 jcenter 关闭后,腾讯也没有做维护。现在的 XLog 是在一个 verve 的仓库中:

ruby 复制代码
//settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        // ......

        // mars-xlog 目前只在这个仓库中有,但是这个仓库是一家公司的公开仓库,但不确定是否稳定:
        // https://verve.jfrog.io/ui/repos/tree/General/verve-gradle-jcenter-cache/com/tencent/mars/mars-xlog/1.2.6
        maven {
            url = uri("https://verve.jfrog.io/artifactory/verve-gradle-release")
        }
    }
}

然后在 module 中引入 xlog:

scss 复制代码
dependencies {
//    从 verve 的公开仓库中下载
    implementation("com.tencent.mars:mars-xlog:1.2.6")
    
//  implementation(files("libs/mars-xlog-1.2.6.aar")) 建议下载aar到本地
}

但是还是建议直接下载 aar 文件进行依赖。毕竟大家都知道中国的网络是个什么回事,能放在本地的,就放在本地,毕竟谁都不像 gradle 在那儿一转就转几个小时。因此我选择本地 aar 文件。

混淆

这个在官方的文档里居然没说,但是如果不设置的话,在打 RELEASE 包时,会无法初始化。 混淆文件 consumer-rules.keep:

csharp 复制代码
# 保留 XLog 的 JNI 类,不能混淆
-keep class com.tencent.mars.** { *; }
-keep class com.tencent.mars.xlog.** { *; }

# 保留 native 方法(JNI 入口)
-keepclasseswithmembernames class * {
    native <methods>;
}

# XLog 的 Log 实体类(如果用到了)
-keep class com.tencent.mars.utils.** { *; }

然后在 module 中的进行设置:

scss 复制代码
android {
    // ......
    defaultConfig {
        // mars-xlog 的 AAR 只包含 armeabi-v7a 和 arm64-v8a 两种架构的 so
        ndk {
            abiFilters += listOf("armeabi-v7a", "arm64-v8a")
        }
        consumerProguardFiles("consumer-rules.keep")
    }
}

这里还关注一下 ndk 的配置,xlog 只提供了两个 abi 的 so,因此要限制目标设备的架构。

初始化

下面是 XLog 的初始化代码:

kotlin 复制代码
System.loadLibrary("c++_shared");
System.loadLibrary("marsxlog");

Log.setLogImp(new Xlog());
Log.appenderOpen(isDebug ? Xlog.LEVEL_VERBOSE : Xlog.LEVEL_INFO,
        Xlog.AppednerModeAsync,
        context.getFilesDir().getAbsolutePath() + File.separator + "xlogmmap",
        context.getFilesDir().getAbsolutePath() + File.separator + "xlog",
        Application.getProcessName(),
        0);

Log.setConsoleLogOpen(isDebug);

在做了上面的设置之后,就可以使用下面的代码进行日志打印:

java 复制代码
package com.tencent.mars.xlog;
public class Log {
    public static void f(String tag, String msg)
    public static void e(String tag, String msg)
    public static void w(String tag, String msg)
    public static void i(String tag, String msg)
    public static void d(String tag, String msg)
    public static void v(String tag, String msg)
}

当然 XLog 提供的方法不止这么几个,下面我们就逐个来解释这些方法的作用。

相关 API

Log.appenderOpen

java 复制代码
public static void appenderOpen(int level, int mode, String cacheDir, String logDir, String nameprefix, int cacheDays) 
  • level:日志级别,与系统的 Log 一样,从 LEVEL_VERBOSE 到 LEVEL_FATAL,也可以通过 setLogLevel 方法设置;
  • mode:写入的模式,AppednerModeAsync 异步写入;AppednerModeSync 同步写入;也可以通过 setAppenderMode 方法设置;
  • cacheDir:设置缓存目录,建议给应用的 /data/data/packname/files/log 目录,会在目录下生成后缀为 .mmap3 的缓存文件,
  • logDir:设置写入的文件目录,生成的 .xlog 日志写入到的目录,需要设置单独的路径。
  • nameprefix:设置日志文件名的前缀,例如该值为TEST,生成的文件名为:TEST_20170102.xlog。
  • cacheDays:表示在多少天后,将日志文件从 cacheDir 移动到 logDir 下,一般情况下填 0 即可。

Log.setConsoleLogOpen

java 复制代码
public static void setConsoleLogOpen(boolean isOpen)

设置是否在控制台打印日志,建议在 DEBUG 下打开,在 RELEASE 下关闭。

Log.appenderClose

java 复制代码
public static void appenderClose()

官方文档中说需要在程序退出时关闭日志,但是在实际情况中,基本没有调用这个方法的时机,Application 类的 onTerminate() 只有在模拟器中才会生效。因此可以不用关注。

Log.appenderFlush()

java 复制代码
public static void appenderFlush()
public static void appenderFlushSync(boolean isSync)

将内存中的日志内容刷入到文件中。

使用 XLog 的注意事项

XLog 的解压

使用 XLog 生成的日志以 xlog 为后缀,其内容为二进制格式,需要进行解码后才能查看,目前官方给的解码脚本为:mars/mars/xlog/crypt/decode_mars_nocrypt_log_file.py at master · Tencent/mars

使用如下的命令即可进行解码:

xml 复制代码
python decode_mars_nocrypt_log_file_py3.py <xlog文件或目录>

此时会在 xlog 的同文件夹中生成解码后的 log 文件。但是这里需要注意官方提供的这个 Python 文件所需的 Python 版本可能会很旧。

多依赖库

如果你的应用依赖了很多库,而如果这些库都使用了 XLog,那么你再使用时,由于 XLog 使用的全局单例的设计模式,基本都是静态方法。

在这种情况下,可能会造成配置冲突。这里可以考虑 XLog 中的多实例方法,或是统一配置参数。

相关推荐
黄林晴1 小时前
Kotlin Toolchain 0.11 发布:Amper 正式更名,统一 kotlin 命令
android·kotlin
雨白3 小时前
C语言基础快速入门与指针初探
android
Exploring4 小时前
避坑指南:升级 AGP 8.0+ 导致第三方 SDK 编译崩溃的完美解决方案
android
石山岭1 天前
自己动手写了一个 Android 虚拟定位 App:GPSSimulate 技术实
android·前端
杉氧1 天前
副作用 (Side Effects) 全攻略:如何像大师一样掌控 Composable 的生命周期?
android·架构·android jetpack
Kapaseker1 天前
Kotlin Toolchain 0.11 发布:主要是把 Amper 干没了
android·kotlin
三少爷的鞋1 天前
Android 现代架构不需要事件总线进阶篇
android
杉氧2 天前
深入理解 Compose 重组机制:快照系统如何驱动 UI 精准刷新?
android·架构·android jetpack
召钱熏2 天前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端