💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
从前面4.6.3小节的内容可以知道,Logcat工具是通过调用源代码文件logcat.cpp中的函数printNextEntry来输出日志记录的。 在前面的4.6.2小节中还提到,如果全局变量g_printBinary的值等于1,那么就说明以二进制格式来输出日志记录。这时候函数printNextEntry就调用函数printBinary来处理。 **system/core/logcat/logcat.cpp** ~~~ 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); } ~~~ 由于不需要对日志记录进行解析,即不用将它的优先级、标签以及内容解析出来,因此,函数printBinary的实现很简单,可直接调用函数write将它输出到文件或者打印到标准输出中。 如果全局变量g_printBinary的值等于0,那么就要以文本格式来输出日志记录。这时候函数printNextEntry就调用函数processBuffer来处理。 **system/core/logcat/logcat.cpp** ~~~ 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; } ~~~ 前面得到的日志记录只是一块二进制数据,保存在一个logger_entry结构体中,因此,函数processBuffer在将它输出之前,首先要将它的内容转换为一个AndroidLogEntry结构体。AndroidLogEntry结构体描述了一条日志记录的写入时间、优先级、标签、内容以及写入进程ID。如果日志记录的类 型是二进制格式的,即是类型为events的日志记录,那么函数processBuffer就会调用函数android_log_processBinaryLogBuffer对它进行解析;否则,就调用函数android_log_processLogBuffer对它进行解析。日志记录解析完成之后,函数processBuffer最后就调用函数android_log_printLogLine将它输出到文件或者打印到标准输出中。这三个函数的实现我们在后面再详细分析,现在先完成对函数processBuffer的分析。 一条日志记录解析完成之后,Logcat工具就得到了它的优先级和标签。由于在启动Logcat工具时,可能设置了日志记录输出过滤器,因此,函数processBuffer就需要调用函数android_log_shouldPrintLine判断一条日志记录是否能够输出。在前面的4.6.2小节中提到,Logcat工具的日志记录输出过滤器列表保存在全局变量g_logformat中,因此,函数processBuffer就以它作为参数来调用函数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); } ~~~ 函数filterPriForTag首先在参数p_format中检查是否对日志记录标签tag设置了输出过滤器。如果设置了,那么函数filterPriForTag就会返回它的过滤优先级。只有这个过滤优先级低于即将要输出的日志记录的优先级时,该日志记录才可以输出。 函数filterPriForTag的实现如下所示。 **system/core/liblog/logprint.c** ~~~ 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; } ~~~ 第6行到第17行的for循环检查在参数p_format的日志记录输出过滤器列表中是否为日志记录标签tag设置了输出过滤器。如果设置了,就返回该日志记录标签所对应的优先级;否则,就返回全局设置的日志记录输出优先级,即返回参数p_format的成员变量global_pri。另外,如果我们为日志记录标签tag设置了输出过滤器,但是将该输出过滤器的优先级设置为ANDROID_LOG_DEFAULT,那么函数filterPriForTag实际上返回的是全局设置的日志记录输出优先级,如第12行代码所示。 回到函数processBuffer中,如果函数android_log_shouldPrintLine的返回值为true,那么它就会先调用函数android_log_printLogLine输出日志记录,然后再将输出的日志记录的字节数bytesWritten增加到全局变量g_outByteCount中,表示到目前为止,一共往文件g_outFD中输出了多少个字节的日志记录。一旦输出到文件g_outFD中的日志记录的字节数大于全局变量g_logRotateSizeKBytes的值时,那么Logcat就会将接下来的其他日志记录输出到另外一个文件中。全局变量g_logRotateSizeKBytes的值是由Logcat工具的启动选项r来指定的,如果没有指定该选项,那么全局变量g_logRotateSizeKBytes的值就会等于0,表示将所有的日志记录都输出到同一个文件中。如果全局变量g_logRotateSizeKBytes的值大于0,那么总共可以用来作为日志记录输出文件的个数就由Logcat工具的选项n来指定。如果没有指定选项n,那么默认就有四个日志记录输出文件。第44行调用函数rotateLogs来设置下一个日志记录输出文件,它的实现如下所示。 **system/core/logcat/logcat.cpp** ~~~ 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来指定日志记录输出文件为“logfile”,并且通过选项n来指定日志记录输出文件个数为3,那么函数rotateLogs就分别将日志记录输出文件设置为logfile、logfile.1、logfile.2和logfile.3。 > 这四个文件是循环使用的,即当文件logfile.3的大小达到限定的值之后,Logcat工具就将接下来的日志记录重新输出到文件logfile、logfile.1和logfile.2中。依此类推,当文件logfile.2的大小再次达到限定的值之后,最后又将新的日志记录输出到文件logfile.3中。 分析完成函数processBuffer的实现之后,接下来我们开始分析函数android_log_processLogBuffer、android_log_processBinaryLogBuffer和android_log_printLogLine的实现。 首先分析函数android_log_processLogBuffer的实现,它是用来解析类型为main、system和radio的日志记录的,如下所示。 **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; } ~~~ 日志记录的优先级字段的长度为1个字节,保存在logger_entry 结构体buf内部的缓冲区msg的第一个字节中。日志记录的标签字段从logger_entry 结构体buf内部的缓冲区msg的第二个字节开始,一直到后面的‘\0’字符为止,因此,第19行可以通过调用函数strlen来计算它的长度。logger_entry 结构体buf内部的缓冲区msg 剩余的字节即为日志记录的内容字段,它的长度等于缓冲区的总长度减去日志记录标签的长度,再减去1个字节的日志记录优先级,以及2个字符串结束字符‘\0’,其中一个是日志记录标签字段的,另一个是日志记录内容字段的。 接下来,我们继续分析函数android_log_processBinaryLogBuffer的实现,它是用来解析类型为events的日志记录的,我们分段来阅读。 **system/core/liblog/logprint.c** ~~~ /** * 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) { size_t inCount; unsigned int tagIndex; const unsigned char* eventData; entry->tv_sec = buf->sec; entry->tv_nsec = buf->nsec; entry->priority = ANDROID_LOG_INFO; entry->pid = buf->pid; entry->tid = buf->tid; /* * Pull the tag out. */ eventData = (const unsigned char*) buf->msg; inCount = buf->len; if (inCount < 4) return -1; tagIndex = get4LE(eventData); eventData += 4; inCount -= 4; if (map != NULL) { entry->tag = android_lookupEventTag(map, tagIndex); } else { entry->tag = NULL; } ~~~ 第17行到21行代码用来初始化AndroidLogEntry 结构体entry中的日志记录写入时间、写入进程ID以及优先级等信息。从前面4.1小节中的图4-3可以知道,类型为events的日志记录是没有优先级这个字段的,但是为了和其他类型的日志记录统一处理,Logcat工具将它们的优先级设置为ANDROID_LOG_INFO。 第26行得到logger_entry 结构体buf内部的缓冲区msg的地址,并且保存在变量eventData中。由于类型为events的日志记录的标签是使用一个整数值来描述的,并且保存在logger_entry 结构体buf内部的缓冲区msg的前面4个字节中,因此第30行就调用函数get4LE将它获取回来,并且保存在变量tagIndex中。 函数get4LE的实现如下所示。 **system/core/liblog/logprint.c** ~~~ /* * Extract a 4-byte value from a byte stream. */ static inline uint32_t get4LE(const uint8_t* src) { return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24); } ~~~ 它只是简单地将缓冲区src前面4个字节的内容组合在一起形成一个整数,然后返回给调用者。 回到函数android_log_processBinaryLogBuffer中,接下来第34行检查参数map的值是否为NULL。如果是,就直接将AndroidLogEntry 结构体entry的成员变量tag设置为NULL,表示当前正在处理的一个日志记录的标签值没有对应的描述字符串;否则,第35行就会调用函数android_lookupEventTag在参数map中找到与日志记录标签值tagIndex对应的描述字符串。 > 参数map指向一个EventTagMap结构体,它是Logcat工具在启动时,通过解析目标设备上的/system/etc/event-log-tags文件得到的。因此,Logcat工具就可以通过它来找到与日志记录标签值tagIndex对应的描述字符串。 数android_lookupEventTag的实现如下所示。 **system/core/liblog/event_tag_map.c** ~~~ /* * Look up an entry in the map. * * The entries are sorted by tag number, so we can do a binary search. */ const char* android_lookupEventTag(const EventTagMap* map, int tag) { int hi, lo, mid; lo = 0; hi = map->numTags-1; while (lo <= hi) { int cmp; mid = (lo+hi)/2; cmp = map->tagArray[mid].tagIndex - tag; if (cmp < 0) { /* tag is bigger */ lo = mid + 1; } else if (cmp > 0) { /* tag is smaller */ hi = mid - 1; } else { /* found */ return map->tagArray[mid].tagStr; } } return NULL; } ~~~ 在前面的4.6.2小节中介绍Logcat工具的初始化过程时提到,Logcat工具解析完成目标设备上的/system/etc/event-log-tags文之后,就得到了一个日志记录标签描述表,表中描述了与每一个日志标签所对应的描述字符串,最后会按照标签值从小到大的顺序保存在EventTagMap结构体内部EventTag结构体数组tagArray中。因此,函数android_lookupEventTag就可以使用二分法从这个数组中得到与日志记录标签值tag对应的描述字符串,并且将它返回给调用者。 回到函数android_log_processBinaryLogBuffer中,继续往下执行。 **system/core/liblog/logprint.c** ~~~ /* * If we don't have a map, or didn't find the tag number in the map, * stuff a generated tag value into the start of the output buffer and * shift the buffer pointers down. */ if (entry->tag == NULL) { int tagLen; tagLen = snprintf(messageBuf, messageBufLen, "[%d]", tagIndex); entry->tag = messageBuf; messageBuf += tagLen+1; messageBufLen -= tagLen+1; } /* * Format the event log data into the buffer. */ char* outBuf = messageBuf; size_t outRemaining = messageBufLen-1; /* leave one for nul byte */ int result; result = android_log_printBinaryEvent(&eventData, &inCount, &outBuf, &outRemaining); ~~~ 如果前面没有在EventTagMap结构体map中找到即将要输出的日志记录的标签值描述字符串,即第44行的if语句为true,那么第47行就将该标签值作为要输出的日志记录的标签值描述字符串。接着第59行调用函数android_log_printBinaryEvent继续解析日志记录的内容字段。 要输出的日志记录解析完成之后,输出结果就保存在缓冲区messageBuf中。在调用函数android_log_printBinaryEvent来解析要输出的日志记录的内容字段之前,第57行会在缓冲区messageBuf的后面保留一个字节,用来保存一个字符串结束字符‘\0’。这样,Logcat工具就可以将缓冲区messageBuf的内容作为一个字符串输出到文件或者打印到标准输出中。 函数android_log_printBinaryEvent的实现如下所示。 **system/core/liblog/logprint.c** ~~~ /* * Recursively convert binary log data to printable form. * * This needs to be recursive because you can have lists of lists. * * If we run out of room, we stop processing immediately. It's important * for us to check for space on every output element to avoid producing * garbled output. * * Returns 0 on success, 1 on buffer full, -1 on failure. */ static int android_log_printBinaryEvent(const unsigned char** pEventData, size_t* pEventDataLen, char** pOutBuf, size_t* pOutBufLen) { const unsigned char* eventData = *pEventData; size_t eventDataLen = *pEventDataLen; char* outBuf = *pOutBuf; size_t outBufLen = *pOutBufLen; unsigned char type; size_t outCount; int result = 0; if (eventDataLen < 1) return -1; type = *eventData++; eventDataLen--; //fprintf(stderr, "--- type=%d (rem len=%d)\n", type, eventDataLen); switch (type) { case EVENT_TYPE_INT: /* 32-bit signed int */ { int ival; if (eventDataLen < 4) return -1; ival = get4LE(eventData); eventData += 4; eventDataLen -= 4; outCount = snprintf(outBuf, outBufLen, "%d", ival); if (outCount < outBufLen) { outBuf += outCount; outBufLen -= outCount; } else { /* halt output */ goto no_room; } } break; case EVENT_TYPE_LONG: /* 64-bit signed long */ { long long lval; if (eventDataLen < 8) return -1; lval = get8LE(eventData); eventData += 8; eventDataLen -= 8; outCount = snprintf(outBuf, outBufLen, "%lld", lval); if (outCount < outBufLen) { outBuf += outCount; outBufLen -= outCount; } else { /* halt output */ goto no_room; } } break; case EVENT_TYPE_STRING: /* UTF-8 chars, not NULL-terminated */ { unsigned int strLen; if (eventDataLen < 4) return -1; strLen = get4LE(eventData); eventData += 4; eventDataLen -= 4; if (eventDataLen < strLen) return -1; if (strLen < outBufLen) { memcpy(outBuf, eventData, strLen); outBuf += strLen; outBufLen -= strLen; } else if (outBufLen > 0) { /* copy what we can */ memcpy(outBuf, eventData, outBufLen); outBuf += outBufLen; outBufLen -= outBufLen; goto no_room; } eventData += strLen; eventDataLen -= strLen; break; } case EVENT_TYPE_LIST: /* N items, all different types */ { unsigned char count; int i; if (eventDataLen < 1) return -1; count = *eventData++; eventDataLen--; if (outBufLen > 0) { *outBuf++ = '['; outBufLen--; } else { goto no_room; } for (i = 0; i < count; i++) { result = android_log_printBinaryEvent(&eventData, &eventDataLen, &outBuf, &outBufLen); if (result != 0) goto bail; if (i < count-1) { if (outBufLen > 0) { *outBuf++ = ','; outBufLen--; } else { goto no_room; } } } if (outBufLen > 0) { *outBuf++ = ']'; outBufLen--; } else { goto no_room; } } break; default: fprintf(stderr, "Unknown binary event type %d\n", type); return -1; } bail: *pEventData = eventData; *pEventDataLen = eventDataLen; *pOutBuf = outBuf; *pOutBufLen = outBufLen; return result; no_room: result = 1; goto bail; } ~~~ 函数android_log_printBinaryEvent正好是在前面4.5小节中介绍的四个日志记录写入函数android_util_EventLog_writeEvent_Integer、android_util_EventLog_writeEvent_Long、android_util_EventLog_writeEvent_String和android_util_EventLog_writeEvent_Array的逆操作,因此,读者可以对照这四个函数来分析函数android_log_printBinaryEvent的实现。简单来说,如果日志记录内容只有一个值,那么函数android_log_printBinaryEvent就会根据它的类型(整数、长整数和字符串)将它从输入缓冲区pEventData取回来,并且保存到输出缓冲区outBuf中。当日志记录的内容是一个列表时,函数android_log_printBinaryEvent就会通过递归调用自己来依次遍历列表中的数据。如果得到的数据是一个整数、长整数或者字符串,那么处理方式就与前面一样;否则,当得到的数据又是一个列表时,函数android_log_printBinaryEvent就会继续对这个子列表执行同样的遍历操作。 函数android_log_printBinaryEvent执行完成之后,如果返回值等于0,那么就说明日志记录解析成功;否则,就说明解析过程中出现了问题,其中,返回值等于-1表示日志记录缓冲区有误,返回值等于1表示输出缓冲区outBuf没有足够的空间来容纳日志记录的内容。 回到函数android_log_processBinaryLogBuffer中,继续往下执行。 **system/core/liblog/logprint.c** ~~~ if (result < 0) { fprintf(stderr, "Binary log entry conversion failed\n"); return -1; } else if (result == 1) { if (outBuf > messageBuf) { /* leave an indicator */ *(outBuf-1) = '!'; } else { /* no room to output anything at all */ *outBuf++ = '!'; outRemaining--; } /* pretend we ate all the data */ inCount = 0; } /* eat the silly terminating '\n' */ if (inCount == 1 && *eventData == '\n') { eventData++; inCount--; } if (inCount != 0) { fprintf(stderr, "Warning: leftover binary log data (%d bytes)\n", inCount); } /* * Terminate the buffer. The NUL byte does not count as part of * entry->messageLen. */ *outBuf = '\0'; entry->messageLen = outBuf - messageBuf; assert(entry->messageLen == (messageBufLen-1) - outRemaining); entry->message = messageBuf; return 0; } ~~~ 变量result的值是调用函数android_log_printBinaryEvent得到的返回值。如果它的值小于0,即第61行的if语句为true,就说明日志记录内容有误,因此,函数android_log_processBinaryLogBuffer就不用进一步对它进行处理了,第63行直接返回错误码-1;如果它的值等于1,即第64行的if语句为true,那么就说明日志记录输出缓冲区messageBuf空间不足,这时候函数android_log_processBinaryLogBuffer就在缓冲区messageBuf的最后一个字节上写入一个号‘!’来说明此种情况。 变量inCount表示原始的日志记录缓冲区中还有多少个字节未处理。如果它的值等于1,并且剩余未处理的字符为‘\n’,那么就说明要输出的是一条正常的日志记录,因为每一条类型为events的日志记录总是以‘\n’字符结束的。如果要输出的日志记录不是这种情况,那么第84行和第85行就会输出一条警告信息来说明此种情况。 前面提到,函数android_log_processBinaryLogBuffer在调用函数android_log_printBinaryEvent来解析要输出的日志记录的内容字段之前,会在日志记录输出缓冲区messageBuf的后面保留一个字节,这个字节就是用来写入一个字符串结束符号‘\0’的,如第92行代码所示。这样,Logcat工具就可以把缓冲区messageBuf的内容当作一个字符串来使用,即可以直接将它以文本的形式输出到文件或者打印到标准输出中。 最后,我们分析函数android_log_printLogLine的实现,它是日志记录输出过程中的最后一个步骤,它的实现如下所示。 **system/core/liblog/logprint.c** ~~~ /** * Either print or do not print log line, based on filter * * Returns count bytes written */ 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; } ~~~ 第17行调用函数android_log_formatLogLine来格式化要输出的日志记录,并且将最后结果保存在缓冲区defaultBuffer中。函数android_log_formatLogLine的实现比较简单,它将要输出的日志记录的内容格式化为“<PREFIX> +MESSAGE+<SUFFIX>”的形式。其中,<PREFIX>和<SUFFIX>的内容取决于Logcat工具在初始化时所设置的日志记录输出格式,即参数p_format的内容;而MESSAGE描述了日志记录的内容字段。有了输出结果缓冲区defaultBuffer之后,第23行到第25行的while循环就调用函数write将它输出到文件描述符fd所描述的目标文件中。 在前面的4.6.2小节中介绍Logcat工具的初始化过程时提到,如果在启动Logcat时,指定了选项v,那么就可以指定日志记录的输出格式。选项v后面所跟的参数可以为“brief”、“process”、“tag”、“thread”、“raw”、“time”、“threadtime”或者“long”。每一个参数都对应一个AndroidLogPrintFormat枚举值,分别为FORMAT_BRIEF、FORMAT_PROCESS、FORMAT_TAG、FORMAT_THREAD、FORMAT_RAW、FORMAT_TIME、FORMAT_THREADTIME和FORMAT_LONG。 下面我们就介绍每一个AndroidLogPrintFormat枚举值所对应的日志记录输出格式。 (1)FORMAT_BRIEF:<PREFIX>和<SUFFIX>的内容分别为“<priority>/<tag>(<pid>): ”和“\n”。 (2)FORMAT_PROCESS:<PREFIX>和<SUFFIX>的内容分别为“<priority>(<pid>) ”和“ (<tag>)\n”。 (3)FORMAT_TAG:<PREFIX>和<SUFFIX>的内容分别为“<priority>/(<tag>): ”和“\n”。 (4)FORMAT_THREAD:<PREFIX>和<SUFFIX>的内容分别为“<priority>(<pid>:<tid>) ”和“\n”。 (5)FORMAT_RAW:<PREFIX>和<SUFFIX>的内容分别为空值和“\n”。 (6)FORMAT_TIME:<PREFIX>和<SUFFIX>的内容分别为“<sec>.<nsec> <priority>/<tag>(<pid>): ”和“\n”。 (7)FORMAT_THREADTIME:<PREFIX>和<SUFFIX>的内容分别为 “<sec>.<nsec> <pid> <tid><priority> <tag>: ”和“\n”。 (8)FORMAT_LONG:<PREFIX>和<SUFFIX>的内容分别为“[ <sec>.<nsec> <pid>:<tid><priority>/<tag> ]”和“\n\n”。 例如,假设应用程序(进程ID为396)中调用Java接口android.util.Log来往Logger日志驱动程序中写入了以下一条日志记录: `android.util.Log.i("LogTag", "Log Content.");` 并且假设Logcat工具在启动时,被指定以FORMAT_BRIEF格式来输出该日志记录的内容,那么Logcat工具从Logger日志驱动程序中读出这条日志记录之后,就会打印出以下的输出: `I/LogTag(396): Log Content.` 至此,日志记录的输出过程就分析完成了。