继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android系统中的Logcat

繁华开满天机
关注TA
已关注
手记 134
粉丝 15
获赞 68

这篇文章介绍android系统中录log的工具 logcat.

Android 系统提供了一整套的API供Java层和Native层的程序写log,以方便调试及在系统出问题的时候有据可查. 而logcat是把这些抓log的工具,可以通过logcat把log显示到标准输出或文件中,同时还可以对log进行过滤. 设定log level及只读取指定module的log. logcat 的详细用法可以在手机中输入”logcat –help” 命令查看.

本文主要对logcat的源码进行分析,从main函数开始.从main函数开始遇到的第一个函数调用是.

g_logformat = android_log_format_new();

看下这个函数的定义:

/* android_log_format_new()*/AndroidLogFormat *android_log_format_new(){
    AndroidLogFormat *p_ret;

    p_ret = calloc(1, sizeof(AndroidLogFormat));

    p_ret->global_pri = ANDROID_LOG_VERBOSE;
    p_ret->format = FORMAT_BRIEF;

    return p_ret;}

这个函数通过malloc生成一个 AndroidLogFormat 的结构体,并将结构体的成员变量 ‘global_pri’ 和 ‘format’ 设置.先省略这个结构体的实现,继续看main 函数的代码.

/* main()*/if (argc == 2 && 0 == strcmp(argv[1], "--test")) {
    logprint_run_tests();
    exit(0);}

test 参数

如果logcat只有一个参数”–test”,则执行logprint_run_tests()函数,从代码来看,这个函数主要是测试logcat的功能的.

/* logprint_run_tests()*/p_format = android_log_format_new();fprintf(stderr, "running tests\n");tag = "random";android_log_addFilterRule(p_format,"*:i");

这个函数开始也call了一次 android_log_format_new()分配了一个结构体.并设置了tag变量,tag是每个module在打log时都需要设置一个tag,可以通过tag用来标志是该module输出的log. 接着call函数 android_log_addFilterRule(), 设置logcat的过滤机制.

/* android_log_addFilterRule() */android_LogPriority pri = ANDROID_LOG_DEFAULT;tagNameLength = strcspn(filterExpression, ":");if(filterExpression[tagNameLength] == ':') {
    pri = filterCharToPri(filterExpression[tagNameLength+1]);

    if (pri == ANDROID_LOG_UNKNOWN) {
        goto error;
    }}

该函数设置pri变量的值为 ANDROID_LOG_DEFAULT, 这个值被定义在一个enum中,如果log pririoty被设为 ANDROID_LOG_DEFAULT, 则表示输出所有等级的log.接着获得filter的tag的长度,根据前面的参数,”*:i”的返回结果是1, 接着调用filterCharToPri(),并传入参数”i” . 这个函数把传入的字符形式的log level 转换为数字level,这些level和 ANDROID_LOG_DEFAULT一起定义在enum中.

} else if (c == 'i') {
    pri = ANDROID_LOG_INFO;/* define */enum  {
    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 */

接下来判断是否有设置全局的log level,即传入的filter中是否包含”*:x”的字符串,如果是的话,就设置一个全局性的log level

/* android_log_addFilterRule() */if(0 == strncmp("*", filterExpression, tagNameLength)) {
    if (pri == ANDROID_LOG_DEFAULT) {
        pri = ANDROID_LOG_DEBUG;
    }
    p_format->global_pri = pri;

这样,runtest()函数的filter设置就完成了,剩下都是一些基本的检查语句检查设置有没有成功.

    assert (ANDROID_LOG_INFO == filterPriForTag(p_format, "random"));

这条语句检查tag “random”的priority是否是ANDROID_LOG_INFO,看下 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;}

这段code很直观,首先遍历p_format的filter,检查有没有设置tag的priority, 如果没有找到,就返回全局的log level. 还有另一个需要检查的地方

assert(android_log_shouldPrintLine(p_format, tag, ANDROID_LOG_DEBUG) == 0);

在android中每条log都对应要一个priority,这个函数检查相应tag的这条log是否应该打印出来.

int android_log_shouldPrintLine (
        AndroidLogFormat *p_format, const char *tag, android_LogPriority pri){
    return pri >= filterPriForTag(p_format, tag);}

通过filterPriForTag()函数查找该tag的priority,然后跟传入的level做比较,判断是否需要打印该tag该level级别的log.

同时,可以通过 android_log_addFilterString()设定多个log filter.

err = android_log_addFilterString(p_format, "*:s random:d ");int android_log_addFilterString(AndroidLogFormat *p_format,
        const char *filterString){
    // Yes, I'm using strsep
    while (NULL != (p_ret = strsep(&p_cur, " \t,"))) {
        // ignore whitespace-only entries
        if(p_ret[0] != '\0') {
            err = android_log_addFilterRule(p_format, p_ret);
        }
    }......	}

android_log_addFilterString()会循环遍历传入的filter string,并将其添加到filter 链表中. ok, “–test” 参数到这里就讲完了.

“-s” 参数

将全局的log level 设为 ANDROID_LOG_SILENT, 即不输出所有level的log

android_log_addFilterRule(g_logformat, "*:s");

“-c” 参数

该参数可以将log device中的log删除.

case 'c':
   clearLog = 1;
   mode = O_WRONLY;break;

    if (clearLog) {
        int ret;
        ret = android::clearLog(dev->fd);

看下clearLog函数

static int clearLog(int logfd){
    return ioctl(logfd, LOGGER_FLUSH_LOG);}

该函数向driver层下发 LOGGER_FLUSH_LOG 命令,告诉logger device的driver将logger中的log清除,关于logger device的实现在后面会讲到.

“-d” “-t N” 参数

这两个参数都会将g_nonblock变量设为true,表示把logger里的log读完就会立刻退出,而不会等待新log的写入. 同时”-t”参数后面还要跟着一个值N,表示只读最近的N条log.

“-g” 参数

给driver发送LOGGER_GET_LOG_BUF_SIZE, 获得logger device的大小.

“-b device” 参数

指定要从哪个buffer中读log, “-b”可以使用多次,例如” -b main -b radio”

“-B” 参数

以二进制方式打印log(目前默认会对log进行解析,以字符串形式打印)

“-f file” 参数

将log 输出到指定文件 file

“-r size” 参数

设定rotate size大小,rotate size 的含义是每种log 最多只有 size 大小. 录满后旧log会被覆盖

“-n num” 参数

设定每种log最大的log file数量,每个file的大小为 rotate_size/num

“-v format” 参数

设定输出的log 格式

err = setLogFormat (optarg);static int setLogFormat(const char * formatString){
    static AndroidLogPrintFormat format;

    format = android_log_formatFromString(formatString);
    android_log_setPrintFormat(g_logformat, format);

    return 0;}AndroidLogPrintFormat android_log_formatFromString(const char * formatString){
    static AndroidLogPrintFormat format;

    if (strcmp(formatString, "brief") == 0) format = FORMAT_BRIEF;
    else if (strcmp(formatString, "process") == 0) format = FORMAT_PROCESS;
    else if (strcmp(formatString, "tag") == 0) format = FORMAT_TAG;
    else if (strcmp(formatString, "thread") == 0) format = FORMAT_THREAD;
    else if (strcmp(formatString, "raw") == 0) format = FORMAT_RAW;
    else if (strcmp(formatString, "time") == 0) format = FORMAT_TIME;
    else if (strcmp(formatString, "threadtime") == 0) format = FORMAT_THREADTIME;
    else if (strcmp(formatString, "long") == 0) format = FORMAT_LONG;
    else format = FORMAT_OFF;

    return format;}

第一个函数把字符串形式的format转换成整形表示,第二个参数把转换后的format设置到全局变量g_logformat中

OK, 到此为止,参数部分就解析完毕.接着执行下面的代码

如果没有指定”-b”参数的话,会默认打开 “main” 和 “system” 两个logger device

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;
    if (0 == access("/dev/"LOGGER_LOG_SYSTEM, accessmode)) {
        devices->next = new log_device_t(strdup("/dev/"LOGGER_LOG_SYSTEM), false, 's');
        android::g_devCount++;
    }}

接下来是设定输出,如果没有指定”-f file”参数,默认输出到标准输出,否则打开file 文件.

static void setupOutput(){

    if (g_outputFileName == NULL) {
        g_outFD = STDOUT_FILENO;
    } else {
        struct stat statbuf;
        g_outFD = openLogFile (g_outputFileName);
        fstat(g_outFD, &statbuf);
        g_outByteCount = statbuf.st_size;
    }}

如果有设定log filter的话,会解析字符串并加入到g_logformat的filter链表中

for (int i = optind ; i < argc ; i++) {
	err = android_log_addFilterString(g_logformat, argv[i]);

接下来会打开logger device,然后就是读log了.

android::readLogLines(devices);

读log

readLogLines()函数通过一个while loop不停的从kernel 层的logger device中读取log

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);

这里有设一个timeout,最开始这个值为false,标志一直等待有log产生. 如果为true, 表示这段时间内没有新的log产生,则会把以及读出来的log全部flush到输出.

如果select()返回,会检查是否有logger device可读,并尝试从device中读取一条log.

    if (result >= 0) {
        for (dev=devices; dev; dev = dev->next) {
            if (FD_ISSET(dev->fd, &readset)) {
                queued_entry_t* entry = new queued_entry_t();
                ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);

logger device read() 的实现是每次读取一条logger_entry, 并存放到结构体queued_entry_t 的成员变量 buf 中,queued_entry_t 的定义如下:

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;
    }};

可以看到buf和logger_entry被定义成union结构,所以读到buffer的内容同时是一条logger_entry. 该结构体的定义如下

struct logger_entry {
    uint16_t    len;    /* length of the payload */
    uint16_t    __pad;  /* no matter what, we get 2 bytes of padding */
    int32_t     pid;    /* generating process's pid */
    int32_t     tid;    /* generating process's tid */
    int32_t     sec;    /* seconds since Epoch */
    int32_t     nsec;   /* nanoseconds */
    char        msg[0]; /* the entry's payload */};

第一个变量len是字符串msg的长度,所以read()函数返回后会对返回值和len的值做比较,如果不相等,表示读的数据有错误.

else if (entry->entry.len != ret - sizeof(struct logger_entry)) {
   		fprintf(stderr, "read: unexpected length. Expected %d, got %d\n",
   		entry->entry.len, ret - sizeof(struct logger_entry));
   		exit(EXIT_FAILURE);
   	}

接着会call device变量dev的enqueue()函数把刚读出来的log插入到dev的entry list中,并排序.

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;
    }}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;}

插入的算法是从链表头开始已有entry与新entry的时间戳,如果新entry的产生时间比较晚,就继续与下一个entry比较. 其实理论上讲,晚到来的log总是产生时间晚的log,所以这种比较的比较次数一般要大于从尾部开始比较. 另外值得一提的是比较算法采用了指针的指针,比较简洁,避免插入时链表头的判断. Linus大婶曾经在一次访谈中说道”这才是指针的真正用法”…….

接下来会打印log,需要说明的是没读出一次log就会判断是否需要打印log. 如果是select超时返回,会打印所有”需要”打印的log(这里加所有是因为如果使用”t”参数的话,只会打印最新的几条log),否则,会打印除最后一条log以外的所有log,剩一条log是为了下次时间戳的比较.

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;

chooseFirst()函数会把device链表中包含最新log的device选出来,这样对于多种类型的log输出到同一个文件的case,可以保证log按时间排序.

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;
        }
    }}

接着就是call printNextEntry()进行log输出.

static void printNextEntry(log_device_t* dev) {
    maybePrintStart(dev);
    if (g_printBinary) {
        printBinary(&dev->queue->entry);
    } else {
        processBuffer(dev, &dev->queue->entry);
    }
    skipNextEntry(dev);}

如果中指定了”B”参数,log将不会被解析,直接以二进制的方式输出,否则,调用 processBuffer()对log entry进行解析.

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);}

android log system目前有四种类型的log: main, system, radio, event. 其中前三种可以分为同一类型,log可以通过android_log_processLogBuffer()直接解析成人类可以读懂的文字. event log则稍有不同,解析后的log也要通过相应的文件才能读懂. 这里主要看一下常规log的解析.

android_log_processLogBuffer()的参数有两个,第一个是logger_entry变量,第二个是AndroidLogEntry变量,其实这两个结构体的内容大致相同,只不过后一个包含的信息更多一些.

struct logger_entry {
    uint16_t    len;    /* length of the payload */
    uint16_t    __pad;  /* no matter what, we get 2 bytes of padding */
    int32_t     pid;    /* generating process's pid */
    int32_t     tid;    /* generating process's tid */
    int32_t     sec;    /* seconds since Epoch */
    int32_t     nsec;   /* nanoseconds */
    char        msg[0]; /* the entry's payload */};
	typedef struct AndroidLogEntry_t {
    time_t tv_sec;
    long tv_nsec;
    android_LogPriority priority;
    int32_t pid;
    int32_t tid;
    const char * tag;
    size_t messageLen;
    const char * message;} AndroidLogEntry;int android_log_processLogBuffer(struct logger_entry *buf,
                                 AndroidLogEntry *entry){
    entry->tv_sec = buf->sec;
    entry->tv_nsec = buf->nsec;
    entry->pid = buf->pid;
    entry->tid = buf->tid;

    int msgStart = -1;
    int msgEnd = -1;

    int i;
    for (i = 1; i < buf->len; i++) {
        if (buf->msg[i] == '\0') {
            if (msgStart == -1) {
                msgStart = i + 1;
            } else {
                msgEnd = i;
                break;
            }
        }
    }

    entry->priority = buf->msg[0];
    entry->tag = buf->msg + 1;
    entry->message = buf->msg + msgStart;
    entry->messageLen = msgEnd - msgStart;

    return 0;}

可以看到转换函数主要是把logger_entry的msg给分割成三个部分:priority, tag, message.

接着会调用android_log_shouldPrintLine()检查该该tag及该level的log是否应该被打印,如果是,则调用android_log_printLogLine()打印.

/* android_log_printLogLine() */outBuffer = android_log_formatLogLine(p_format, defaultBuffer,
        sizeof(defaultBuffer), entry, &totalLen);do {
    ret = write(fd, outBuffer, totalLen);} while (ret < 0 && errno == EINTR);......if (outBuffer != defaultBuffer) {
    free(outBuffer);}

前面讲过可以通过参数”-v”设置打印的log格式,所以android_log_formatLogLine()的作用就是将entry 转换为最终的打印格式.

/* android_log_formatLogLine() */priChar = filterPriToChar(entry->priority);ptm = localtime(&(entry->tv_sec));strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);size_t prefixLen, suffixLen;switch (p_format->format) {
    case FORMAT_TAG:
        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
            "%c/%-8s: ", priChar, entry->tag);
        strcpy(suffixBuf, "\n"); suffixLen = 1;
        break;
    case FORMAT_PROCESS:
        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
            "%c(%5d) ", priChar, entry->pid);
        suffixLen = snprintf(suffixBuf, sizeof(suffixBuf),
            "  (%s)\n", entry->tag);
        break;
    case FORMAT_THREAD:
        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
            "%c(%5d:%5d) ", priChar, entry->pid, entry->tid);
        strcpy(suffixBuf, "\n");
        suffixLen = 1;
        break;
    case FORMAT_RAW:
        prefixBuf[0] = 0;
        prefixLen = 0;
        strcpy(suffixBuf, "\n");
        suffixLen = 1;
        break;
    case FORMAT_TIME:
        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
            "%s.%03ld %c/%-8s(%5d): ", timeBuf, entry->tv_nsec / 1000000,
            priChar, entry->tag, entry->pid);
        strcpy(suffixBuf, "\n");
        suffixLen = 1;
        break;
    case FORMAT_THREADTIME:
        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
            "%s.%03ld %5d %5d %c %-8s: ", timeBuf, entry->tv_nsec / 1000000,
            entry->pid, entry->tid, priChar, entry->tag);
        strcpy(suffixBuf, "\n");
        suffixLen = 1;
        break;
    case FORMAT_LONG:
        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
            "[ %s.%03ld %5d:%5d %c/%-8s ]\n",
            timeBuf, entry->tv_nsec / 1000000, entry->pid,
            entry->tid, priChar, entry->tag);
        strcpy(suffixBuf, "\n\n");
        suffixLen = 2;
        prefixSuffixIsHeaderFooter = 1;
        break;
    case FORMAT_BRIEF:
    default:
        prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
            "%c/%-8s(%5d): ", priChar, entry->tag, entry->pid);
        strcpy(suffixBuf, "\n");
        suffixLen = 1;
        break;}size_t numLines;size_t i;char *p;size_t bufferSize;const char *pm;ret[0] = '\0';       /* to start strcat off */p = ret;pm = entry->message;

首先会将数字格式的priority转为字符格式,接着生成格式化时间字符串.然后进入switch判断当前的format形式,并生成对应的prefix. 因为snprintf/vsnprintf有个特点:虽然它们最多只会向buffer写入指定长度的字符串(也就是说,如果buffer不足,字符串会被截断),但是,它们的返回值确是理想情况下(buffer足够大)可以写入的字符串长度.所以程序接下来会判断返回值跟buffer size是否相等.

/* android_log_formatLogLine() */if(prefixLen >= sizeof(prefixBuf))
    prefixLen = sizeof(prefixBuf) - 1;if(suffixLen >= sizeof(suffixBuf))
    suffixLen = sizeof(suffixBuf) - 1;

接着会遍历msg中的”\n”判断该条log需要分几行打出,每行打出的log都会有prefix字符串

/* android_log_formatLogLine() */if (prefixSuffixIsHeaderFooter) {
    numLines = 1;} else {
    pm = entry->message;
    numLines = 0;

    while (pm < (entry->message + entry->messageLen)) {
        if (*pm++ == '\n') numLines++;
    }
    if (pm > entry->message && *(pm-1) != '\n') numLines++;}

在函数参数中已经传入了存log的buffer,但是,如果需要打印的log 长度超过了buffer size,则系统会重新malloc一个新的buffer,记住:这个buffer需要在函数外free掉!!!!(logcat的做法是判断函数返回值是否等于传入的buffer,如果不是,则表示有新buffer malloc,就会free掉)

/* android_log_formatLogLine() */bufferSize = (numLines * (prefixLen + suffixLen)) + entry->messageLen + 1;if (defaultBufferSize >= bufferSize) {
    ret = defaultBuffer;} else {
    ret = (char *)malloc(bufferSize);

    if (ret == NULL) {
        return ret;
    }}/* android_log_printLogLine() */if (outBuffer != defaultBuffer) {
    free(outBuffer);}

最后是生成最终的log字符串.对于”long”格式的log format来讲,prefix只需打印一次,所以不需要遍历msg中的”\n”.否则,对于每行log都要加上prefix.

if (prefixSuffixIsHeaderFooter) {
    strcat(p, prefixBuf);
    p += prefixLen;
    strncat(p, entry->message, entry->messageLen);
    p += entry->messageLen;
    strcat(p, suffixBuf);
    p += suffixLen;} else {
    while(pm < (entry->message + entry->messageLen)) {
        const char *lineStart;
        size_t lineLen;
        lineStart = pm;

        // Find the next end-of-line in message
        while (pm < (entry->message + entry->messageLen)
                && *pm != '\n') pm++;
        lineLen = pm - lineStart;

        strcat(p, prefixBuf);
        p += prefixLen;
        strncat(p, lineStart, lineLen);
        p += lineLen;
        strcat(p, suffixBuf);
        p += suffixLen;

        if (*pm == '\n') pm++;
    }}if (p_outLength != NULL) {
    *p_outLength = p - ret;}return ret;

函数返回后,就把最终字符串写到输出.

OK,logcat的用法及实现流程到这里就基本结束了.

原文链接:http://www.apkbus.com/blog-705730-60478.html

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP