在前面两篇文章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工具的相关数据结构。

这些数据结构是用来保存从日志设备文件读出来的日志记录:

  1. struct queued_entry_t {
  2. union {
  3. unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1] __attribute__((aligned(4)));
  4. struct logger_entry entry __attribute__((aligned(4)));
  5. };
  6. queued_entry_t* next;
  7. queued_entry_t() {
  8. next = NULL;
  9. }
  10. };
  11. struct log_device_t {
  12. char* device;
  13. bool binary;
  14. int fd;
  15. bool printed;
  16. char label;
  17. queued_entry_t* queue;
  18. log_device_t* next;
  19. log_device_t(char* d, bool b, char l) {
  20. device = d;
  21. binary = b;
  22. label = l;
  23. queue = NULL;
  24. next = NULL;
  25. printed = false;
  26. }
  27. void enqueue(queued_entry_t* entry) {
  28. if (this->queue == NULL) {
  29. this->queue = entry;
  30. } else {
  31. queued_entry_t** e = &this->queue;
  32. while (*e && cmp(entry, *e) >= 0) {
  33. e = &((*e)->next);
  34. }
  35. entry->next = *e;
  36. *e = entry;
  37. }
  38. }
  39. };

其中,宏LOGGER_ENTRY_MAX_LEN和struct logger_entry定义在system/core/include/cutils/logger.h文件中,在Android应用程序框架层和系统运行库层日志系统源代码分析一文有提到,为了方便描述,这里列出这个宏和结构体的定义:

  1. struct logger_entry {
  2. __u16       len;    /* length of the payload */
  3. __u16       __pad;  /* no matter what, we get 2 bytes of padding */
  4. __s32       pid;    /* generating process's pid */
  5. __s32       tid;    /* generating process's tid */
  6. __s32       sec;    /* seconds since Epoch */
  7. __s32       nsec;   /* nanoseconds */
  8. char        msg[0]; /* the entry's payload */
  9. };
  10. #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的定义如下:

  1. static int cmp(queued_entry_t* a, queued_entry_t* b) {
  2. int n = a->entry.sec - b->entry.sec;
  3. if (n != 0) {
  4. return n;
  5. }
  6. return a->entry.nsec - b->entry.nsec;
  7. }

为什么日志记录要按照时间戳从小到大排序呢?原来,Logcat在使用时,可以指定一个参数-t <count>,可以指定只显示最新count条记录,超过count的记录将被丢弃,在这里的实现中,就是要把排在队列前面的多余日记记录丢弃了,因为排在前面的日志记录是最旧的,默认是显示所有的日志记录。在下面的代码中,我们还会继续分析这个过程。
        二. 打开日志设备文件。

Logcat工具的入口函数main,打开日志设备文件和一些初始化的工作也是在这里进行。main函数的内容也比较多,前面的逻辑都是解析命令行参数。这里假设我们使用logcat工具时,不带任何参数。这不会影响我们分析logcat读取日志的主线,有兴趣的读取可以自行分析解析命令行参数的逻辑。

分析完命令行参数以后,就开始要创建日志设备文件上下文结构体struct log_device_t了:

  1. if (!devices) {
  2. devices = new log_device_t(strdup("/dev/"LOGGER_LOG_MAIN), false, 'm');
  3. android::g_devCount = 1;
  4. int accessmode =
  5. (mode & O_RDONLY) ? R_OK : 0
  6. | (mode & O_WRONLY) ? W_OK : 0;
  7. // only add this if it's available
  8. if (0 == access("/dev/"LOGGER_LOG_SYSTEM, accessmode)) {
  9. devices->next = new log_device_t(strdup("/dev/"LOGGER_LOG_SYSTEM), false, 's');
  10. android::g_devCount++;
  11. }
  12. }

由于我们假设使用logcat时,不带任何命令行参数,这里的devices变量为NULL,因此,就会默认创建/dev/log/main设备上下文结构体,如果存在/dev/log/system设备文件,也会一并创建。宏LOGGER_LOG_MAIN和LOGGER_LOG_SYSTEM也是定义在system/core/include/cutils/logger.h文件中:

  1. #define LOGGER_LOG_MAIN     "log/main"
  2. #define LOGGER_LOG_SYSTEM   "log/system"

我们在Android日志系统驱动程序Logger源代码分析一文中看到,在Android日志系统驱动程序Logger中,默认是不创建/dev/log/system设备文件的。

往下看,调用setupOutput()函数来初始化输出文件:

  1. android::setupOutput();

setupOutput()函数定义如下:

  1. static void setupOutput()
  2. {
  3. if (g_outputFileName == NULL) {
  4. g_outFD = STDOUT_FILENO;
  5. } else {
  6. struct stat statbuf;
  7. g_outFD = openLogFile (g_outputFileName);
  8. if (g_outFD < 0) {
  9. perror ("couldn't open output file");
  10. exit(-1);
  11. }
  12. fstat(g_outFD, &statbuf);
  13. g_outByteCount = statbuf.st_size;
  14. }
  15. }

如果我们在执行logcat命令时,指定了-f  <filename>选项,日志内容就输出到filename文件中,否则,就输出到标准输出控制台去了。

再接下来,就是打开日志设备文件了:

  1. dev = devices;
  2. while (dev) {
  3. dev->fd = open(dev->device, mode);
  4. if (dev->fd < 0) {
  5. fprintf(stderr, "Unable to open log device '%s': %s\n",
  6. dev->device, strerror(errno));
  7. exit(EXIT_FAILURE);
  8. }
  9. if (clearLog) {
  10. int ret;
  11. ret = android::clearLog(dev->fd);
  12. if (ret) {
  13. perror("ioctl");
  14. exit(EXIT_FAILURE);
  15. }
  16. }
  17. if (getLogSize) {
  18. int size, readable;
  19. size = android::getLogSize(dev->fd);
  20. if (size < 0) {
  21. perror("ioctl");
  22. exit(EXIT_FAILURE);
  23. }
  24. readable = android::getLogReadableSize(dev->fd);
  25. if (readable < 0) {
  26. perror("ioctl");
  27. exit(EXIT_FAILURE);
  28. }
  29. printf("%s: ring buffer is %dKb (%dKb consumed), "
  30. "max entry is %db, max payload is %db\n", dev->device,
  31. size / 1024, readable / 1024,
  32. (int) LOGGER_ENTRY_MAX_LEN, (int) LOGGER_ENTRY_MAX_PAYLOAD);
  33. }
  34. dev = dev->next;
  35. }

如果执行logcat命令的目的是清空日志,即clearLog为true,则调用android::clearLog函数来执行清空日志操作:

  1. static int clearLog(int logfd)
  2. {
  3. return ioctl(logfd, LOGGER_FLUSH_LOG);
  4. }

这里是通过标准的文件函数ioctl函数来执行日志清空操作,具体可以参考logger驱动程序的实现。

如果执行logcat命令的目的是获取日志内存缓冲区的大小,即getLogSize为true,通过调用android::getLogSize函数实现:

  1. /* returns the total size of the log's ring buffer */
  2. static int getLogSize(int logfd)
  3. {
  4. return ioctl(logfd, LOGGER_GET_LOG_BUF_SIZE);
  5. }

如果为负数,即size < 0,就表示出错了,退出程序。

接着验证日志缓冲区可读内容的大小,即调用android::getLogReadableSize函数:

  1. /* returns the readable size of the log's ring buffer (that is, amount of the log consumed) */
  2. static int getLogReadableSize(int logfd)
  3. {
  4. return ioctl(logfd, LOGGER_GET_LOG_LEN);
  5. }

如果返回负数,即readable < 0,也表示出错了,退出程序。

接下去的printf语句,就是输出日志缓冲区的大小以及可读日志的大小到控制台去了。

继续看下看代码,如果执行logcat命令的目的是清空日志或者获取日志的大小信息,则现在就完成使命了,可以退出程序了:

  1. if (getLogSize) {
  2. return 0;
  3. }
  4. if (clearLog) {
  5. return 0;
  6. }

否则,就要开始读取设备文件的日志记录了:

  1. android::readLogLines(devices);

至此日志设备文件就打开并且初始化好了,下面,我们继续分析从日志设备文件读取日志记录的操作,即readLogLines函数。

三. 读取日志设备文件。

读取日志设备文件内容的函数是readLogLines函数:

  1. static void readLogLines(log_device_t* devices)
  2. {
  3. log_device_t* dev;
  4. int max = 0;
  5. int ret;
  6. int queued_lines = 0;
  7. bool sleep = true;
  8. int result;
  9. fd_set readset;
  10. for (dev=devices; dev; dev = dev->next) {
  11. if (dev->fd > max) {
  12. max = dev->fd;
  13. }
  14. }
  15. while (1) {
  16. do {
  17. timeval timeout = { 0, 5000 /* 5ms */ }; // If we oversleep it's ok, i.e. ignore EINTR.
  18. FD_ZERO(&readset);
  19. for (dev=devices; dev; dev = dev->next) {
  20. FD_SET(dev->fd, &readset);
  21. }
  22. result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout);
  23. } while (result == -1 && errno == EINTR);
  24. if (result >= 0) {
  25. for (dev=devices; dev; dev = dev->next) {
  26. if (FD_ISSET(dev->fd, &readset)) {
  27. queued_entry_t* entry = new queued_entry_t();
  28. /* NOTE: driver guarantees we read exactly one full entry */
  29. ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);
  30. if (ret < 0) {
  31. if (errno == EINTR) {
  32. delete entry;
  33. goto next;
  34. }
  35. if (errno == EAGAIN) {
  36. delete entry;
  37. break;
  38. }
  39. perror("logcat read");
  40. exit(EXIT_FAILURE);
  41. }
  42. else if (!ret) {
  43. fprintf(stderr, "read: Unexpected EOF!\n");
  44. exit(EXIT_FAILURE);
  45. }
  46. entry->entry.msg[entry->entry.len] = '\0';
  47. dev->enqueue(entry);
  48. ++queued_lines;
  49. }
  50. }
  51. if (result == 0) {
  52. // we did our short timeout trick and there's nothing new
  53. // print everything we have and wait for more data
  54. sleep = true;
  55. while (true) {
  56. chooseFirst(devices, &dev);
  57. if (dev == NULL) {
  58. break;
  59. }
  60. if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {
  61. printNextEntry(dev);
  62. } else {
  63. skipNextEntry(dev);
  64. }
  65. --queued_lines;
  66. }
  67. // the caller requested to just dump the log and exit
  68. if (g_nonblock) {
  69. exit(0);
  70. }
  71. } else {
  72. // print all that aren't the last in their list
  73. sleep = false;
  74. while (g_tail_lines == 0 || queued_lines > g_tail_lines) {
  75. chooseFirst(devices, &dev);
  76. if (dev == NULL || dev->queue->next == NULL) {
  77. break;
  78. }
  79. if (g_tail_lines == 0) {
  80. printNextEntry(dev);
  81. } else {
  82. skipNextEntry(dev);
  83. }
  84. --queued_lines;
  85. }
  86. }
  87. }
  88. next:
  89. ;
  90. }
  91. }

由于可能同时打开了多个日志设备文件,这里使用select函数来同时监控哪个文件当前可读:

  1. do {
  2. timeval timeout = { 0, 5000 /* 5ms */ }; // If we oversleep it's ok, i.e. ignore EINTR.
  3. FD_ZERO(&readset);
  4. for (dev=devices; dev; dev = dev->next) {
  5. FD_SET(dev->fd, &readset);
  6. }
  7. result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout);
  8. } while (result == -1 && errno == EINTR);

如果result >= 0,就表示有日志设备文件可读或者超时。接着,用一个for语句检查哪个设备文件可读,即FD_ISSET(dev->fd, &readset)是否为true,如果为true,表明可读,就要进一步通过read函数将日志读出,注意,每次只读出一条日志记录:

  1. for (dev=devices; dev; dev = dev->next) {
  2. if (FD_ISSET(dev->fd, &readset)) {
  3. queued_entry_t* entry = new queued_entry_t();
  4. /* NOTE: driver guarantees we read exactly one full entry */
  5. ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);
  6. if (ret < 0) {
  7. if (errno == EINTR) {
  8. delete entry;
  9. goto next;
  10. }
  11. if (errno == EAGAIN) {
  12. delete entry;
  13. break;
  14. }
  15. perror("logcat read");
  16. exit(EXIT_FAILURE);
  17. }
  18. else if (!ret) {
  19. fprintf(stderr, "read: Unexpected EOF!\n");
  20. exit(EXIT_FAILURE);
  21. }
  22. entry->entry.msg[entry->entry.len] = '\0';
  23. dev->enqueue(entry);
  24. ++queued_lines;
  25. }
  26. }

调用read函数之前,先创建一个日志记录项entry,接着调用read函数将日志读到entry->buf中,最后调用dev->enqueue(entry)将日志记录加入到日志队例中去。同时,把当前的日志记录数保存在queued_lines变量中。

继续进一步处理日志:

  1. if (result == 0) {
  2. // we did our short timeout trick and there's nothing new
  3. // print everything we have and wait for more data
  4. sleep = true;
  5. while (true) {
  6. chooseFirst(devices, &dev);
  7. if (dev == NULL) {
  8. break;
  9. }
  10. if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {
  11. printNextEntry(dev);
  12. } else {
  13. skipNextEntry(dev);
  14. }
  15. --queued_lines;
  16. }
  17. // the caller requested to just dump the log and exit
  18. if (g_nonblock) {
  19. exit(0);
  20. }
  21. } else {
  22. // print all that aren't the last in their list
  23. sleep = false;
  24. while (g_tail_lines == 0 || queued_lines > g_tail_lines) {
  25. chooseFirst(devices, &dev);
  26. if (dev == NULL || dev->queue->next == NULL) {
  27. break;
  28. }
  29. if (g_tail_lines == 0) {
  30. printNextEntry(dev);
  31. } else {
  32. skipNextEntry(dev);
  33. }
  34. --queued_lines;
  35. }
  36. }

如果result == 0,表明是等待超时了,目前没有新的日志可读,这时候就要先处理之前已经读出来的日志。调用chooseFirst选择日志队列不为空,且日志队列中的第一个日志记录的时间戳为最小的设备,即先输出最旧的日志:

  1. static void chooseFirst(log_device_t* dev, log_device_t** firstdev) {
  2. for (*firstdev = NULL; dev != NULL; dev = dev->next) {
  3. if (dev->queue != NULL && (*firstdev == NULL || cmp(dev->queue, (*firstdev)->queue) < 0)) {
  4. *firstdev = dev;
  5. }
  6. }
  7. }

如果存在这样的日志设备,接着判断日志记录是应该丢弃还是输出。前面我们说过,如果执行logcat命令时,指定了参数-t <count>,那么就会只显示最新的count条记录,其它的旧记录将被丢弃:

  1. if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {
  2. printNextEntry(dev);
  3. } else {
  4. skipNextEntry(dev);
  5. }

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的情况处理方式一样,先处理所有已经读出的日志记录,再进入下一次循环。希望读者可以好好体会这段代码:

  1. while (g_tail_lines == 0 || queued_lines > g_tail_lines) {
  2. chooseFirst(devices, &dev);
  3. if (dev == NULL || dev->queue->next == NULL) {
  4. break;
  5. }
  6. if (g_tail_lines == 0) {
  7. printNextEntry(dev);
  8. } else {
  9. skipNextEntry(dev);
  10. }
  11. --queued_lines;
  12. }

丢弃日志记录的函数skipNextEntry实现如下:

  1. static void skipNextEntry(log_device_t* dev) {
  2. maybePrintStart(dev);
  3. queued_entry_t* entry = dev->queue;
  4. dev->queue = entry->next;
  5. delete entry;
  6. }

这里只是简单地跳过日志队列头,这样就把最旧的日志丢弃了。

printNextEntry函数处理日志输出,下一节中继续分析。

四. 输出日志设备文件的内容。

从前面的分析中看出,最终日志设备文件内容的输出是通过printNextEntry函数进行的:

  1. static void printNextEntry(log_device_t* dev) {
  2. maybePrintStart(dev);
  3. if (g_printBinary) {
  4. printBinary(&dev->queue->entry);
  5. } else {
  6. processBuffer(dev, &dev->queue->entry);
  7. }
  8. skipNextEntry(dev);
  9. }

g_printBinary为true时,以二进制方式输出日志内容到指定的文件中:

  1. void printBinary(struct logger_entry *buf)
  2. {
  3. size_t size = sizeof(logger_entry) + buf->len;
  4. int ret;
  5. do {
  6. ret = write(g_outFD, buf, size);
  7. } while (ret < 0 && errno == EINTR);
  8. }

我们关注g_printBinary为false的情况,调用processBuffer进一步处理:

  1. static void processBuffer(log_device_t* dev, struct logger_entry *buf)
  2. {
  3. int bytesWritten = 0;
  4. int err;
  5. AndroidLogEntry entry;
  6. char binaryMsgBuf[1024];
  7. if (dev->binary) {
  8. err = android_log_processBinaryLogBuffer(buf, &entry, g_eventTagMap,
  9. binaryMsgBuf, sizeof(binaryMsgBuf));
  10. //printf(">>> pri=%d len=%d msg='%s'\n",
  11. //    entry.priority, entry.messageLen, entry.message);
  12. } else {
  13. err = android_log_processLogBuffer(buf, &entry);
  14. }
  15. if (err < 0) {
  16. goto error;
  17. }
  18. if (android_log_shouldPrintLine(g_logformat, entry.tag, entry.priority)) {
  19. if (false && g_devCount > 1) {
  20. binaryMsgBuf[0] = dev->label;
  21. binaryMsgBuf[1] = ' ';
  22. bytesWritten = write(g_outFD, binaryMsgBuf, 2);
  23. if (bytesWritten < 0) {
  24. perror("output error");
  25. exit(-1);
  26. }
  27. }
  28. bytesWritten = android_log_printLogLine(g_logformat, g_outFD, &entry);
  29. if (bytesWritten < 0) {
  30. perror("output error");
  31. exit(-1);
  32. }
  33. }
  34. g_outByteCount += bytesWritten;
  35. if (g_logRotateSizeKBytes > 0
  36. && (g_outByteCount / 1024) >= g_logRotateSizeKBytes
  37. ) {
  38. rotateLogs();
  39. }
  40. error:
  41. //fprintf (stderr, "Error processing record\n");
  42. return;
  43. }

当dev->binary为true,日志记录项是二进制形式,不同于我们在Android日志系统驱动程序Logger源代码分析一文中提到的常规格式:

struct logger_entry | priority | tag | msg
        这里我们不关注这种情况,有兴趣的读者可以自已分析,android_log_processBinaryLogBuffer函数定义在system/core/liblog/logprint.c文件中,它的作用是将一条二进制形式的日志记录转换为ASCII形式,并保存在entry参数中,它的原型为:

  1. /**
  2. * Convert a binary log entry to ASCII form.
  3. *
  4. * For convenience we mimic the processLogBuffer API.  There is no
  5. * pre-defined output length for the binary data, since we're free to format
  6. * it however we choose, which means we can't really use a fixed-size buffer
  7. * here.
  8. */
  9. int android_log_processBinaryLogBuffer(struct logger_entry *buf,
  10. AndroidLogEntry *entry, const EventTagMap* map, char* messageBuf,
  11. int messageBufLen);

通常情况下,dev->binary为false,调用android_log_processLogBuffer函数将日志记录由logger_entry格式转换为AndroidLogEntry格式。logger_entry格式在在Android日志系统驱动程序Logger源代码分析一文中已经有详细描述,这里不述;AndroidLogEntry结构体定义在system/core/include/cutils/logprint.h中:

  1. typedef struct AndroidLogEntry_t {
  2. time_t tv_sec;
  3. long tv_nsec;
  4. android_LogPriority priority;
  5. pid_t pid;
  6. pthread_t tid;
  7. const char * tag;
  8. size_t messageLen;
  9. const char * message;
  10. } AndroidLogEntry;

android_LogPriority是一个枚举类型,定义在system/core/include/android/log.h文件中:

  1. /*
  2. * Android log priority values, in ascending priority order.
  3. */
  4. typedef enum android_LogPriority {
  5. ANDROID_LOG_UNKNOWN = 0,
  6. ANDROID_LOG_DEFAULT,    /* only for SetMinPriority() */
  7. ANDROID_LOG_VERBOSE,
  8. ANDROID_LOG_DEBUG,
  9. ANDROID_LOG_INFO,
  10. ANDROID_LOG_WARN,
  11. ANDROID_LOG_ERROR,
  12. ANDROID_LOG_FATAL,
  13. ANDROID_LOG_SILENT,     /* only for SetMinPriority(); must be last */
  14. } android_LogPriority;

android_log_processLogBuffer定义在system/core/liblog/logprint.c文件中:

  1. /**
  2. * Splits a wire-format buffer into an AndroidLogEntry
  3. * entry allocated by caller. Pointers will point directly into buf
  4. *
  5. * Returns 0 on success and -1 on invalid wire format (entry will be
  6. * in unspecified state)
  7. */
  8. int android_log_processLogBuffer(struct logger_entry *buf,
  9. AndroidLogEntry *entry)
  10. {
  11. size_t tag_len;
  12. entry->tv_sec = buf->sec;
  13. entry->tv_nsec = buf->nsec;
  14. entry->priority = buf->msg[0];
  15. entry->pid = buf->pid;
  16. entry->tid = buf->tid;
  17. entry->tag = buf->msg + 1;
  18. tag_len = strlen(entry->tag);
  19. entry->messageLen = buf->len - tag_len - 3;
  20. entry->message = entry->tag + tag_len + 1;
  21. return 0;
  22. }

结合logger_entry结构体中日志项的格式定义(struct logger_entry | priority | tag | msg),这个函数很直观,不再累述。

调用完android_log_processLogBuffer函数后,日志记录的具体信息就保存在本地变量entry中了,接着调用android_log_shouldPrintLine函数来判断这条日志记录是否应该输出。

在分析android_log_shouldPrintLine函数之前,我们先了解数据结构AndroidLogFormat,这个结构体定义在system/core/liblog/logprint.c文件中:

  1. struct AndroidLogFormat_t {
  2. android_LogPriority global_pri;
  3. FilterInfo *filters;
  4. AndroidLogPrintFormat format;
  5. };

AndroidLogPrintFormat也是定义在system/core/liblog/logprint.c文件中:

  1. typedef struct FilterInfo_t {
  2. char *mTag;
  3. android_LogPriority mPri;
  4. struct FilterInfo_t *p_next;
  5. } FilterInfo;

因此,可以看出,AndroidLogFormat结构体定义了日志过滤规范。在logcat.c文件中,定义了变量

  1. static AndroidLogFormat * g_logformat;

这个变量是在main函数里面进行分配的:

  1. g_logformat = android_log_format_new();

在main函数里面,在分析logcat命令行参数时,会将g_logformat进行初始化,有兴趣的读者可以自行分析。

回到android_log_shouldPrintLine函数中,它定义在system/core/liblog/logprint.c文件中:

  1. /**
  2. * returns 1 if this log line should be printed based on its priority
  3. * and tag, and 0 if it should not
  4. */
  5. int android_log_shouldPrintLine (
  6. AndroidLogFormat *p_format, const char *tag, android_LogPriority pri)
  7. {
  8. return pri >= filterPriForTag(p_format, tag);
  9. }

这个函数判断在p_format中根据tag值,找到对应的pri值,如果返回来的pri值小于等于参数传进来的pri值,那么就表示这条日志记录可以输出。我们来看filterPriForTag函数的实现:

  1. static android_LogPriority filterPriForTag(
  2. AndroidLogFormat *p_format, const char *tag)
  3. {
  4. FilterInfo *p_curFilter;
  5. for (p_curFilter = p_format->filters
  6. ; p_curFilter != NULL
  7. ; p_curFilter = p_curFilter->p_next
  8. ) {
  9. if (0 == strcmp(tag, p_curFilter->mTag)) {
  10. if (p_curFilter->mPri == ANDROID_LOG_DEFAULT) {
  11. return p_format->global_pri;
  12. } else {
  13. return p_curFilter->mPri;
  14. }
  15. }
  16. }
  17. return p_format->global_pri;
  18. }

如果在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文件中:

  1. int android_log_printLogLine(
  2. AndroidLogFormat *p_format,
  3. int fd,
  4. const AndroidLogEntry *entry)
  5. {
  6. int ret;
  7. char defaultBuffer[512];
  8. char *outBuffer = NULL;
  9. size_t totalLen;
  10. outBuffer = android_log_formatLogLine(p_format, defaultBuffer,
  11. sizeof(defaultBuffer), entry, &totalLen);
  12. if (!outBuffer)
  13. return -1;
  14. do {
  15. ret = write(fd, outBuffer, totalLen);
  16. } while (ret < 0 && errno == EINTR);
  17. if (ret < 0) {
  18. fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
  19. ret = 0;
  20. goto done;
  21. }
  22. if (((size_t)ret) < totalLen) {
  23. fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", ret,
  24. (int)totalLen);
  25. goto done;
  26. }
  27. done:
  28. if (outBuffer != defaultBuffer) {
  29. free(outBuffer);
  30. }
  31. return ret;
  32. }

这个函数的作用就是把AndroidLogEntry格式的日志记录按照指定的格式AndroidLogFormat进行输出了,这里,不再进一步分析这个函数。

processBuffer函数的最后,还有一个rotateLogs的操作:

  1. static void rotateLogs()
  2. {
  3. int err;
  4. // Can't rotate logs if we're not outputting to a file
  5. if (g_outputFileName == NULL) {
  6. return;
  7. }
  8. close(g_outFD);
  9. for (int i = g_maxRotatedLogs ; i > 0 ; i--) {
  10. char *file0, *file1;
  11. asprintf(&file1, "%s.%d", g_outputFileName, i);
  12. if (i - 1 == 0) {
  13. asprintf(&file0, "%s", g_outputFileName);
  14. } else {
  15. asprintf(&file0, "%s.%d", g_outputFileName, i - 1);
  16. }
  17. err = rename (file0, file1);
  18. if (err < 0 && errno != ENOENT) {
  19. perror("while rotating log files");
  20. }
  21. free(file1);
  22. free(file0);
  23. }
  24. g_outFD = openLogFile (g_outputFileName);
  25. if (g_outFD < 0) {
  26. perror ("couldn't open output file");
  27. exit(-1);
  28. }
  29. g_outByteCount = 0;
  30. }

这个函数只有在执行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的日志系统有一个清晰的认识。

转载于:https://blog.51cto.com/shyluo/964523

Android日志系统Logcat源代码简要分析相关推荐

  1. android core log,Android 日志系统(Logcat)的实现分析

    这篇说一下Android 日志系统的实现: 1. Android中的打印分为4个缓冲区和6个打印等级,在frameworks\base\core\java\android\util\Log.java中 ...

  2. Android日志系统驱动程序Logger源代码分析

    我们知道,在Android系统中,提供了一个轻量级的日志系统,这个日志系统是以驱动程序的形式实现在内核空间的,而在用户空间分别提供了Java接口和C/C++接口来使用这个日志系统,取决于你编写的是An ...

  3. Android源码解析之日志系统Logcat

    转载自:http://blog.csdn.net/Luoshengyang/article/details/6606957 在前面两篇文章Android日志系统驱动程序Logger源代码分析和Andr ...

  4. Android Camera 系统架构源码分析

    Android Camera 系统架构源码分析(1)---->Camera的初始化 Android Camera 系统架构源码分析(2)---->Camera的startPreview和s ...

  5. 安卓日志系统初探(带你了解Android日志系统的概貌)

    目录 1 介绍 2 ADB的原理 2.1 adbd服务进程 2.2 ADB 服务端 2.2.1 USB方式 2.2.2 网络方式 2.2.3串口方式 2.3 ADB 客户端 3 安卓框架中日志的收集和 ...

  6. 搭建Android日志系统 美团点评大前端Logan入门指南

    目录 前言 总览 快速开始 下载官方源码 使用IDEA打开Logan Server项目 使用Docker下载MySQL镜像并启动容器 运行Logan Server 修改db.properties 修改 ...

  7. Android 切换系统语言源码分析

    以前了解Android的多语言实现很简单,可以在不同的语言环境下使用不同的资源,就做好相应的语言适配就好,但是一直没有实际使用过. 最近公司的项目要用到多国语言切换,并且还是和手机上系统设置里面的语言 ...

  8. 《hadoop进阶》web日志系统 KPI指标的分析与实现

    推广一下个人的微信公众号[搜索与推荐Wiki] 转载请注明出处: 转载自  Thinkgamer的CSDN博客:blog.csdn.net/gamer_gyt 代码下载地址:点击查看 1:Web日志分 ...

  9. android log系统

    转载自http://blog.csdn.net/Luoshengyang/article/category/838604/3 Android系统开发中LOG的使用 在程序开发过程中,LOG是广泛使用的 ...

最新文章

  1. 数据结构之【队列】的基本操作C语言实现
  2. Node.js v8.0.0 带来了哪些主要新特性
  3. Qt中的QDialog
  4. 关于IDEA 的一些常用设置
  5. 【CTF】paradigm-CTF babysandbox
  6. mybatis plus使用雪花算法_MyBatis-Plus进阶
  7. C#规范整理·集合和Linq
  8. cookie 百科_cookie是什么
  9. C++中用函数模板实现和优化抽象操作
  10. oracle 10g 学习之函数和存储过程(12)
  11. Python学习入门基础教程(learning Python)--6 Python下的list数据类型
  12. ubuntu下的项目管理工具dia, planner
  13. 153. php 引用
  14. 数据库优化之MySQL
  15. nc财务系统适配的java_用友NC系统使用过程中常见问题和解决方法!财会必看!...
  16. XShell安装配置教程
  17. C#ObjectArx Cad添加图层
  18. python爬取新浪微博大V的所有微博内容
  19. 第10章 模糊查询和聚合函数
  20. Ubuntu16.04下安装QQ的完整操作记录(经验证可用)

热门文章

  1. php 变量存活期,php 变量生命周期:PHP源码分析-PHP的生_php
  2. usb接口驱动_技术丨USB接口无法识别设备的处理方法
  3. Oracle 备份shell,oracle数据库shell备份脚本
  4. 使用slf4j和log4j记录日志
  5. 一台服务器装两个sql server_搭建我的世界基岩版服务器
  6. window连接不上linux ftp_不懂操作?手把手教你如何在linux下搭建FTP
  7. 系统学习深度学习(二十六)--BiRNN
  8. 双目测距(六)--三维重建及UI显示
  9. jsoup教程_2 http-client 讲解
  10. Mybatis-Plus 使用操作_1