现象回顾
一次修改bug过程中发现,com.android.bluetooth这个app一开始能正常输出debug、verbose级别的日志,但运行一会儿之后就再也看不到debug、verbose级别的日志了,只能看见info级别的日志。然后我就写了一个demo app分别用Log.d和Log.v打印日志,发现logcat能正常输出demo的日志。
于是我就猜测,是不是有什么接口可以设置app的logcat日志输出级别。
原理分析
于是乎,循着打印log的源码分析:
一般打印日志调用Log.d
bash
Log.d(TAG, "onCreate: ");
Log.d实现在frameworks/base/core/java/android/util/Log.java
bash
public static final int DEBUG = 3;
public static int d(@Nullable String tag, @NonNull String msg) {
return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
public static native int println_native(int bufID, int priority, String tag, String msg);
Log.d接着调用println_native,println_native是一个jni方法,实现在frameworks/base/core/jni/android_util_Log.cpp
bash
/*
* In class android.util.Log:
* public static native int println_native(int buffer, int priority, String tag, String msg)
*/
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
const char* tag = NULL;
const char* msg = NULL;
if (msgObj == NULL) {
jniThrowNullPointerException(env, "println needs a message");
return -1;
}
if (bufID < 0 || bufID >= LOG_ID_MAX) {
jniThrowNullPointerException(env, "bad bufID");
return -1;
}
if (tagObj != NULL)
tag = env->GetStringUTFChars(tagObj, NULL);
msg = env->GetStringUTFChars(msgObj, NULL);
int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
if (tag != NULL)
env->ReleaseStringUTFChars(tagObj, tag);
env->ReleaseStringUTFChars(msgObj, msg);
return res;
}
android_util_Log_println_native调用__android_log_buf_write,__android_log_buf_write实现在system/logging/liblog/logger_write.cpp
bash
int __android_log_buf_write(int log_id, int prio, const char* tag, const char* msg) {
ErrnoRestorer errno_restorer;
if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
return -EPERM;
}
__android_log_message log_message = {
sizeof(__android_log_message), log_id, prio, tag, nullptr, 0, msg};
__android_log_write_log_message(&log_message);
return 1;
}
__android_log_is_loggable判断能否打印,实现在system/logging/liblog/properties.cpp
bash
int __android_log_is_loggable(int prio, const char* tag, int default_prio) {
auto len = tag ? strlen(tag) : 0;
return __android_log_is_loggable_len(prio, tag, len, default_prio);
}
int __android_log_is_loggable_len(int prio, const char*, size_t, int def) {
return __android_log_is_loggable(prio, nullptr, def);
}
int __android_log_is_loggable(int prio, const char*, int) {
int minimum_priority = __android_log_get_minimum_priority();
if (minimum_priority == ANDROID_LOG_DEFAULT) {
minimum_priority = ANDROID_LOG_INFO;
}
return prio >= minimum_priority;
}
经过层层调用,发现能否打印取决于是否prio >= minimum_priority,prio是调用Log.d时传过来的DEBUG=3,minimum_priority取自__android_log_get_minimum_priority方法,实现在system/logging/liblog/logger_write.cpp
bash
static std::atomic_int32_t minimum_log_priority = ANDROID_LOG_DEFAULT;
int32_t __android_log_set_minimum_priority(int32_t priority) {
return minimum_log_priority.exchange(priority, std::memory_order_relaxed);
}
int32_t __android_log_get_minimum_priority() {
return minimum_log_priority;
}
ANDROID_LOG_DEFAULT 定义在system/logging/liblog/include/android/log.h
bash
/**
* Android log priority values, in increasing order of priority.
*/
typedef enum android_LogPriority {
/** For internal use only. */
ANDROID_LOG_UNKNOWN = 0,
/** The default priority, for internal use only. */
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
/** Verbose logging. Should typically be disabled for a release apk. */
ANDROID_LOG_VERBOSE,
/** Debug logging. Should typically be disabled for a release apk. */
ANDROID_LOG_DEBUG,
/** Informational logging. Should typically be disabled for a release apk. */
ANDROID_LOG_INFO,
/** Warning logging. For use with recoverable failures. */
ANDROID_LOG_WARN,
/** Error logging. For use with unrecoverable failures. */
ANDROID_LOG_ERROR,
/** Fatal logging. For use when aborting. */
ANDROID_LOG_FATAL,
/** For internal use only. */
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
} android_LogPriority;
c语言的枚举值从第一个开始依次递增,所以ANDROID_LOG_DEFAULT=1,minimum_log_priority默认是ANDROID_LOG_DEFAULT,Log.d传过来的DEBUG=3>ANDROID_LOG_DEFAULT=1,所以默认Log.d是能打印的。
但有个接口__android_log_set_minimum_priority可以改变minimum_log_priority的值,看到这,是不是发现了什么。
猜想,这个就是设置单个应用的默认日志输出级别接口。
顺着猜想看看com.android.bluetooth是不是哪里调用了这个接口。
bluetooth里会启动AdapterService,实现在packages/modules/Bluetooth/android/app/src/com/android/bluetooth/btservice/AdapterService.java,AdapterService的init方法会加载bluetooth_jni库
bash
public class AdapterService extends Service {
@SuppressLint("AndroidFrameworkRequiresPermission")
private void init() {
Log.d(TAG, "init()");
...
if (Utils.isInstrumentationTestMode()) {
Log.w(TAG, "This Bluetooth App is instrumented. ** Skip loading the native **");
} else {
Log.d(TAG, "Loading JNI Library");
System.loadLibrary("bluetooth_jni");
}
bluetooth_jni库的接口实现packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
bash
/*
* JNI Initialization
*/
jint JNI_OnLoad(JavaVM* jvm, void* /* reserved */) {
/* Set the default logging level for the process using the tag
* "log.tag.bluetooth" and/or "persist.log.tag.bluetooth" via the android
* logging framework.
*/
const char* stack_default_log_tag = "bluetooth";
int default_prio = ANDROID_LOG_INFO;
if (__android_log_is_loggable(ANDROID_LOG_VERBOSE, stack_default_log_tag, default_prio)) {
__android_log_set_minimum_priority(ANDROID_LOG_VERBOSE);
log::info("Set stack default log level to 'VERBOSE'");
} else if (__android_log_is_loggable(ANDROID_LOG_DEBUG, stack_default_log_tag, default_prio)) {
__android_log_set_minimum_priority(ANDROID_LOG_DEBUG);
log::info("Set stack default log level to 'DEBUG'");
} else if (__android_log_is_loggable(ANDROID_LOG_INFO, stack_default_log_tag, default_prio)) {
__android_log_set_minimum_priority(ANDROID_LOG_INFO);
log::info("Set stack default log level to 'INFO'");
} else if (__android_log_is_loggable(ANDROID_LOG_WARN, stack_default_log_tag, default_prio)) {
__android_log_set_minimum_priority(ANDROID_LOG_WARN);
log::info("Set stack default log level to 'WARN'");
} else if (__android_log_is_loggable(ANDROID_LOG_ERROR, stack_default_log_tag, default_prio)) {
__android_log_set_minimum_priority(ANDROID_LOG_ERROR);
log::info("Set stack default log level to 'ERROR'");
}
JNI_OnLoad里调用了上面提到的__android_log_set_minimum_priority方法,当加载so库的时候,会先调用JNI_OnLoad方法。观察bluetooth启动日志也发现了相关信息。

也就是说,com.android.bluetooth正是通过调用__android_log_set_minimum_priority改变了默认的日志输出级别,所以才出现了一开始能看到Log.d的日志后面却只能看到Log.i及以上的日志的现象。
设置单个应用日志输出级别
假设我们需要设置自己app的默认日志输出级别,参考com.android.bluetooth的做法:
- app启动时加载一个自定义so库
- 重写jni方法的JNI_OnLoad方法
- JNI_OnLoad里调用__android_log_set_minimum_priority改变默认日志级别
补充
- 设置系统默认日志输出级别:adb shell setprop log.tag v,重启后无效
- 设置系统默认日志输出级别:adb shell setprop persist.log.tag d,重启后也有效
- 设置单个tag(如hai)日志输出级别:adb shell setprop log.tag.hai v,重启后无效
- 设置单个tag(如hai)日志输出级别:adb shell setprop persist.log.tag.hai d,重启后也有效