MacOS 编译腾讯 Mars XLog 及使用(已16KB对齐)

本文整理在 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_FILEANDROID_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-v7aarm64-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.XLogConfigXlog.open(...)。以自编 AAR 中的 com.tencent.mars.xlog.XlogLog 方法签名为准。旧版 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 解码器。

  1. 打开 Automator ,选择 新建文稿 → 应用程序
  2. 在左侧搜索并拖入 运行 Shell 脚本
  3. 传递输入 设置为 作为参数
  4. 填入以下脚本,并根据本机路径修改 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
  1. 将应用保存为 Xlog Decoder.app,放到"应用程序"或"下载"目录均可。
  2. 在任意 .xlog 文件上右键,选择 显示简介
  3. 打开方式 中选择 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

脚本会逐个输出 ALIGNEDUNALIGNED 以及首个 LOAD 段的对齐值;其中只有 arm64-v8ax86_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 提供的解密/解压工具,并确保公钥和发布版本配置一致。

参考资料

相关推荐
alexhilton21 小时前
让架构边界变成可执行的测试
android·kotlin·android jetpack
数据治理自习室1 天前
AI 应用评测体系(GraphRAG)
android·大数据·人工智能·kotlin
weixin_531670892 天前
Interlude起来:动作动画和文字通知哪个更适合休息提醒?Mac上有没有用桌宠做动作引导的软件?
人工智能·macos·mac·swift
名剑走天下2 天前
Android 面试题大全
android·kotlin
chjif2 天前
Android 设备管控开发实战:自定义 Launcher 如何只显示指定 App?
android·kotlin
非凡ghost2 天前
用 Kotlin 和 Jetpack Compose 重写的安卓视频播放器,原生体验有多流畅?
android·java·kotlin·音视频·软件需求
名剑走天下3 天前
android 面试题
kotlin
蜡台3 天前
Jetpack Compose 稳定性、重组优化(Stable / @NonRestartableComposable)
android·kotlin·compose·jepack
JMchen1233 天前
Jetpack 内核实战(十):实战(下)——编辑页、全局设置与内存泄漏排查
kotlin·内存泄漏·android 实战·datastore 设置·jetpack 项目优化·编辑页开发