<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                原文出處——>[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的日志系統有一個清晰的認識。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看