安卓如何收敛自身以及第三方sdk日志

本文最后更新于:2025年11月23日 下午

安卓日志来源一般有自身 app 或者第三方 SDK,自己的比较容易归一化处理,优秀的 SDK 也会提供日志策略接口,但有一些喜欢直接用安卓的标准日志接口或者不能设置日志策略,这种就比较讨厌了,处理起来也比较麻烦。有两种办法:混淆时全部去掉或者 hook 系统日志接口。

编译期“阉割”:混淆处理

编译期“阉割”:配置R8/Proguard 日志优化规则,直接把日志方法当空气处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 要全局去掉-dontoptimize
# 去掉 SDK 的 debug/info 日志
-assumenosideeffects class com.xxx.sdk.internal.SdkLog {
public static int d(...);
public static int i(...);
# 不写 e(...),保留错误日志
}

# 去除全部log
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** i(...);
public static *** e(...);
public static *** w(...);
}

# 去除System.out.println();
-assumenosideeffects class java.io.PrintStream {
public void println(...);
}

这样的打击范围太大,出现问题时无法收集日志。

hook系统日志打印接口

下面介绍使用字节跳动的bhook和腾讯的xlog来处理日志:前者用来hook日志接口,后者用来储存日志。

日志打印原理:

Android app层或framework层,通过调用Log/Slog/Rlog中的方法打印日志,接着通过JNI会调用到native层android_util_Log_println_native接口,最终通过liblog的__android_log_buf_write方法进行日志打印。

1
2
3
4
5
6
7
8
9
10
11
__android_log_buf_write

-->__android_log_write_log_message

-->get_logger_function()

-->__android_log_logd_logger

-->write_to_log

-->LogdWrite

如果只需要拦截app层或framework层,只需要拦截android_util_Log_println_native 所在的libandroid_runtime.so 即可,android_util_Log_println_native 调用了__android_log_buf_write 方法。

实践

参考:bhook/doc/quickstart.zh-CN.md at main · bytedance/bhook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <jni.h>
#include <string>
//#include <android/log.h>
#include <bytehook.h>
#include "xlogger/android_xlog.h"
#define TAG "wesley-jni"
//#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
//#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
//#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__);

static bool g_consoleLogOpen = false;
static bool g_saveToFile = false;

int android_log_buf_write_proxy(int bufID, int prio, const char *tag, const char *text) {
BYTEHOOK_STACK_SCOPE();
//LOGE("hook log")
int ret = 0;
char tag_buffer[50];
if (g_consoleLogOpen) {
snprintf(tag_buffer, sizeof(tag_buffer), "wesley-%s", tag);
ret = BYTEHOOK_CALL_PREV(android_log_buf_write_proxy, bufID, prio, tag_buffer, text);
}
if (g_saveToFile) {
if (prio >= ANDROID_LOG_ERROR) {
XLOGE(tag, "%s", text);
} else {
XLOGI(tag, "%s", text);
}
}
return ret;
}

// 过滤器函数。返回 true 表示需要 hook 这个 so;返回 false 表示不需要 hook 这个 so。
bool hook_log_allow_filter(const char *caller_path_name, void *arg)
{
//if(nullptr != strstr(caller_path_name, "liblog.so")) return true;

//android_util_Log_println_native 所在so
if(nullptr != strstr(caller_path_name, "libandroid_runtime.so")) return true;

return false;
}

bytehook_stub_t stub_of_android_log_buf_write = nullptr; // hook 的存根,用于后续 unhook
int hook_logcat() {
//__android_log_write
//bytehook_hook_all(nullptr, "android_log_print_proxy", (void *) (my_log_print), nullptr,
// nullptr);
// stub_of_android_log_buf_write = bytehook_hook_all(nullptr,
// "__android_log_buf_write",
// (void *) (android_log_buf_write_proxy),
// nullptr,
// nullptr);
stub_of_android_log_buf_write = bytehook_hook_partial(
hook_log_allow_filter,
nullptr,
nullptr,
"__android_log_buf_write",
reinterpret_cast<void *>(android_log_buf_write_proxy),
nullptr,
nullptr);
if (stub_of_android_log_buf_write == nullptr) {
return -1;
}
return 0;
}

void unhook_logcat() {
if (stub_of_android_log_buf_write != nullptr) {
bytehook_unhook(stub_of_android_log_buf_write);
stub_of_android_log_buf_write = nullptr;
}
}


extern "C"
JNIEXPORT void JNICALL
Java_com_wesley_android_c_toolkit_NativeHelper_00024Companion_unhookLogcat(JNIEnv *env,
jobject thiz) {
XLOGE(TAG, "unhook_logcat");
unhook_logcat();
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_wesley_android_c_toolkit_NativeHelper_00024Companion_hookLogcat(JNIEnv *env, jobject thiz,
jboolean console_log_open, jboolean save_to_file) {
XLOGE(TAG,"hook_logcat");
g_consoleLogOpen = (console_log_open == JNI_TRUE);
g_saveToFile = (save_to_file == JNI_TRUE);
if (hook_logcat() == 0) {
return JNI_TRUE;
}
return JNI_FALSE;
}

app 层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.wesley.android.c.toolkit

import android.util.Log
import com.bytedance.android.bytehook.ByteHook

class NativeHelper {

companion object {
init {
val ret = ByteHook.init()
Log.e("ByteHook", "init ret: $ret")
System.loadLibrary("toolkit")
}
external fun hookLogcat(consoleLogOpen: Boolean = true, saveToFile: Boolean = true): Boolean
external fun unhookLogcat()

}
}

完整代码参考:wesley666/AndroidToolkit: AndroidToolkit

运行 app 后可以看到日志 tag 加上了前缀 wesley,测试环境:小米 13,安卓 15。

参考

Android logd日志简介及典型案例分析

深入理解安卓日志系统(logcat / liblog / logd

Android帝国之日志系统–logd、logcat

Android logd日志原理 - Gityuan博客

Android日志是如何输出的:Log.d经历了什么


安卓如何收敛自身以及第三方sdk日志
https://iwesley.top/article/62681a5f/
作者
Wesley
发布于
2025年11月22日
许可协议