android log

来源:互联网 发布:mobiscroll.js官网 编辑:程序博客网 时间:2024/06/14 15:45

Android log的重要性不言而喻,是我们分析问题的依据,理解代码的好助手。
本文从以下两方面对log做一些简单总结:
1. Log分类
2. Log打印控制


1. Log分类

Android 打印的log分以下几类:
1. main log
2. sytem log
3. radio log
4. event log
5. kernel log
6. crash log
7. security log
这些log都是system/core/liblog/Logger_write.c去打印的; 分类在Log_id.h中是定义好了的:

typedef enum log_id {  LOG_ID_MIN = 0,  LOG_ID_MAIN = 0,  LOG_ID_RADIO = 1,  LOG_ID_EVENTS = 2,  LOG_ID_SYSTEM = 3,  LOG_ID_CRASH = 4,  LOG_ID_SECURITY = 5,  LOG_ID_KERNEL = 6, /* place last, third-parties can not use it */  LOG_ID_MAX} log_id_t;#endif

对我AP侧的研发来说, 经常要打印的log也就是前4种; 打印log所调用的类是android.util.Log。
android.util.Log.java内对常用的4中log也做了定义:

    /** @hide */ public static final int LOG_ID_MAIN = 0;    /** @hide */ public static final int LOG_ID_RADIO = 1;    /** @hide */ public static final int LOG_ID_EVENTS = 2;    /** @hide */ public static final int LOG_ID_SYSTEM = 3;    /** @hide */ public static final int LOG_ID_CRASH = 4;

如果要打印main log,那么直接调用android.util.Log中的方法即可。
如果要打印system log,可以调用android.util.Slog。
如果要打印event log, 可以调用android.utile.Eventlog。
如果要打印radio log,可以调用android.telephony.Rlog。
上面几个类只是为大家写好了要打印的类别,当然实际开发中是经常要包装自定义的工具log类的,以便可以更好的控制打印。


2. Log打印控制——可以使用adb 命令设置property的方式来控制log的打印

log的打印是要消耗资源的,而且有些log中包含一些敏感信息,所以有必要控制log的打印。

工作中经常见到如下的log打印控制方式,DBG可能是true或false这种比较简单的控制;也可能是通过property等控制。
这一般是我们自己控制log的方式,没什么好说的。

......if (DBG) {    //log打印}......

Android源生也提供了log的控制机制,了解这个机制之后我们可以通过使用adb 命令设置property的方式来控制log的打印,下面来说说这个机制。

不管是android.telephonu.Rlog还是android.util.Slog其实都是通过android.util.log的native方法 Log.println_native来打印log的。
对应的jni文件是android_util_Log.cpp。android.util.log还提供了native方法isLoggable(String tag, int level),这个方法可以用来判断当前level的log是否可以打印。isLoggable方法在android_util_Log.cpp中对应的方法是android_util_Log_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_util_Log_isLoggable调用了函数isLoggable,这个函数很简单:

static jboolean isLoggable(const char* tag, jint level) {    return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO);}

__android_log_is_loggable是在properties.c中定义的(函数__android_log_level(const char* tag, size_t len, int default_prio)在Android N上定义在/system/core/liblog/log_is_loggable.c中,Android O放到了properties.c文件中)。

LIBLOG_ABI_PUBLIC int __android_log_is_loggable(int prio, const char* tag,                                                int default_prio) {  int logLevel =      __android_log_level(tag, (tag && *tag) ? strlen(tag) : 0, default_prio);  return logLevel >= 0 && prio >= logLevel;}

这个函数通过调用__android_log_level获取tag所设置的level,默认值是ANDROID_LOG_INFO。
下面看看__android_log_level是如何获取指定tag所对应的level的。

static int __android_log_level(const char* tag, size_t len, int default_prio) {  /* sizeof() is used on this array below */  static const char log_namespace[] = "persist.log.tag.";//property的前缀,即查找这种形式的property。  static const size_t base_offset = 8; /* skip "persist." */  /* calculate the size of our key temporary buffer */  const size_t taglen = tag ? len : 0;  /* sizeof(log_namespace) = strlen(log_namespace) + 1 */  char key[sizeof(log_namespace) + taglen];//根据key的长度,可以猜到会用于存储完整的property,e.x. persist.log.tag.TelecomFramework  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;//静态变量,所以不不会因为函数调用结束而丢失; 用于记录上次查询的tag。  static size_t last_tag_len;//静态变量;用于记录上次查询tag的长度。  static uint32_t global_serial;//静态变量; 这个变量应该用于判断property是否发生了改变。  /* 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();//成功时返回0,否则返回非0值。  //下面这段代码用于判断property是否发生了改变。  if (!not_locked) {//成功lock    /*     *  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;    }  }  //下面这段代码用于判断本次请求的tag是否和上次记录tag不同,以便决定是否需要重新查找。  if (taglen) {    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)) {//last_tag为null,说明是初次查找        /* 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';//置成空字符,这样下面的if语句块便可以进入执行        local_change_detected = 1;      }      if (!last_tag || !last_tag[0]) {//初次查找或者tag和上次记录tag不同的情况        if (!last_tag) {          last_tag = calloc(1, len + 1);          last_tag_len = 0;          if (last_tag) last_tag_len = len + 1;        } else if (len >= last_tag_len) {          last_tag = 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的值是"persist.log.tag.*"    key[sizeof(log_namespace) - 1 + len] = '\0';    //下面就开始查找和tag具体相关的property:"persist.log.tag.*"或者"log.tag.*"    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;//    }  }  //下面会在查找"persist.log", 即全局配置。  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';//现在key的值是"persist.log"      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();  }  //下面的代码跟property的值,返回对应的log level。  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 -1; /* ANDROID_LOG_SUPPRESS */    /* clang-format on */  }  return default_prio;//如果没有查找到对应的property,那么返回默认值。}

上面的函数完成的逻辑,真正执行property查找的是函数refresh_cache(struct cache_char* cache, const char* key),这个函数就不说了,比较简单。

我们平时在工作中,为了打印一些log,我们常用下面的方式向手机/data目录下push local.prop.

adb rootadb remountadb push local.prop /data/local.propadb shell chmod 644 /data/local.propadb shell chown root.root /data/local.propadb reboot

local.prop文件内容如下:

log.tag.Telecom=VERBOSElog.tag.RIL-SIM=VERBOSElog.tag.SIMRecords=VERBOSElog.tag.DCT=VERBOSE

当手机重启时,init会加载/data/local.prop, 文件中的内容会被加载为property。所以当我们的log语句执行时,__android_log_level可以查到到相应的property,进而获取我们设置的level。
其实push文件有些繁琐,根据上面的分析,我们可以直接用adb 命令在设置property控制log输出。
可以使用的property name是:

  • log.tag.*
  • persist.log.tag.*
  • persist.log(优先级比前面两个低,所以如果有设置前面两个, 后面这个是不生效的)

[例子1]
输出以Telecom为tag的所有log,可以用下面的方法。
adb shell setprop log.tag.Telecom V 或
adb shell setprop persist.log.tag.Telecom V

[例子2]
下面的命令可以输出所以的log,不过不建议使用,因为log太多可能会给查看带来不便。
adb shell setprop persist.log V

原创粉丝点击