Android Logger日誌系統
目錄
目錄 前言 執行階段程式庫層日誌庫liblog 源碼分析 CC日誌寫入介面 Java日誌寫入介面 logcat工具分析 基礎資料結構
執行階段程式庫層日誌庫——liblog
Android系統在執行階段程式庫層提供了一個用來和Logger日誌驅動程式進行互動的日誌庫liblog。通過日誌庫liblog提供的介面,應用程式就可以方便地往Logger日誌驅動程式中寫入日誌記錄。
位於執行階段程式庫層的C/C++日誌寫入介面和位於應用程式架構層的Java日誌寫入介面都是通過liblog庫提供的日誌寫入介面來往Logger日誌驅動程式中寫入日誌記錄的。
源碼分析
日誌庫liblog提供的日誌記錄寫入介面實現在logd_write.c檔案中,它的源碼位置為:/system/core/liblog/logd_write.c。
根據寫入的日誌記錄的類型不同,這些函數可以劃分為三個類別,其中:
函數__android_log_assert、__android_log_vprint和__android_log_print用來寫入類型為main的日誌記錄。 函數__android_log_btwrite和__android_log_bwrite用來寫入類型為events的日誌記錄。 函數__android_log_buf_print可以寫入任意一種類型的日誌記錄。
無論寫入的是什麼類型的日誌記錄,它們最終都是通過調用函數write_to_log寫入到Logger日誌驅動程式中的。write_to_log是一個函數指標,它開始時指向函數__write_to_log_init。因此,當函數write_to_log第一次被調用時,實際上執行的是函數__write_to_log_init。函數__write_to_log_init主要是進行一些日誌庫初始化操作,接著函數指標write_to_log重新導向到函數__write_to_log_kernel或者__write_to_log_null中,這取決於能否成功地將日誌裝置檔案開啟。
源碼分析如上,源碼實現如下:
// 先聲明,後引用static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;// 一些定義在system/core/include/cutils/log.h中的宏typedef enum { LOG_ID_MAIN = 0, LOG_ID_RADIO = 1, LOG_ID_EVENTS = 2, LOG_ID_SYSTEM = 3, LOG_ID_MAX} log_id_t;#define LOGGER_LOG_MAIN log/main#define LOGGER_LOG_RADIO log/radio#define LOGGER_LOG_EVENTS log/events#define LOGGER_LOG_SYSTEM log/system// 真正函數執行的地方static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr){ if (write_to_log == __write_to_log_init) { log_fds[LOG_ID_MAIN] = log_open(/dev/LOGGER_LOG_MAIN, O_WRONLY); log_fds[LOG_ID_RADIO] = log_open(/dev/LOGGER_LOG_RADIO, O_WRONLY); log_fds[LOG_ID_EVENTS] = log_open(/dev/LOGGER_LOG_EVENTS, O_WRONLY); log_fds[LOG_ID_SYSTEM] = log_open(/dev/LOGGER_LOG_SYSTEM, O_WRONLY); // 修改write_to_log函數指標 write_to_log = __write_to_log_kernel; if (log_fds[LOG_ID_MAIN] < 0 || log_fds[LOG_ID_RADIO] < 0 || log_fds[LOG_ID_EVENTS] < 0) { log_close(log_fds[LOG_ID_MAIN]); log_close(log_fds[LOG_ID_RADIO]); log_close(log_fds[LOG_ID_EVENTS]); log_fds[LOG_ID_MAIN] = -1; log_fds[LOG_ID_RADIO] = -1; log_fds[LOG_ID_EVENTS] = -1; write_to_log = __write_to_log_null; } if (log_fds[LOG_ID_SYSTEM] < 0) { log_fds[LOG_ID_SYSTEM] = log_fds[LOG_ID_MAIN]; } } return write_to_log(log_id, vec, nr);}
通過上述代碼,我們在替換宏定義之後,是可以知道調用log_open開啟的分別是/dev/log/main、/dev/log/radio、/dev/log/events、/dev/log/system四個日誌裝置檔案。而宏log_open定義在system/core/liblog/logd_write.c中:
#if FAKE_LOG_DEVICE// 不需要care這裡,真正編譯的時候FAKE_LOG_DEVICE為0#else#define log_open(pathname, flags) open(pathname, (flags) | O_CLOEXEC)#define log_writev(filedes, vector, count) writev(filedes, vector, count)#define log_close(filedes) close(filedes)#endif
從上面代碼可以看出,log_open的真正實現是open函數。
回到最開始的地方,如果log_open的檔案都是ok的,那接下來會調用__write_to_log_kernel函數,源碼實現如下:
static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr){ ssize_t ret; int log_fd; if ((int)log_id < (int)LOG_ID_MAX) { log_fd = log_fds[(int)log_id]; } else { return EBADF; } do { ret = log_writev(log_fd, vec, nr); } while (ret < 0 && errno == EINTR); return ret;}
函數__write_to_log_kernel會根據參數log_id在全域數組log_fds中找到對應的日誌裝置檔案描述符,然後調用宏log_writev,即函數writev,把日誌記錄寫入到Logger日誌驅動程式中。
如果裝置檔案開啟失敗的話,write_to_log函數指標會被賦值為__write_to_log_kernel,這個函數其實什麼都沒有做,只是返回了個-1。所以就不貼源碼了。
最後,我們在分析一下__android_log_buf_write函數。因為C/C++日誌寫入介面和Java日誌寫入介面最終都是調用了這個函數完成了日誌的寫入。源碼如下:
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg){ struct iovec vec[3]; char tmp_tag[32]; if (! tag) tag = ; if ((bufID != LOG_ID_RADIO) && (!strcmp(tag, HTC_RIL) || (!strncmp(tag, RIL, 3)) || (!strncmp(tag, IMS, 3)) || !strcmp(tag, AT) || !strcmp(tag, GSM) || !strcmp(tag, STK) || !strcmp(tag, CDMA) || !strcmp(tag, PHONE) || !strcmp(tag, SMS))) { bufID = LOG_ID_RADIO; snprintf(tmp_tag, sizeof(tmp_tag), use-Rlog/RLOG-%s, tag); tag = tmp_tag; } vec[0].iov_base = (unsigned char *) &prio; vec[0].iov_len = 1; vec[1].iov_base = (void *) tag; vec[1].iov_len = strlen(tag) + 1; vec[2].iov_base = (void *) msg; vec[2].iov_len = strlen(msg) + 1; return write_to_log(log_id, vec, 3); }
在預設情況下,函數__android_log_write寫入的日誌記錄類型為main。然後,如果傳進來的日誌記錄的標請以”RIL”等標誌開頭,那麼它就會被認為是類型是radio的日誌記錄。
C/C++日誌寫入介面
Android系統提供了三組常用的C/C++宏來封裝日誌寫入介面。之所以這樣做,是為了方便開發同學進行日誌的開關控制,例如不在發布版本中開啟日誌。
三組宏定義分別為:
ALOGV,ALOGD,ALOGI,ALOGW和ALOGE。用來記錄類型為main的日誌。 SLOGV,SLOGD,SLOGI,SLOGW和SLOGE,用來寫入類型為system的日誌。 LOG_EVENT_INT,LOG_EVENT_LONG和LOG_EVENT_STRING,它們用來寫入類型為events的日誌記錄。
這些宏定義在system/core/include/log/log.h中,並且使用了一個LOG_NDEBUG的宏來作為日誌開關。
具體源碼如下:
// 日誌開關#ifndef LOG_NDEBUG#ifdef NDEBUG#define LOG_NDEBUG 1#else#define LOG_NDEBUG 0#endif#endif// 以ALOGE為例子#ifnded ALOGE#define ALOGE(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))#endif#ifndef ALOG#define ALOG(priority, tag, ...) LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)#endif#ifndef LOG_PRI#define LOG_PRI(priority, tag, ...) android_printLog(priority, tag, __VA_ARGS__)#endif# 回到了我們熟悉的__android_log_print函數#define android_printLog(prio, tag, fmt...) __android_log_print(prio, tag, fmt)
Java日誌寫入介面
Android系統在應用程式架構中定義了三個Java日誌寫入介面,它們分別是android.util.Log、android.util.Slog和android.util.EventLog,寫入的日誌記錄類型分別為main、system和events。
這裡主要分析android.util.log的實現。源碼如下:
public final class Log { /** * Priority constant for the println method; use Log.v. */ public static final int VERBOSE = 2; /** * Priority constant for the println method; use Log.d. */ public static final int DEBUG = 3; /** * Priority constant for the println method; use Log.i. */ public static final int INFO = 4; /** * Priority constant for the println method; use Log.w. */ public static final int WARN = 5; /** * Priority constant for the println method; use Log.e. */ public static final int ERROR = 6; /** * Priority constant for the println method. */ public static final int ASSERT = 7; private Log() { } /** * Send a {@link #VERBOSE} log message. * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int v(String tag, String msg) { return println_native(LOG_ID_MAIN, VERBOSE, tag, msg); } /** * Send a {@link #DEBUG} log message. * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int d(String tag, String msg) { return println_native(LOG_ID_MAIN, DEBUG, tag, msg); } /** * Send an {@link #INFO} log message. * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int i(String tag, String msg) { return println_native(LOG_ID_MAIN, INFO, tag, msg); } /** * Send a {@link #WARN} log message. * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int w(String tag, String msg) { return println_native(LOG_ID_MAIN, WARN, tag, msg); } /** * Send an {@link #ERROR} log message. * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int e(String tag, String msg) { return println_native(LOG_ID_MAIN, ERROR, tag, msg); } /** @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 native int println_native(int bufID, int priority, String tag, String msg);}
可以看到,JAVA應用程式層logger代碼是調用了JNI層的android_util_Log.cpp,源碼如下:
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); } 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 = -1; // 真正日誌寫入的函數(liblog.so中的函數) res = __android_log_buf_write(bufID, (android_LogPriority), tag, msg); return res;}
logcat工具分析
前面分析的將日誌記錄寫入到Logger日誌中的目的就是通過logcat工具將它們讀出來,然後給開發人員進行分析。
Logcat的用法很多,但是這裡主要從源碼的角度出發,分析Logcat的四個部分:
基礎資料結構。 初始化過程。 日誌記錄的讀取過程。 日誌記錄的輸出過程。
logcat的源碼位於:system/core/logcat.cpp中。
基礎資料結構
首先是定義在system/core/include/log/logger.h中的logger_entry,定義如下:
struct logger_entry { uint16_t len; uint16_t __pad; int32_t pid; int32_t tid; int32_t sec; int32_t nsec; char msg[0];};
結構體logger_entry用來描述一條日誌記錄。其中,char msg[0]指標用來記錄訊息實體內容。
然後,在看一下queued_entry_t結構體,源碼如下:
struct queued_entry_t {};