Logcat工具內置在Android系統中,可以在主機上通過adb logcat命令來查看模擬機上日志信息。Logcat工具的用法很豐富,因此,源代碼也比較多,本文並不打算完整地介紹整個Logcat工具的源代碼,主要是介紹Logcat讀取日志的主線,即從打開日志設備文件到讀取日志設備文件的日志記錄到輸出日志記錄的主要過程,希望能起到一個拋磚引玉的作用。
Logcat工具源代碼位於system/core/logcat目錄下,隻有一個源代碼文件logcat.cpp,編譯後生成的可執行文件位於out/target/product/generic/system/bin目錄下,在模擬機中,可以在/system/bin目錄下看到logcat工具。下面我們就分段來閱讀logcat.cpp源代碼文件。
一. Logcat工具的相關數據結構。
這些數據結構是用來保存從日志設備文件讀出來的日志記錄:
[color=amily:Consolas,'Courier]
[cpp]
struct queued_entry_t {
union {
unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1] __attribute__((aligned(4)));
struct logger_entry entry __attribute__((aligned(4)));
};
queued_entry_t* next;
queued_entry_t() {
next = NULL;
}
};
struct log_device_t {
char* device;
bool binary;
int fd;
bool printed;
char label;
queued_entry_t* queue;
log_device_t* next;
log_device_t(char* d, bool b, char l) {
device = d;
binary = b;
label = l;
queue = NULL;
next = NULL;
printed = false;
}
void enqueue(queued_entry_t* entry) {
if (this->queue == NULL) {
this->queue = entry;
} else {
queued_entry_t** e = &this->queue;
while (*e && cmp(entry, *e) >= 0) {
e = &((*e)->next);
}
entry->next = *e;
*e = entry;
}
}
};
其中,宏LOGGER_ENTRY_MAX_LEN和struct logger_entry定義在system/core/include/cutils/logger.h文件中,在Android應用程序框架層和系統運行庫層日志系統源代碼分析一文有提到,為瞭方便描述,這裡列出這個宏和結構體的定義:
[color=amily:Consolas,'Courier]
[cpp]
struct logger_entry {
__u16 len; /* length of the payload */
__u16 __pad; /* no matter what, we get 2 bytes of padding */
__s32 pid; /* generating process's pid */
__s32 tid; /* generating process's tid */
__s32 sec; /* seconds since Epoch */
__s32 nsec; /* nanoseconds */
char msg[0]; /* the entry's payload */
};
#define LOGGER_ENTRY_MAX_LEN (4*1024)
從結構體struct queued_entry_t和struct log_device_t的定義可以看出,每一個log_device_t都包含有一個queued_entry_t隊列,queued_entry_t就是對應從日志設備文件讀取出來的一條日志記錄瞭,而log_device_t則是對應一個日志設備文件上下文。在Android日志系統驅動程序Logger源代碼分析一文中,我們曾提到,Android日志系統有三個日志設備文件,分別是/dev/log/main、/dev/log/events和/dev/log/radio。
每個日志設備上下文通過其next成員指針連接起來,每個設備文件上下文的日志記錄也是通過next指針連接起來。日志記錄隊例是按時間戳從小到大排列的,這個log_device_t::enqueue函數可以看出,當要插入一條日志記錄的時候,先隊列頭開始查找,直到找到一個時間戳比當前要插入的日志記錄的時間戳大的日志記錄的位置,然後插入當前日志記錄。比較函數cmp的定義如下:
[color=amily:Consolas,'Courier]
[cpp]
static int cmp(queued_entry_t* a, queued_entry_t* b) {
int n = a->entry.sec – b->entry.sec;
if (n != 0) {
return n;
}
return a->entry.nsec – b->entry.nsec;
}
為什麼日志記錄要按照時間戳從小到大排序呢?原來,Logcat在使用時,可以指定一個參數-t <count>,可以指定隻顯示最新count條記錄,超過count的記錄將被丟棄,在這裡的實現中,就是要把排在隊列前面的多餘日記記錄丟棄瞭,因為排在前面的日志記錄是最舊的,默認是顯示所有的日志記錄。在下面的代碼中,我們還會繼續分析這個過程。
二. 打開日志設備文件。
Logcat工具的入口函數main,打開日志設備文件和一些初始化的工作也是在這裡進行。main函數的內容也比較多,前面的邏輯都是解析命令行參數。這裡假設我們使用logcat工具時,不帶任何參數。這不會影響我們分析logcat讀取日志的主線,有興趣的讀取可以自行分析解析命令行參數的邏輯。
分析完命令行參數以後,就開始要創建日志設備文件上下文結構體struct log_device_t瞭:
[color=amily:Consolas,'Courier]
[cpp]
if (!devices) {
devices = new log_device_t(strdup("/dev/"LOGGER_LOG_MAIN), false, 'm');
android::g_devCount = 1;
int accessmode =
(mode & O_RDONLY) ? R_OK : 0
| (mode & O_WRONLY) ? W_OK : 0;
// only add this if it's available
if (0 == access("/dev/"LOGGER_LOG_SYSTEM, accessmode)) {
devices->next = new log_device_t(strdup("/dev/"LOGGER_LOG_SYSTEM), false, 's');
android::g_devCount++;
}
}
由於我們假設使用logcat時,不帶任何命令行參數,這裡的devices變量為NULL,因此,就會默認創建/dev/log/main設備上下文結構體,如果存在/dev/log/system設備文件,也會一並創建。宏LOGGER_LOG_MAIN和LOGGER_LOG_SYSTEM也是定義在system/core/include/cutils/logger.h文件中:
[color=amily:Consolas,'Courier]
[cpp]
#define LOGGER_LOG_MAIN "log/main"
#define LOGGER_LOG_SYSTEM "log/system"
我們在Android日志系統驅動程序Logger源代碼分析一文中看到,在Android日志系統驅動程序Logger中,默認是不創建/dev/log/system設備文件的。
往下看,調用setupOutput()函數來初始化輸出文件:
[color=amily:Consolas,'Courier]
[cpp]
android::setupOutput();
setupOutput()函數定義如下:
[color=amily:Consolas,'Courier]
[cpp] view plaincopy
static void setupOutput()
{
if (g_outputFileName == NULL) {
g_outFD = STDOUT_FILENO;
} else {
struct stat statbuf;
g_outFD = openLogFile (g_outputFileName);
if (g_outFD < 0) {
perror ("couldn't open output file");
exit(-1);
}
fstat(g_outFD, &statbuf);
g_outByteCount = statbuf.st_size;
}
}
如果我們在執行logcat命令時,指定瞭-f <filename>選項,日志內容就輸出到filename文件中,否則,就輸出到標準輸出控制臺去瞭。
再接下來,就是打開日志設備文件瞭:
[color=amily:Consolas,'Courier]
[cpp]
dev = devices;
while (dev) {
dev->fd = open(dev->device, mode);
if (dev->fd < 0) {
fprintf(stderr, "Unable to open log device '%s': %s\n",
dev->device, strerror(errno));
exit(EXIT_FAILURE);
}
if (clearLog) {
int ret;
ret = android::clearLog(dev->fd);
if (ret) {
perror("ioctl");
exit(EXIT_FAILURE);
}
}
if (getLogSize) {
int size, readable;
size = android::getLogSize(dev->fd);
if (size < 0) {
perror("ioctl"