原文出處——>[Android日志系統Logcat源代碼簡要分析](http://blog.csdn.net/luoshengyang/article/details/6606957)
在前面兩篇文章Android日志系統驅動程序Logger源代碼分析和Android應用程序框架層和系統運行庫層日志系統源代碼中,介紹了Android內核空間層、系統運行庫層和應用程序框架層日志系統相關的源代碼,其中,后一篇文章著重介紹了日志的寫入操作。為了描述完整性,這篇文章著重介紹日志的讀取操作,這就是我們在開發Android應用程序時,經常要用到日志查看工具Logcat了。
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工具的相關數據結構。**
這些數據結構是用來保存從日志設備文件讀出來的日志記錄:
~~~
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應用程序框架層和系統運行庫層日志系統源代碼分析一文有提到,為了方便描述,這里列出這個宏和結構體的定義:
~~~
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的定義如下:
~~~
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了:
~~~
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文件中:
~~~
#define LOGGER_LOG_MAIN "log/main"
#define LOGGER_LOG_SYSTEM "log/system"
~~~
我們在Android日志系統驅動程序Logger源代碼分析一文中看到,在Android日志系統驅動程序Logger中,默認是不創建/dev/log/system設備文件的。
往下看,調用setupOutput()函數來初始化輸出文件:
~~~
android::setupOutput();
~~~
setupOutput()函數定義如下:
~~~
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文件中,否則,就輸出到標準輸出控制臺去了。
再接下來,就是打開日志設備文件了:
~~~
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");
exit(EXIT_FAILURE);
}
readable = android::getLogReadableSize(dev->fd);
if (readable < 0) {
perror("ioctl");
exit(EXIT_FAILURE);
}
printf("%s: ring buffer is %dKb (%dKb consumed), "
"max entry is %db, max payload is %db\n", dev->device,
size / 1024, readable / 1024,
(int) LOGGER_ENTRY_MAX_LEN, (int) LOGGER_ENTRY_MAX_PAYLOAD);
}
dev = dev->next;
}
~~~
如果執行logcat命令的目的是清空日志,即clearLog為true,則調用android::clearLog函數來執行清空日志操作:
~~~
static int clearLog(int logfd)
{
return ioctl(logfd, LOGGER_FLUSH_LOG);
}
~~~
這里是通過標準的文件函數ioctl函數來執行日志清空操作,具體可以參考logger驅動程序的實現。
如果執行logcat命令的目的是獲取日志內存緩沖區的大小,即getLogSize為true,通過調用android::getLogSize函數實現:
~~~
/* returns the total size of the log's ring buffer */
static int getLogSize(int logfd)
{
return ioctl(logfd, LOGGER_GET_LOG_BUF_SIZE);
}
~~~
如果為負數,即size < 0,就表示出錯了,退出程序。
接著驗證日志緩沖區可讀內容的大小,即調用android::getLogReadableSize函數:
~~~
/* returns the readable size of the log's ring buffer (that is, amount of the log consumed) */
static int getLogReadableSize(int logfd)
{
return ioctl(logfd, LOGGER_GET_LOG_LEN);
}
~~~
如果返回負數,即readable < 0,也表示出錯了,退出程序。
接下去的printf語句,就是輸出日志緩沖區的大小以及可讀日志的大小到控制臺去了。
繼續看下看代碼,如果執行logcat命令的目的是清空日志或者獲取日志的大小信息,則現在就完成使命了,可以退出程序了:
~~~
if (getLogSize) {
return 0;
}
if (clearLog) {
return 0;
}
~~~
否則,就要開始讀取設備文件的日志記錄了:
~~~
android::readLogLines(devices);
~~~
至此日志設備文件就打開并且初始化好了,下面,我們繼續分析從日志設備文件讀取日志記錄的操作,即readLogLines函數。
**三. 讀取日志設備文件。**
讀取日志設備文件內容的函數是readLogLines函數:
~~~
static void readLogLines(log_device_t* devices)
{
log_device_t* dev;
int max = 0;
int ret;
int queued_lines = 0;
bool sleep = true;
int result;
fd_set readset;
for (dev=devices; dev; dev = dev->next) {
if (dev->fd > max) {
max = dev->fd;
}
}
while (1) {
do {
timeval timeout = { 0, 5000 /* 5ms */ }; // If we oversleep it's ok, i.e. ignore EINTR.
FD_ZERO(&readset);
for (dev=devices; dev; dev = dev->next) {
FD_SET(dev->fd, &readset);
}
result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout);
} while (result == -1 && errno == EINTR);
if (result >= 0) {
for (dev=devices; dev; dev = dev->next) {
if (FD_ISSET(dev->fd, &readset)) {
queued_entry_t* entry = new queued_entry_t();
/* NOTE: driver guarantees we read exactly one full entry */
ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);
if (ret < 0) {
if (errno == EINTR) {
delete entry;
goto next;
}
if (errno == EAGAIN) {
delete entry;
break;
}
perror("logcat read");
exit(EXIT_FAILURE);
}
else if (!ret) {
fprintf(stderr, "read: Unexpected EOF!\n");
exit(EXIT_FAILURE);
}
entry->entry.msg[entry->entry.len] = '\0';
dev->enqueue(entry);
++queued_lines;
}
}
if (result == 0) {
// we did our short timeout trick and there's nothing new
// print everything we have and wait for more data
sleep = true;
while (true) {
chooseFirst(devices, &dev);
if (dev == NULL) {
break;
}
if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
// the caller requested to just dump the log and exit
if (g_nonblock) {
exit(0);
}
} else {
// print all that aren't the last in their list
sleep = false;
while (g_tail_lines == 0 || queued_lines > g_tail_lines) {
chooseFirst(devices, &dev);
if (dev == NULL || dev->queue->next == NULL) {
break;
}
if (g_tail_lines == 0) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
}
}
next:
;
}
}
~~~
由于可能同時打開了多個日志設備文件,這里使用select函數來同時監控哪個文件當前可讀:
~~~
do {
timeval timeout = { 0, 5000 /* 5ms */ }; // If we oversleep it's ok, i.e. ignore EINTR.
FD_ZERO(&readset);
for (dev=devices; dev; dev = dev->next) {
FD_SET(dev->fd, &readset);
}
result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout);
} while (result == -1 && errno == EINTR);
~~~
如果result >= 0,就表示有日志設備文件可讀或者超時。接著,用一個for語句檢查哪個設備文件可讀,即FD_ISSET(dev->fd, &readset)是否為true,如果為true,表明可讀,就要進一步通過read函數將日志讀出,注意,每次只讀出一條日志記錄:
~~~
for (dev=devices; dev; dev = dev->next) {
if (FD_ISSET(dev->fd, &readset)) {
queued_entry_t* entry = new queued_entry_t();
/* NOTE: driver guarantees we read exactly one full entry */
ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);
if (ret < 0) {
if (errno == EINTR) {
delete entry;
goto next;
}
if (errno == EAGAIN) {
delete entry;
break;
}
perror("logcat read");
exit(EXIT_FAILURE);
}
else if (!ret) {
fprintf(stderr, "read: Unexpected EOF!\n");
exit(EXIT_FAILURE);
}
entry->entry.msg[entry->entry.len] = '\0';
dev->enqueue(entry);
++queued_lines;
}
}
~~~
調用read函數之前,先創建一個日志記錄項entry,接著調用read函數將日志讀到entry->buf中,最后調用dev->enqueue(entry)將日志記錄加入到日志隊例中去。同時,把當前的日志記錄數保存在queued_lines變量中。
繼續進一步處理日志:
~~~
if (result == 0) {
// we did our short timeout trick and there's nothing new
// print everything we have and wait for more data
sleep = true;
while (true) {
chooseFirst(devices, &dev);
if (dev == NULL) {
break;
}
if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
// the caller requested to just dump the log and exit
if (g_nonblock) {
exit(0);
}
} else {
// print all that aren't the last in their list
sleep = false;
while (g_tail_lines == 0 || queued_lines > g_tail_lines) {
chooseFirst(devices, &dev);
if (dev == NULL || dev->queue->next == NULL) {
break;
}
if (g_tail_lines == 0) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
}
~~~
如果result == 0,表明是等待超時了,目前沒有新的日志可讀,這時候就要先處理之前已經讀出來的日志。調用chooseFirst選擇日志隊列不為空,且日志隊列中的第一個日志記錄的時間戳為最小的設備,即先輸出最舊的日志:
~~~
static void chooseFirst(log_device_t* dev, log_device_t** firstdev) {
for (*firstdev = NULL; dev != NULL; dev = dev->next) {
if (dev->queue != NULL && (*firstdev == NULL || cmp(dev->queue, (*firstdev)->queue) < 0)) {
*firstdev = dev;
}
}
}
~~~
如果存在這樣的日志設備,接著判斷日志記錄是應該丟棄還是輸出。前面我們說過,如果執行logcat命令時,指定了參數-t `<count>`,那么就會只顯示最新的count條記錄,其它的舊記錄將被丟棄:
~~~
if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
~~~
g_tail_lines表示顯示最新記錄的條數,如果為0,就表示全部顯示。如果g_tail_lines == 0或者queued_lines <= g_tail_lines,就表示這條日志記錄應該輸出,否則就要丟棄了。每處理完一條日志記錄,queued_lines就減1,這樣,最新的g_tail_lines就可以輸出出來了。
如果result > 0,表明有新的日志可讀,這時候的處理方式與result == 0的情況不同,因為這時候還有新的日志可讀,所以就不能先急著處理之前已經讀出來的日志。這里,分兩種情況考慮,如果能設置了只顯示最新的g_tail_lines條記錄,并且當前已經讀出來的日志記錄條數已經超過g_tail_lines,就要丟棄,剩下的先不處理,等到下次再來處理;如果沒有設備顯示最新的g_tail_lines條記錄,即g_tail_lines == 0,這種情況就和result == 0的情況處理方式一樣,先處理所有已經讀出的日志記錄,再進入下一次循環。希望讀者可以好好體會這段代碼:
~~~
while (g_tail_lines == 0 || queued_lines > g_tail_lines) {
chooseFirst(devices, &dev);
if (dev == NULL || dev->queue->next == NULL) {
break;
}
if (g_tail_lines == 0) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
~~~
丟棄日志記錄的函數skipNextEntry實現如下:
~~~
static void skipNextEntry(log_device_t* dev) {
maybePrintStart(dev);
queued_entry_t* entry = dev->queue;
dev->queue = entry->next;
delete entry;
}
~~~
這里只是簡單地跳過日志隊列頭,這樣就把最舊的日志丟棄了。
printNextEntry函數處理日志輸出,下一節中繼續分析。
**四. 輸出日志設備文件的內容。**
從前面的分析中看出,最終日志設備文件內容的輸出是通過printNextEntry函數進行的:
~~~
static void printNextEntry(log_device_t* dev) {
maybePrintStart(dev);
if (g_printBinary) {
printBinary(&dev->queue->entry);
} else {
processBuffer(dev, &dev->queue->entry);
}
skipNextEntry(dev);
}
~~~
g_printBinary為true時,以二進制方式輸出日志內容到指定的文件中:
~~~
void printBinary(struct logger_entry *buf)
{
size_t size = sizeof(logger_entry) + buf->len;
int ret;
do {
ret = write(g_outFD, buf, size);
} while (ret < 0 && errno == EINTR);
}
~~~
我們關注g_printBinary為false的情況,調用processBuffer進一步處理:
~~~
static void processBuffer(log_device_t* dev, struct logger_entry *buf)
{
int bytesWritten = 0;
int err;
AndroidLogEntry entry;
char binaryMsgBuf[1024];
if (dev->binary) {
err = android_log_processBinaryLogBuffer(buf, &entry, g_eventTagMap,
binaryMsgBuf, sizeof(binaryMsgBuf));
//printf(">>> pri=%d len=%d msg='%s'\n",
// entry.priority, entry.messageLen, entry.message);
} else {
err = android_log_processLogBuffer(buf, &entry);
}
if (err < 0) {
goto error;
}
if (android_log_shouldPrintLine(g_logformat, entry.tag, entry.priority)) {
if (false && g_devCount > 1) {
binaryMsgBuf[0] = dev->label;
binaryMsgBuf[1] = ' ';
bytesWritten = write(g_outFD, binaryMsgBuf, 2);
if (bytesWritten < 0) {
perror("output error");
exit(-1);
}
}
bytesWritten = android_log_printLogLine(g_logformat, g_outFD, &entry);
if (bytesWritten < 0) {
perror("output error");
exit(-1);
}
}
g_outByteCount += bytesWritten;
if (g_logRotateSizeKBytes > 0
&& (g_outByteCount / 1024) >= g_logRotateSizeKBytes
) {
rotateLogs();
}
error:
//fprintf (stderr, "Error processing record\n");
return;
}
~~~
當dev->binary為true,日志記錄項是二進制形式,不同于我們在Android日志系統驅動程序Logger源代碼分析一文中提到的常規格式:
~~~
struct logger_entry | priority | tag | msg
~~~
這里我們不關注這種情況,有興趣的讀者可以自已分析,android_log_processBinaryLogBuffer函數定義在system/core/liblog/logprint.c文件中,它的作用是將一條二進制形式的日志記錄轉換為ASCII形式,并保存在entry參數中,它的原型為:
~~~
/**
* Convert a binary log entry to ASCII form.
*
* For convenience we mimic the processLogBuffer API. There is no
* pre-defined output length for the binary data, since we're free to format
* it however we choose, which means we can't really use a fixed-size buffer
* here.
*/
int android_log_processBinaryLogBuffer(struct logger_entry *buf,
AndroidLogEntry *entry, const EventTagMap* map, char* messageBuf,
int messageBufLen);
~~~
通常情況下,dev->binary為false,調用android_log_processLogBuffer函數將日志記錄由logger_entry格式轉換為AndroidLogEntry格式。logger_entry格式在在Android日志系統驅動程序Logger源代碼分析一文中已經有詳細描述,這里不述;AndroidLogEntry結構體定義在system/core/include/cutils/logprint.h中:
~~~
typedef struct AndroidLogEntry_t {
time_t tv_sec;
long tv_nsec;
android_LogPriority priority;
pid_t pid;
pthread_t tid;
const char * tag;
size_t messageLen;
const char * message;
} AndroidLogEntry;
~~~
android_LogPriority是一個枚舉類型,定義在system/core/include/android/log.h文件中:
~~~
/*
* Android log priority values, in ascending priority order.
*/
typedef enum android_LogPriority {
ANDROID_LOG_UNKNOWN = 0,
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL,
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
} android_LogPriority;
~~~
android_log_processLogBuffer定義在system/core/liblog/logprint.c文件中:
~~~
/**
* Splits a wire-format buffer into an AndroidLogEntry
* entry allocated by caller. Pointers will point directly into buf
*
* Returns 0 on success and -1 on invalid wire format (entry will be
* in unspecified state)
*/
int android_log_processLogBuffer(struct logger_entry *buf,
AndroidLogEntry *entry)
{
size_t tag_len;
entry->tv_sec = buf->sec;
entry->tv_nsec = buf->nsec;
entry->priority = buf->msg[0];
entry->pid = buf->pid;
entry->tid = buf->tid;
entry->tag = buf->msg + 1;
tag_len = strlen(entry->tag);
entry->messageLen = buf->len - tag_len - 3;
entry->message = entry->tag + tag_len + 1;
return 0;
}
~~~
結合logger_entry結構體中日志項的格式定義(struct logger_entry | priority | tag | msg),這個函數很直觀,不再累述。
調用完android_log_processLogBuffer函數后,日志記錄的具體信息就保存在本地變量entry中了,接著調用android_log_shouldPrintLine函數來判斷這條日志記錄是否應該輸出。
在分析android_log_shouldPrintLine函數之前,我們先了解數據結構AndroidLogFormat,這個結構體定義在system/core/liblog/logprint.c文件中:
~~~
struct AndroidLogFormat_t {
android_LogPriority global_pri;
FilterInfo *filters;
AndroidLogPrintFormat format;
};
~~~
AndroidLogPrintFormat也是定義在system/core/liblog/logprint.c文件中:
~~~
typedef struct FilterInfo_t {
char *mTag;
android_LogPriority mPri;
struct FilterInfo_t *p_next;
} FilterInfo;
~~~
因此,可以看出,AndroidLogFormat結構體定義了日志過濾規范。在logcat.c文件中,定義了變量
~~~
static AndroidLogFormat * g_logformat;
~~~
這個變量是在main函數里面進行分配的:
~~~
g_logformat = android_log_format_new();
~~~
在main函數里面,在分析logcat命令行參數時,會將g_logformat進行初始化,有興趣的讀者可以自行分析。
回到android_log_shouldPrintLine函數中,它定義在system/core/liblog/logprint.c文件中:
~~~
/**
* returns 1 if this log line should be printed based on its priority
* and tag, and 0 if it should not
*/
int android_log_shouldPrintLine (
AndroidLogFormat *p_format, const char *tag, android_LogPriority pri)
{
return pri >= filterPriForTag(p_format, tag);
}
~~~
這個函數判斷在p_format中根據tag值,找到對應的pri值,如果返回來的pri值小于等于參數傳進來的pri值,那么就表示這條日志記錄可以輸出。我們來看filterPriForTag函數的實現:
~~~
static android_LogPriority filterPriForTag(
AndroidLogFormat *p_format, const char *tag)
{
FilterInfo *p_curFilter;
for (p_curFilter = p_format->filters
; p_curFilter != NULL
; p_curFilter = p_curFilter->p_next
) {
if (0 == strcmp(tag, p_curFilter->mTag)) {
if (p_curFilter->mPri == ANDROID_LOG_DEFAULT) {
return p_format->global_pri;
} else {
return p_curFilter->mPri;
}
}
}
return p_format->global_pri;
}
~~~
如果在p_format中找到與tag值對應的filter,并且該filter的mPri不等于ANDROID_LOG_DEFAULT,那么就返回該filter的成員變量mPri的值;其它情況下,返回p_format->global_pri的值。
回到processBuffer函數中,如果執行完android_log_shouldPrintLine函數后,表明當前日志記錄應當輸出,則調用android_log_printLogLine函數來輸出日志記錄到文件fd中, 這個函數也是定義在system/core/liblog/logprint.c文件中:
~~~
int android_log_printLogLine(
AndroidLogFormat *p_format,
int fd,
const AndroidLogEntry *entry)
{
int ret;
char defaultBuffer[512];
char *outBuffer = NULL;
size_t totalLen;
outBuffer = android_log_formatLogLine(p_format, defaultBuffer,
sizeof(defaultBuffer), entry, &totalLen);
if (!outBuffer)
return -1;
do {
ret = write(fd, outBuffer, totalLen);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
ret = 0;
goto done;
}
if (((size_t)ret) < totalLen) {
fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", ret,
(int)totalLen);
goto done;
}
done:
if (outBuffer != defaultBuffer) {
free(outBuffer);
}
return ret;
}
~~~
這個函數的作用就是把AndroidLogEntry格式的日志記錄按照指定的格式AndroidLogFormat進行輸出了,這里,不再進一步分析這個函數。
processBuffer函數的最后,還有一個rotateLogs的操作:
~~~
static void rotateLogs()
{
int err;
// Can't rotate logs if we're not outputting to a file
if (g_outputFileName == NULL) {
return;
}
close(g_outFD);
for (int i = g_maxRotatedLogs ; i > 0 ; i--) {
char *file0, *file1;
asprintf(&file1, "%s.%d", g_outputFileName, i);
if (i - 1 == 0) {
asprintf(&file0, "%s", g_outputFileName);
} else {
asprintf(&file0, "%s.%d", g_outputFileName, i - 1);
}
err = rename (file0, file1);
if (err < 0 && errno != ENOENT) {
perror("while rotating log files");
}
free(file1);
free(file0);
}
g_outFD = openLogFile (g_outputFileName);
if (g_outFD < 0) {
perror ("couldn't open output file");
exit(-1);
}
g_outByteCount = 0;
}
~~~
這個函數只有在執行logcat命令時,指定了`-f <filename>`參數時,即g_outputFileName不為NULL時才起作用。它的作用是在將日志記錄循環輸出到一組文件中。例如,指定-f參數為logfile,g_maxRotatedLogs為3,則這組文件分別為:
`logfile,logfile.1,logfile.2,logfile.3`
當當前輸入到logfile文件的日志記錄大小g_outByteCount大于等于g_logRotateSizeKBytes時,就要將logfile.2的內容移至logfile.3中,同時將logfile.1的內容移至logfile.2中,同時logfle的內容移至logfile.1中,再重新打開logfile文件進入后續輸入。這樣做的作用是不至于使得日志文件變得越來越來大,以至于占用過多的磁盤空間,而是只在磁盤上保存一定量的最新的日志記錄。這樣,舊的日志記錄就會可能被新的日志記錄所覆蓋。
至此,關于Android日志系統源代碼,我們就完整地分析完了,其中包括位于內核空間的驅動程序Logger源代碼分析,還有位于應用程序框架層和系統運行庫層的日志寫入操作接口源代碼分析和用于日志讀取的工具Logcat源代碼分析,希望能夠幫助讀者對Android的日志系統有一個清晰的認識。
- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析