Android各类日志如何动态打开
1. ProtoLog如何动态打开(android R开始引入)
类似源码里面的 ProtoLog.v
,在userdebug
版本可以动态打开
ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation continue waiting for draw in %s", w);
具体可以参考frameworks/base/tools/protologtool/README.md
如类似下面的ProtoLog
ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);
将会给ProtoLogTool
转换成
if (ProtoLogImpl.isEnabled(GROUP_NAME)) {
int protoLogParam0 = value1;
String protoLogParam1 = String.valueOf(value2);
ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, 0b0100, "Format string %d %s or null", protoLogParam0, protoLogParam1);
}
ps:
它有两种log形式:
=> 第一种写入到db中
举例
adb shell
$ cmd window logging enable WM_DEBUG_ORIENTATION
$ cmd window logging start
开始log的输出
=>Start logging to /data/misc/wmtrace/wm_log.pb.
$ cmd window logging stop
停止log的输出
Stop logging to /data/misc/wmtrace/wm_log.pb. Waiting for log to flush. Log written to /data/misc/wmtrace/wm_log.pb.
=> 第二种输出到logcat
举例:
这个是直接保存在logcat中
$ cmd window logging enable-text WM_DEBUG_ORIENTATION
使用logcat就可以直接看得到日志输出
$ adb logcat -b all | egrep -i Orientation
2. 关于代码里面写的isLoggable,开关如何打开(很早就有了)
例如NotificationManagerService
的DBG
如何默认开呢?
public class NotificationManagerService extends AbsNotificationManagerService {
public static final String TAG = "NotificationService";
public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
方法是:设置系统属性persist.log.tag.NotificationService
为 V
,然后重启手机。即可生效(user
版本一样可以生效)
这里用的是liblog
自带的根据tag
过滤是否输出日志的功能
Log.isLoggable
其实调用的是native
的方法
//frameworks/base/core/java/android/util/Log.java
public static native boolean isLoggable(@Nullable String tag, @Level int level);
jni
调用isLoggable
相当于android_util_Log_isLoggable
//frameworks/base/core/jni/android_util_Log.cpp
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
//...
};
//实现还是本文件的isLoggable
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
if (tag == NULL) {
return false;
}
const char* chars = env->GetStringUTFChars(tag, NULL);
if (!chars) {
return false;
}
jboolean result = isLoggable(chars, level);
env->ReleaseStringUTFChars(tag, chars);
return result;
}
//具体判断是否可以输出日志是在__android_log_is_loggable,这个实现是在liblog里面
static jboolean isLoggable(const char* tag, jint level) {
return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO);
}
__android_log_is_loggable
的实现是liblog
的__android_log_is_loggable_len
///system/core/liblog/properties.cpp
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);
}
//__android_log_is_loggable_len根据__android_log_is_loggable_len返回的tag对应允许日志等级输出的范围,和传入的优先级prio(这里是上面的Log.DEBUG)
//判定是否运行输出
int __android_log_is_loggable_len(int prio, const char* tag, size_t len, int default_prio) {
int minimum_log_priority = __android_log_get_minimum_priority();
int property_log_level = __android_log_level(tag, len);//根据tag判断,允许输出的日志等级范围
//判断当前prio的优先级是否可以输出
if (property_log_level >= 0 && minimum_log_priority != ANDROID_LOG_DEFAULT) {
return prio >= std::min(property_log_level, minimum_log_priority);
} else if (property_log_level >= 0) {
return prio >= property_log_level;
} else if (minimum_log_priority != ANDROID_LOG_DEFAULT) {
return prio >= minimum_log_priority;
} else {
return prio >= default_prio;
}
}
//取出类似persist.log.tag.<tag>的内容,看这个tag允许输出的level范围
static int __android_log_level(const char* tag, size_t len) {
/* sizeof() is used on this array below */
static const char log_namespace[] = "persist.log.tag.";
static const size_t base_offset = 8; /* skip "persist." */
if (tag == nullptr || len == 0) {
auto& tag_string = GetDefaultTag();
tag = tag_string.c_str();
len = tag_string.size();
}
/* sizeof(log_namespace) = strlen(log_namespace) + 1 */
char key[sizeof(log_namespace) + len];
char* kp;
size_t i;
char c = 0;
/*
* Single layer cache of four properties. Priorities are:
* log.tag.<tag>
* persist.log.tag.<tag>
* log.tag
* persist.log.tag
* Where the missing tag matches all tags and becomes the
* system global default. We do not support ro.log.tag* .
*/
static char* last_tag;
static size_t last_tag_len;
static uint32_t global_serial;
/* some compilers erroneously see uninitialized use. !not_locked */
uint32_t current_global_serial = 0;
static struct cache_char tag_cache[2];
static struct cache_char global_cache[2];
int change_detected;
int global_change_detected;
int not_locked;
strcpy(key, log_namespace);
global_change_detected = change_detected = not_locked = lock();
if (!not_locked) {
/*
* check all known serial numbers to changes.
*/
for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
if (check_cache(&tag_cache[i].cache)) {
change_detected = 1;
}
}
for (i = 0; i < (sizeof(global_cache) / sizeof(global_cache[0])); ++i) {
if (check_cache(&global_cache[i].cache)) {
global_change_detected = 1;
}
}
current_global_serial = __system_property_area_serial();
if (current_global_serial != global_serial) {
change_detected = 1;
global_change_detected = 1;
}
}
if (len) {
int local_change_detected = change_detected;
if (!not_locked) {
if (!last_tag || !last_tag[0] || (last_tag[0] != tag[0]) ||
strncmp(last_tag + 1, tag + 1, last_tag_len - 1)) {
/* invalidate log.tag.<tag> cache */
for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
tag_cache[i].cache.pinfo = NULL;
tag_cache[i].c = '\0';
}
if (last_tag) last_tag[0] = '\0';
local_change_detected = 1;
}
if (!last_tag || !last_tag[0]) {
if (!last_tag) {
last_tag = static_cast<char*>(calloc(1, len + 1));
last_tag_len = 0;
if (last_tag) last_tag_len = len + 1;
} else if (len >= last_tag_len) {
last_tag = static_cast<char*>(realloc(last_tag, len + 1));
last_tag_len = 0;
if (last_tag) last_tag_len = len + 1;
}
if (last_tag) {
strncpy(last_tag, tag, len);
last_tag[len] = '\0';
}
}
}
strncpy(key + sizeof(log_namespace) - 1, tag, len);
key[sizeof(log_namespace) - 1 + len] = '\0';
kp = key;
for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
struct cache_char* cache = &tag_cache[i];
struct cache_char temp_cache;
if (not_locked) {
temp_cache.cache.pinfo = NULL;
temp_cache.c = '\0';
cache = &temp_cache;
}
if (local_change_detected) {
refresh_cache(cache, kp);
}
if (cache->c) {
c = cache->c;
break;
}
kp = key + base_offset;
}
}
switch (toupper(c)) { /* if invalid, resort to global */
case 'V':
case 'D':
case 'I':
case 'W':
case 'E':
case 'F': /* Not officially supported */
case 'A':
case 'S':
case BOOLEAN_FALSE: /* Not officially supported */
break;
default:
/* clear '.' after log.tag */
key[sizeof(log_namespace) - 2] = '\0';
kp = key;
for (i = 0; i < (sizeof(global_cache) / sizeof(global_cache[0])); ++i) {
struct cache_char* cache = &global_cache[i];
struct cache_char temp_cache;
if (not_locked) {
temp_cache = *cache;
if (temp_cache.cache.pinfo != cache->cache.pinfo) { /* check atomic */
temp_cache.cache.pinfo = NULL;
temp_cache.c = '\0';
}
cache = &temp_cache;
}
if (global_change_detected) {
refresh_cache(cache, kp);
}
if (cache->c) {
c = cache->c;
break;
}
kp = key + base_offset;
}
break;
}
if (!not_locked) {
global_serial = current_global_serial;
unlock();
}
switch (toupper(c)) {
/* clang-format off */
case 'V': return ANDROID_LOG_VERBOSE;
case 'D': return ANDROID_LOG_DEBUG;
case 'I': return ANDROID_LOG_INFO;
case 'W': return ANDROID_LOG_WARN;
case 'E': return ANDROID_LOG_ERROR;
case 'F': /* FALLTHRU */ /* Not officially supported */
case 'A': return ANDROID_LOG_FATAL;
case BOOLEAN_FALSE: /* FALLTHRU */ /* Not Officially supported */
case 'S': return ANDROID_LOG_SILENT;
/* clang-format on */
}
return -1;
}
3. ams wms等日志的动态打开
默认源码不支持这类动态日志,需要我们自己加入代码
用反射拿到带DEBUG_
的一些调试变量
Field[] fields_am = ActivityManagerDebugConfig.class.getDeclaredFields();
Field[] fields_atm = ActivityTaskManagerDebugConfig.class.getDeclaredFields();
Field[] fields_thread = ActivityThread.class.getDeclaredFields();
Field[] fields = WindowManagerDebugConfig.class.getDeclaredFields();
Field[] fieldsPolicy = PhoneWindowManager.class.getDeclaredFields();
找到DEBUG_
相关的变量,类似于ActivityThread.DEBUG_BROADCAST
、ActivityManagerDebugConfig.DEBUG_BROADCAST
,并将其默认值记录下来,此处是先放入debugValue
int bitLocation = 0;
long debugValue = 0;
for (int i = 0; i < fields_thread.length; ++i) {
fieldName = fields_thread[i].getName();
if (fieldName == null) continue;
if (fieldName.startsWith("DEBUG_") || fieldName.equals("localLOGV")) {
try {
fields_thread[i].setAccessible(true);
if (fields_thread[i].getBoolean(null)) {
debugValue = (debugValue | (1 << bitLocation));//取得原来每个调试变量DEBUG_原有值,注意long数组溢出,一般都是够的
}
bitLocation++;
} catch (IllegalAccessException e) {
pw.println("enableAmsLog exception4:" + e);
}
}
}
调用触发,可以参考原生的 adb shell dumpsys activity p
自己设置一个 adb shell dumpsys debuglog amslog enable DEBUG_BROADCAST
进行参数识别,由于除了系统进程,部分还和app进程相关,保存设置到自定义的系统属性里面persist.sys.debug.ams.log
for (int i = 0; i < fields_thread.length; ++i) {
fieldName = fields_thread[i].getName();
if (fieldName == null) continue;
try {
if (fieldName.startsWith("DEBUG_") || fieldName.equals("localLOGV")) {
if (setAll || fieldName.equals(cmd)) {//cmd就是DEBUG_ABC,或者all(匹配all后设置setAll = true)
isChange = true;
fields_thread[i].setAccessible(true);
fields_thread[i].setBoolean(null, isEnable);//如果传进来的参数是enable,则isEnable = true; disable,则isEnable = false
if (isEnable) {
debugValue = (debugValue | (1 << bitLocation));//根据设定值改变原有调试开关
} else {
debugValue = (debugValue & (~(1 << bitLocation)));
}
pw.println(String.format(" ActivityThread.%s = %b", fieldName, fields_thread[i].getBoolean(null)));
if (!setAll) {
break;
}
}
bitLocation++;
}
} catch (IllegalAccessException e) {
pw.println("enable exception:" + e);
}
}
//将debug开关用系统属性保存起来
String debugHexValue = "0x" + Long.toHexString(debugValue);
SystemProperties.set("persist.sys.debug.ams.log", debugHexValue);
遍历mLruProcesses
分发到android
的每个进程里面去
//参考伪代码
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = mLruProcesses.get(i);
if (r != null && r.thread != null) {
r.thread.updateDebugLog(key);
}
}
//frameworks/base/core/java/android/app/ActivityThread.java
public final void updateDebugLog(int func) {
DebugLogManager.updateDebugLog(func);
}
//新建一个DebugLogManager
public static void updateDebugLog(int func) {
try {
String fieldName = "";
Field[] fields = null;
int bitLocation = 0;
long setDebugValue = -1;
switch (func) {
case FUNC_ACTIVITY_LOG:
setDebugValue = SystemProperties.getLong("persist.sys.debug.ams.log", -1);//将上面设置的debugValue取出来
if (setDebugValue >= 0) {
fields = ActivityThread.class.getDeclaredFields();
bitLocation = 0;
for (int i = 0; i < fields.length; ++i) {
fieldName = fields[i].getName();
if (fieldName == null) continue;//按照原有顺序一个个设置进去
if (fieldName.startsWith("DEBUG_") || fieldName.equals("localLOGV")) {
fields[i].setAccessible(true);
if ((setDebugValue & (1 << bitLocation)) == 0) {
fields[i].setBoolean(null, false);
} else {
fields[i].setBoolean(null, true);
}
bitLocation++;
}
}
}
break;
类似于ActivityThread.DEBUG_BROADCAST
、ActivityManagerDebugConfig.DEBUG_BROADCAST
的动态设置将不是问题
4. 其它动态日志
其它各个模块也有很多方式可以做动态日志,这里只是一个抛砖引玉,注意做动态日志的时候不要影响到性能了,毕竟真实用户是用不到的