本文整理在 macOS 主机上使用 Python 3 和 Android NDK 28 编译腾讯 Mars XLog 的流程,并说明如何将生成的动态库接入 Android 项目。
编译环境
| 项目 | 要求 |
|---|---|
| 操作系统 | macOS(Intel 或 Apple Silicon) |
| Python | Python 3.10 及以上;Mars 官方 README 要求 Python >=3.10 |
| Android NDK | NDK 28,建议使用项目统一版本 |
| 构建工具 | CMake、Ninja(Android Studio/SDK 通常会提供) |
| 源码 | Tencent Mars master |
以下示例假定已经安装 Git、Android SDK,并将 NDK 解压到 Android SDK 的 ndk 目录。
一、配置 NDK 环境变量
1. 设置 NDK_ROOT
Mars 的 mars/build_android.py 会读取 NDK_ROOT,并检查该目录下的 source.properties。将版本号替换为本机实际安装的 NDK 28 目录:
bash
export NDK_ROOT="$HOME/Library/Android/sdk/ndk/28.0.13004108"
test -f "$NDK_ROOT/source.properties" && echo "NDK_ROOT=$NDK_ROOT"
如果使用 Android Studio 的 SDK 路径,可以先查看已安装版本:
bash
ls "$HOME/Library/Android/sdk/ndk"
需要长期生效时,将 export NDK_ROOT=... 写入 ~/.zshrc,然后执行 source ~/.zshrc。
2. 检查 Python、CMake 和 NDK 工具链
bash
python3 --version
cmake --version
"$NDK_ROOT/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-strip" --version
二、修改编译脚本
1. 获取 Mars 源码
bash
git clone https://github.com/Tencent/mars.git
cd mars
mars/build_android.py 是官方 Android 编译入口,但其中的 ANDROID_STRIP_FILE 和 ANDROID_STL_FILE 仍使用旧 NDK(GCC/sources/cxx-stl)目录。NDK 28 必须改用 LLVM 工具链目录。
2. 修改 mars/build_android.py
将原来的 ANDROID_STRIP_FILE 替换为:
python
ANDROID_STRIP_FILE = {
'armeabi': NDK_ROOT + '/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-strip',
'armeabi-v7a': NDK_ROOT + '/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-strip',
'x86': NDK_ROOT + '/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-strip',
'arm64-v8a': NDK_ROOT + '/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-strip',
'x86_64': NDK_ROOT + '/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-strip',
}
将原来的 ANDROID_STL_FILE 替换为:
python
ANDROID_STL_FILE = {
'armeabi-v7a': LLVM_PREBUILT + '/sysroot/usr/lib/arm-linux-androideabi/libc++_shared.so',
'arm64-v8a': LLVM_PREBUILT + '/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so',
'x86': LLVM_PREBUILT + '/sysroot/usr/lib/i686-linux-android/libc++_shared.so',
'x86_64': LLVM_PREBUILT + '/sysroot/usr/lib/x86_64-linux-android/libc++_shared.so',
}
然后将 get_android_strip_cmd 简化为直接返回 LLVM strip:
python
def get_android_strip_cmd(arch):
strip_cmd = ANDROID_STRIP_FILE[arch]
print('Android strip cmd:%s' % strip_cmd)
return strip_cmd
脚本默认只编译 armeabi-v7a 和 arm64-v8a,这两个 ABI 已覆盖大多数 Android 设备;如果需要 x86/x86_64,可在 archs 集合中加入对应 ABI。
3. 处理 NDK 28 的告警和导出符号(按需)
如果编译因为新版 Clang 将告警视为错误而失败,可在 mars/comm/CMakeExtraFlags.txt 的 Android 分支中,将额外参数追加到 SELF_EXTRA_FLAGS:
cmake
set(SELF_EXTRA_FLAGS "${SELF_EXTRA_FLAGS} -Wno-error=deprecated-builtins -Wno-error=deprecated-declarations -Wno-deprecated-builtins -Wno-deprecated-declarations")
如果链接阶段出现 undefined symbol,检查 mars/libraries/mars_android_sdk/jni/export.exp。针对 NDK 28,可删除以下旧符号导出项后重试:
text
__xlogger_Print_impl;
_Z22appender_set_console_logb;
_Z24appender_set_console_logb;
这一步只在实际出现对应链接错误时进行,避免无故修改导出 ABI。
三、编译过程
1. 编译 XLog
在 mars 目录执行:
bash
python3 build_android.py
脚本显示菜单后输入 3,即执行 Clean && build xlog。也可以直接为指定 ABI 调用脚本:
bash
python3 build_android.py <revision-tag> armeabi-v7a arm64-v8a
其中 <revision-tag> 会写入 Mars 的版本信息;不需要自定义版本信息时,使用菜单方式最稳妥。
编译过程会使用 CMake + Android toolchain,并通过 --target libzstd_static marsxlog 构建 XLog 及其压缩依赖。首次编译耗时较长,失败时先保留完整终端日志,重点检查 NDK_ROOT、CMake 版本和链接错误。
2. 生成 AAR(可选)
如果要将 Java/Kotlin API 和 .so 一起打包为 AAR,在 Mars 根目录执行:
bash
./gradlew :mars_xlog_sdk:assembleRelease
如果当前分支的 Gradle 项目显示模块路径为 :libraries:mars_xlog_sdk,则使用:
bash
./gradlew :libraries:mars_xlog_sdk:assembleRelease
以 ./gradlew projects 的实际输出为准。
四、编译结果
官方 build_android.py 会将 XLog 产物复制到以下目录:
text
mars/libraries/mars_xlog_sdk/libs/armeabi-v7a/libmarsxlog.so
mars/libraries/mars_xlog_sdk/libs/armeabi-v7a/libc++_shared.so
mars/libraries/mars_xlog_sdk/libs/arm64-v8a/libmarsxlog.so
mars/libraries/mars_xlog_sdk/libs/arm64-v8a/libc++_shared.so
未 strip 的符号文件位于 obj/local,必须长期归档,便于线上崩溃和 native 日志问题分析:
text
mars/libraries/mars_xlog_sdk/obj/local/armeabi-v7a/
mars/libraries/mars_xlog_sdk/obj/local/arm64-v8a/
检查产物架构和动态库依赖:
bash
file mars/libraries/mars_xlog_sdk/libs/arm64-v8a/libmarsxlog.so
otool -L mars/libraries/mars_xlog_sdk/libs/arm64-v8a/libmarsxlog.so
如果生成了 AAR,通常位于:
text
mars/libraries/mars_xlog_sdk/build/outputs/aar/mars_xlog_sdk-release.aar
也可以不使用 AAR,直接将每个 ABI 目录下的 .so 复制到业务工程的 app/src/main/jniLibs/<abi>/。
五、使用方式
1. Gradle 接入
只使用 XLog 时,可以依赖官方 Maven 包:
kotlin
dependencies {
implementation("com.tencent.mars:mars-xlog:1.2.6")
}
使用自编 AAR 时:
kotlin
dependencies {
implementation(files("libs/mars_xlog_sdk-release.aar"))
}
如果手动复制 .so,目录结构应为:
text
app/src/main/jniLibs/armeabi-v7a/libmarsxlog.so
app/src/main/jniLibs/armeabi-v7a/libc++_shared.so
app/src/main/jniLibs/arm64-v8a/libmarsxlog.so
app/src/main/jniLibs/arm64-v8a/libc++_shared.so
不要同时从 AAR 和 jniLibs 引入同名 .so,否则可能触发重复打包错误。
2. 初始化 XLog
在 Application.onCreate() 或其他全局初始化入口中加载 native 库,并为日志和 mmap 缓存使用独立目录:
kotlin
System.loadLibrary("c++_shared")
System.loadLibrary("marsxlog")
val logDir = File(filesDir, "xlog").apply { mkdirs() }
val cacheDir = File(filesDir, "xlog_mmap").apply { mkdirs() }
Xlog.open(
true,
if (BuildConfig.DEBUG) Xlog.LEVEL_VERBOSE else Xlog.LEVEL_INFO,
Xlog.AppednerModeAsync,
cacheDir.absolutePath,
logDir.absolutePath,
"app",
"<PUBKEY>"
)
val xlog = Xlog()
Log.setLogImp(xlog)
Log.setConsoleLogOpen(BuildConfig.DEBUG)
不同 Mars 版本的 Java API 参数形式可能不同:部分版本使用 Log.appenderOpen(...),部分版本使用 Xlog.XLogConfig 或 Xlog.open(...)。以自编 AAR 中的 com.tencent.mars.xlog.Xlog 和 Log 方法签名为准。旧版 appenderOpen 的典型调用如下(第三个参数是公钥,不是缓存目录):
java
Log.appenderOpen(Xlog.LEVEL_DEBUG, Xlog.AppednerModeAsync, "<PUBKEY>", logDir, "app", 0);
使用配置对象时,核心配置应包含:
java
Xlog.XLogConfig config = new Xlog.XLogConfig();
config.mode = Xlog.AppednerModeAsync;
config.logdir = logDir;
config.cachedir = cacheDir;
config.nameprefix = "app";
config.pubkey = "<PUBKEY>";
config.compressmode = Xlog.ZLIB_MODE;
config.compresslevel = 0;
config.level = BuildConfig.DEBUG ? Xlog.LEVEL_VERBOSE : Xlog.LEVEL_INFO;
Log.setLogImp(new Xlog());
其中 <PUBKEY> 不是随意填写的字符串,而是由 Mars 的密钥生成脚本计算出的公钥。启用加密后,日志文件中的异步数据使用该公钥对应的私钥解密;私钥只能离线保存,不能打包进 App 或提交到仓库。
如果 pubkey 留空,XLog 不会使用该密钥对异步日志加密,拿到日志文件后可直接使用 Mars 官方的解密工具处理。需要保护日志内容时,应配置生成的 pubkey,并使用与之配套的私钥配合 Python 3 解码器离线解密。
生成 pubkey 和私钥
Mars 源码中的 mars/xlog/crypt/gen_key.py 是 Python 2 脚本,在 Python 3 下不能直接运行。本文使用 Python 3 重写版本:
执行脚本:
bash
python3 gen_mars_xlog_key.py
脚本会输出两段十六进制字符串:
text
保存好私钥(仅用于离线解密,切勿写入 App 或提交到仓库):
<64 个十六进制字符>
appender_open 的 pubkey 参数:
<128 个十六进制字符>
将第二段 128 个字符填入 Xlog.open(...) 的最后一个参数,或填入 XLogConfig.pubkey。生成的第一段 64 个字符是配套私钥;公钥和私钥必须成对使用,重新生成密钥后要同步替换 App 中的公钥,并妥善保存新的私钥。
使用私钥解密日志
Mars 源码中的 mars/xlog/crypt/decode_mars_crypt_log_file.py 同样是 Python 2 脚本。本文使用 Python 3 重写版本:
解密命令如下,--private-key 后填写生成密钥时保存的 64 位私钥:
bash
python3 mars_xlog_decoder.py /path/to/app.xlog --private-key <64 个十六进制字符>
默认输出文件为 app.xlog.log,也可以显式指定输出路径:
bash
python3 /path/to/mars_xlog_decoder.py /path/to/app.xlog
/path/to/app.decoded.log
--private-key <64 个十六进制字符>
解码器支持当前 Mars XLog 的 zlib 和 zstd 格式。解码 zstd 日志时,若系统没有可用的 zstd 动态库,需要先安装 Python 包:
bash
python3 -m pip install zstandard
不要把私钥直接写入 shell 历史或公开 CI 日志;更安全的做法是通过环境变量或密码管理器在本地临时注入。
macOS 双击 .xlog 自动解码(Automator)
可以使用 macOS 自带的 Automator 创建一个应用程序,并将 .xlog 文件关联到该应用。以后双击任意 .xlog 文件即可自动调用 Python 3 解码器。
- 打开 Automator ,选择 新建文稿 → 应用程序。
- 在左侧搜索并拖入 运行 Shell 脚本。
- 将 传递输入 设置为 作为参数。
- 填入以下脚本,并根据本机路径修改
DECODER。将PRIVATE_KEY替换为生成密钥时保存的 64 位私钥:
bash
DECODER="/path/to/mars_xlog_decoder.py"
PRIVATE_KEY="<64 个十六进制字符的私钥>"
for f in "$@"
do
/usr/bin/python3 "$DECODER" "$f" --private-key "$PRIVATE_KEY"
done
- 将应用保存为
Xlog Decoder.app,放到"应用程序"或"下载"目录均可。 - 在任意
.xlog文件上右键,选择 显示简介。 - 在 打开方式 中选择
Xlog Decoder.app,然后点击 全部更改... 并确认。
之后双击任意 .xlog 文件,Finder 就会把文件路径作为参数传给 Automator,脚本会逐个调用解码器。解码器未指定输出路径时,会在原文件旁生成 <原文件名>.xlog.log。不要把真实私钥提交到代码仓库或分享给文章读者;发布教程时应保留占位符,让读者使用自己的私钥。
检查 ELF 是否满足 16 KB 对齐
Android 设备和应用对 16 KB page size 的兼容性检查可以使用:
上面三个链接指向当前工作区中的脚本路径。发布本文时,请将脚本上传到可公开访问的代码仓库、附件存储或文章资源目录,并将链接替换为对应的公开 URL;否则文章读者无法访问本机绝对路径。
检查编译出的所有 .so:
bash
chmod +x /path/to/check_elf_alignment.sh
/path/to/check_elf_alignment.sh mars/libraries/mars_xlog_sdk/libs
也可以直接检查 APK:
bash
/path/to/check_elf_alignment.sh app/build/outputs/apk/release/app-release.apk
脚本会逐个输出 ALIGNED 或 UNALIGNED 以及首个 LOAD 段的对齐值;其中只有 arm64-v8a 和 x86_64 的 native 库需要重点满足 16 KB 对齐。检查 APK 时,脚本还会调用 zipalign -P 16 验证 APK 内的 so 条目;如提示缺少参数,需要安装 Build Tools 35.0.0-rc3 或更高版本。
日志目录必须独占。XLog 的清理逻辑可能删除目录中的过期文件,不要把业务文件、数据库或其他缓存放入同一目录;多进程应用也应为每个进程使用独立日志文件前缀或目录。
3. 输出和关闭日志
kotlin
Log.i("Demo", "Mars XLog initialized")
Log.appenderFlush()
// 应用进程退出前调用
Log.appenderClose()
生产环境建议关闭控制台输出并将日志级别设为 LEVEL_INFO;Debug 环境可使用 LEVEL_VERBOSE,便于定位问题。压缩日志通常以 .xlog 文件保存,不能直接用普通文本编辑器阅读,应使用 Mars 提供的解密/解压工具,并确保公钥和发布版本配置一致。