1. 程式人生 > >android log

android log

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 root
adb remount
adb push local.prop /data/local.prop
adb shell chmod 644 /data/local.prop
adb shell chown root.root /data/local.prop
adb reboot

local.prop檔案內容如下:

log.tag.Telecom=VERBOSE
log.tag.RIL-SIM=VERBOSE
log.tag.SIMRecords=VERBOSE
log.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