Android 日志系统分析(三):logcat

一、前言

logcat 作为读取日志的工具,相当于client 的角色;在前两篇文章中,关于 logcat 如何与其他部分沟通获取日志信息的流程已经介绍的比较清晰,本文不在赘述,转而归纳一下 logcat 的一些常用指令,并对其中一些做详细分析

二、命令简介

选项 描述 eg
-s 输出指定 tag 的日志,相当于过滤器表达式 '*:S' logcat -s tag
-f 设置logcat 内容保存的位置,默认是stdout logcat -f sdcard/log.txt
-r 每输出 时轮替日志文件,默认是16 必须配合 -f (暂不明白) logcat -f sdcard/log.txt -r 1
-n 设置日志输出的最大数目, 需要 -r 参数 暂不明白
-v 设置日志消息的输出格式。详见下文 格式化输出 logcat -v thread
-D 输出各个日志缓冲区之间的分隔线 logcat -D ...
-c 清除(清空)所选的缓冲区并退出,默认清除 main、system 和 crash logcat -c / -b all -c
-d 将日志转储到屏幕并退出 logcat -d > log.txt
-e 输出正则匹配的日志消息 logcat -e 匹配数据 -m 5
-m 输出 行后退出 ......
-t 仅输出最新的行数,此选项包括 -d 功能 logcat -t 5
-t ' 输出自指定时间以来的最新行,此选项包括 -d 功能 logcat -t '01-26 20:52:41.820'
-g 获取指定日志缓冲区的大小并退出 logcat -g
-G 设置日志环形缓冲区的大小,可以在结尾处添加 K 或 M logcat -G 2M
-b 加载可供查看的日志缓冲区,更多可见下文 日志缓冲区 logcat -b system
-B 以二进制文件形式输出日志 ......
-S 在输出中包含统计信息,以识别和定位日志垃圾信息发送者 ......
--pid= 仅输出来自给定 PID 的日志 logcat --pid=4355

三、日志缓冲区

Android 日志系统为日志消息保留了多个环形缓冲区,但并非多有的日志消息都会发送到默认的环形缓冲区。这里可以采用 logcat -b 命令查看设备的其他缓冲区:

缓冲区 描述 eg
radio 输出通信系统的日志,包含无线装置/电话相关消息 logcat -b radio
events 输出event模块的日志 logcat -b events
main 主日志缓冲区(默认),不包含系统和崩溃日志消息 logcat -b main
system 输出系统日志 logcat -b system
crash 输出崩溃日志 logcat -b crash
all 输出所有缓冲区日志 logcat -b all
default 输出main、system、crash缓冲区日志 logcat -b default

如果需要查看内核空间日志信息,可采用如下几种方式查看:

1、读取 /proc/kmsg ,命令如下

adb shell cat /proc/kmsg

读取/proc/kmsg属于消费型读取,读取之后再次读取不会显示已经读取过的日志信息

2、读取 /dev/kmsg,命令如下

adb shell cat /dev/kmsg

读取/dev/kmsg会显示缓存区里面的所有日志信息。新写入的日志信息会不断累加到日志缓冲器中

3、使用 dmesg 命令读取

adb shell dmesg

dmesg命令读取一次只显示一部分日志,非阻塞执行

四、格式化输出

使用 -v 命令来修改 log 的输出格式,以显示特定的元数据字段:

格式 描述 eg
brief 显示优先级、标记以及发出消息的进程的 PID ......
long 显示所有元数据字段,并使用空白行分隔消息 ......
process 仅显示 PID ......
raw 显示不包含其他元数据字段的原始日志消息 ......
tag 仅显示优先级和标记 ......
thread 旧版格式,显示优先级、PID 以及发出消息的线程的 TID ......
threadtime (默认值)显示日期、调用时间、优先级、标记、PID 以及发出消息的线程的 TID ......
time 显示日期、调用时间、优先级、标记以及发出消息的进程的 PID ......
color 使用不同的颜色来显示每个优先级 ......
descriptive 显示日志缓冲区事件说明。此修饰符仅影响事件日志缓冲区消息,不会对其他非二进制文件缓冲区产生任何影响 ......
epoch 显示自 1970 年 1 月 1 日以来的时间(以秒为单位) ......
monotonic 显示自上次启动以来的时间(以 CPU 秒为单位) ......
printable 确保所有二进制日志记录内容都进行了转义 ......
uid 如果访问控制允许,则显示 UID 或记录的进程的 Android ID ......
usec 显示精确到微秒的时间 ......
UTC 显示 UTC 时间 ......
year 将年份添加到显示的时间 ......
zone 将本地时区添加到显示的时间 ......

优先级:

选项 描述 eg
V –Verbose(最低优先级) adb logcat *:v
D – Debug adb logcat *:d
I – Info adb logcat *:i
W – Warning adb logcat *:w
E – Error adb logcat *:e
F – Fatal adb logcat *:f
S – Silent adb logcat *:s

五、logcat -f 命令详解

logcat -f 命令可以将日志消息输出到指定的文件中。这里我们需要确定的一件事是 logcat 作为客户端的角色,会将通过 liblog 获得的日志信息进行格式解析、格式化处理,而 liblog 库本身并不存在保存、解析的功能。这里来对 -f 指令做一下解析:

logcat_main.cpp # main()
    ---> logcat.cpp # android_logcat_run_command()
        ---> __logcat()
            {
                ......

                case 'f':
                    if ((tail_time == log_time::EPOCH) && !tail_lines) {
                        tail_time = lastLogTime(optctx.optarg);
                    }
                    // redirect output to a file
                    context->outputFileName = optctx.optarg;   //注释 ①
                    break;

                ......


                setupOutputAndSchedulingPolicy()   //注释 ②

 
                 while (...) {   //注释 ③

                    int ret = android_logger_list_read(logger_list, &log_msg);
                    
                    if (context->printBinary) {
                        printBinary(context, &log_msg);
                    } else {
                        processBuffer(context, dev, &log_msg);
                    }

                ......

            }

5.1 注释① :解析 -f 指令

          case 'f':
                  if ((tail_time == log_time::EPOCH) && !tail_lines) {
                        tail_time = lastLogTime(optctx.optarg);
                    }
                    // redirect output to a file
                    context->outputFileName = optctx.optarg;   //注释 ①
                    break;

_logcat() 函数中解析 -f 指令,设置日志输出文件。例如 logcat -f sdcard/log.txt ,则 context->outputFileName 赋值为 sdcard/log.txt

5.2 注释② :设置输出路径

static void setupOutputAndSchedulingPolicy(
    android_logcat_context_internal* context, bool blocking) {
    
    if (!context->outputFileName) return;

    ......
  
    // 打开文件获得 fd 
    context->output_fd = openLogFile(context->outputFileName);

    if (context->output_fd < 0) {
        logcat_panic(context, HELP_FALSE, "couldn't open output file");
        return;
    }

    ......
}

5.3 注释③ :写入日志


               while (...) {   
                                // 调用 liblog 库中的 android_logger_list_read 函数获取日志 
                    int ret = android_logger_list_read(logger_list, &log_msg);
                    
                    if (context->printBinary) {       
                                    // 根据上面获取的文件 fd ,将日志消息写入文件
                        printBinary(context, &log_msg);
                    } else {
                        processBuffer(context, dev, &log_msg);
                    }

printBinary() 函数为例:

logcat.cpp # printBinary() :

void printBinary(android_logcat_context_internal* context, struct log_msg* buf) {
    size_t size = buf->len();

    TEMP_FAILURE_RETRY(write(context->output_fd, buf, size));
}

参考

[ 1 ] Android物语:logcat
[ 2 ] android调试——logcat详解
[ 3 ] 玩转Android10源码开发定制(12)内核篇之logcat输出内核日志

你可能感兴趣的:(Android 日志系统分析(三):logcat)