Android各类日志如何动态打开

  • 1. ProtoLog如何动态打开(android R开始引入)
  • 2. 关于代码里面写的isLoggable,开关如何打开(很早就有了)
  • 3. ams wms等日志的动态打开
  • 4. 其它动态日志


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,开关如何打开(很早就有了)

例如NotificationManagerServiceDBG如何默认开呢?

public class NotificationManagerService extends AbsNotificationManagerService {
    public static final String TAG = "NotificationService";
    public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);

方法是:设置系统属性persist.log.tag.NotificationServiceV,然后重启手机。即可生效(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_BROADCASTActivityManagerDebugConfig.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_BROADCASTActivityManagerDebugConfig.DEBUG_BROADCAST的动态设置将不是问题

4. 其它动态日志

其它各个模块也有很多方式可以做动态日志,这里只是一个抛砖引玉,注意做动态日志的时候不要影响到性能了,毕竟真实用户是用不到的