Android 7系统日志(五)日志读取—logcat源码深度分析
系列目录:第一篇:全景图与架构概览| 第二篇:logd守护进程—启动、初始化与Socket通信 | 第三篇:liblog库—日志写入的完整链路 | 第四篇:日志写入接口—Java层与Native层 | 第五篇:日志读取—logcat源码深度分析 | 第六篇:日志缓冲区管理—容量、裁剪与统计机制 | 第七篇:实战调试与常见问题分析
从源码角度解释 logcat 各种参数的工作原理。Android 7 中 logcat 是单个 C++ 文件(约 1300 行),配合logprint库完成格式化与过滤。
一、logcat 源码概览
system/core/logcat/ ├── logcat.cpp ← 主源文件(1327 行) ├── event.logtags ← events tag 映射表 ├── logcatd.rc ← logcatd 服务 rc 文件 ├── logpersist ← 日志持久化脚本 └── tests/ ← 测试涉及的关键库文件:
system/core/liblog/logprint.c ← 格式化和过滤逻辑 system/core/liblog/logger_read.c ← 日志读取 API(android_logger_list_read) system/core/include/log/logprint.h ← 格式化/过滤 API 头文件 system/core/include/log/log_read.h ← 日志读取 API 头文件关键点:logcat 本身不直接操作 socket,而是通过
liblog提供的 API(android_logger_list_alloc、android_logger_open、android_logger_list_read)来读取日志。格式化和过滤逻辑也不在 logcat.cpp 中,而是在liblog/logprint.c中。
二、main() 入口 — 三层结构
intmain(intargc,char**argv){// ===== 阶段1:参数解析(getopt_long) =====// 解析 -b/-v/-c/-g/-d/-t/-T/-e/-m/-s/-f/-n/-r/-L/-B/-S/-p/-P/-G/-D 等// ===== 阶段2:初始化日志读取器 =====// 2.1 确定缓冲区列表(默认 main + system + crash)// 2.2 设置输出格式(默认 threadtime)// 2.3 解析过滤器(命令行 / ANDROID_LOG_TAGS / 内核 cmdline)// 2.4 分配 logger_list,为每个缓冲区 open logger// ===== 阶段3:命令模式或读取循环 =====// 如果是 -c/-g/-G/-p/-P/-S:执行命令后退出// 否则进入主循环:android_logger_list_read() → 过滤 → 格式化 → 输出}三、-b 参数 — 缓冲区选择
3.1 默认缓冲区
// 未指定 -b 时的默认值(logcat.cpp 第1035-1046行)if(!devices){dev=devices=newlog_device_t("main",false);g_devCount=1;if(android_name_to_log_id("system")==LOG_ID_SYSTEM){dev=dev->next=newlog_device_t("system",false);g_devCount++;}if(android_name_to_log_id("crash")==LOG_ID_CRASH){dev=dev->next=newlog_device_t("crash",false);g_devCount++;}}默认读取
main + system + crash三个缓冲区,不是只读 main。
3.2 缓冲区名称映射
通过android_name_to_log_id()和android_log_id_to_name()做名称 ↔ ID 的转换:
// liblog 内部映射(logger_write.c 中定义)"main"→ LOG_ID_MAIN=0"radio"→ LOG_ID_RADIO=1"events"→ LOG_ID_EVENTS=2"system"→ LOG_ID_SYSTEM=3"crash"→ LOG_ID_CRASH=4// kernel 和 security 不暴露给普通 logcat3.3 特殊值 “all” 和 “default”
// -b 解析逻辑(第843-861行)while((optarg=strtok(optarg,",:; \t\n\r\f"))!=NULL){if(strcmp(optarg,"default")==0){idMask|=(1<<LOG_ID_MAIN)|(1<<LOG_ID_SYSTEM)|(1<<LOG_ID_CRASH);}elseif(strcmp(optarg,"all")==0){idMask=(unsigned)-1;// 全部缓冲区}else{log_id_t log_id=android_name_to_log_id(optarg);idMask|=(1<<log_id);}}支持逗号/冒号/分号分隔:
-b main,system和-b main -b system等效。
3.4 binary 标记
events和security缓冲区自动标记为 binary 模式,读取时走android_log_processBinaryLogBuffer()解码路径:
boolbinary=!strcmp(name,"events")||!strcmp(name,"security");3.5 常用命令
logcat# 默认 -b main,system,crashlogcat-bevents# 只看事件日志logcat-bmain-bsystem# 同时看 main 和 system(可多次 -b)logcat-ball# 所有缓冲区logcat-bdefault# main + system + crash(同默认)四、-v 参数 — 输出格式
4.1 格式枚举(logprint.h)
typedefenum{FORMAT_OFF=0,FORMAT_BRIEF,// 基础格式FORMAT_PROCESS,// 只显示进程FORMAT_TAG,// 只显示 tagFORMAT_THREAD,// 显示线程FORMAT_RAW,// 原始消息FORMAT_TIME,// 带时间FORMAT_THREADTIME,// ★ 时间 + PID + TID + 级别 + TAG(默认)FORMAT_LONG,// 多行详细格式// 以下为修饰符,可与上述格式组合FORMAT_MODIFIER_COLOR,// 按级别着色FORMAT_MODIFIER_TIME_USEC,// 微秒精度(默认毫秒)FORMAT_MODIFIER_PRINTABLE,// 非可打印字符转义FORMAT_MODIFIER_YEAR,// 添加年份FORMAT_MODIFIER_ZONE,// 添加时区FORMAT_MODIFIER_EPOCH,// 以 Unix 时间戳显示FORMAT_MODIFIER_MONOTONIC,// 以启动后的时间显示FORMAT_MODIFIER_UID,// 添加 UID}AndroidLogPrintFormat;4.2 默认格式
// 源码第1064行:默认是 threadtime,不是 briefsetLogFormat("threadtime");4.3 各格式示例输出
| 格式 | 示例输出 |
|---|---|
| brief | D/MyTag(12345): hello world |
| process | D(12345) hello world |
| tag | D/MyTag: hello world |
| thread | D(12345:0x3039) hello world |
| raw | hello world |
| time | 01-15 14:30:00.123 D/MyTag(12345): hello world |
| threadtime | 01-15 14:30:00.123 12345 12345 D MyTag: hello world |
| long | [ 01-15 14:30:00.123 12345:12345 D/MyTag ]hello world(多行,含空行分隔) |
| color | 同 brief,但级别字符带 ANSI 颜色 |
| usec | 时间戳精度为微秒(默认毫秒) |
| epoch | 1234567.890 D/MyTag(12345): hello world |
| monotonic | 从启动开始计时 |
| printable | 不可打印字符显示为\xXX |
4.4 格式可以组合
# 多个修饰符可以叠加logcat-vthreadtime,color,usec,year,zone五、过滤机制
5.1 过滤 API(logprint.h / logprint.c)
logcat 不直接实现过滤逻辑,而是调用logprint库的 API:
// 添加过滤规则intandroid_log_addFilterRule(AndroidLogFormat*p_format,constchar*filterExpression);intandroid_log_addFilterString(AndroidLogFormat*p_format,constchar*filterString);// 判断某条日志是否应该输出intandroid_log_shouldPrintLine(AndroidLogFormat*p_format,constchar*tag,android_LogPriority pri);5.2 过滤规则语法
<tag>[:priority] 例如: MyTag:V → MyTag 的日志级别 >= VERBOSE 时输出 MyTag:D → MyTag 的日志级别 >= DEBUG 时输出 *:S → 默认静默(不输出任何未匹配的日志) *:V → 默认全部输出单独的
<tag>等价于<tag>:V,单独的*等价于*:D。
5.3 过滤规则来源(优先级从高到低)
- 命令行参数:
logcat MyTag:D *:S - 环境变量
ANDROID_LOG_TAGS:命令行未指定时使用 - 内核 cmdline
androidboot.logcat=:-Q模式专用
5.4 processBuffer() — 实际过滤与输出流程
staticvoidprocessBuffer(log_device_t*dev,structlog_msg*buf){AndroidLogEntry entry;// 步骤1:解析日志消息if(dev->binary){// events/security 缓冲区:用 EventTagMap 解码二进制格式err=android_log_processBinaryLogBuffer(&buf->entry_v1,&entry,eventTagMap,binaryMsgBuf,sizeof(binaryMsgBuf));}else{// 普通缓冲区:直接解析 text 格式err=android_log_processLogBuffer(&buf->entry_v1,&entry);}// 步骤2:过滤 — 调用 logprint 库的 APIif(android_log_shouldPrintLine(g_logformat,entry.tag,entry.priority)){// 步骤3:正则过滤(-e 参数)boolmatch=regexOk(entry);g_printCount+=match;if(match||g_printItAnyways){// 步骤4:格式化并输出 — 调用 logprint 库的 APIbytesWritten=android_log_printLogLine(g_logformat,g_outFD,&entry);}}// 步骤5:检查是否需要文件轮转(-r 参数)if(g_logRotateSizeKBytes>0&&...){rotateLogs();}}关键:
formatBuf()函数在 logcat.cpp 中不存在。格式化由android_log_printLogLine()完成,该函数内部调用android_log_formatLogLine()生成字符串后再写入 fd。
六、日志读取核心循环
6.1 liblog 读取 API
logcat 通过 liblog 的 API 读取日志,不直接操作 socket:
// 1. 分配 logger_liststructlogger_list*logger_list;if(tail_time!=log_time::EPOCH){logger_list=android_logger_list_alloc_time(mode,tail_time,pid);}else{logger_list=android_logger_list_alloc(mode,tail_lines,pid);}// 2. 为每个缓冲区打开 loggerfor(dev=devices;dev;dev=dev->next){dev->logger=android_logger_open(logger_list,android_name_to_log_id(dev->device));}// 3. 主循环 — 阻塞读取while(!g_maxCount||(g_printCount<g_maxCount)){structlog_msglog_msg;intret=android_logger_list_read(logger_list,&log_msg);// ... 处理 ...}6.2 log_device_t 结构
structlog_device_t{constchar*device;// 缓冲区名称("main", "system", ...)boolbinary;// 是否为二进制格式(events/security)structlogger*logger;// liblog 读取句柄structlogger_list*logger_list;boolprinted;// 是否已打印过分隔线log_device_t*next;// 链表指针};6.3 多缓冲区交替输出
当读取多个缓冲区时,logcat 在切换缓冲区时打印分隔线(-D参数强制显示):
--------- beginning of main --------- beginning of system分隔线仅在g_devCount > 1时输出(-D强制输出)。
七、其他重要参数
7.1 -d / -t / -T — 日志拉取模式
| 参数 | 行为 | mode 标志 |
|---|---|---|
-d | dump 日志后退出(非阻塞) | ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK |
-t <N> | 打印最近 N 行后退出(隐含 -d) | 同上 |
-t '<time>' | 打印指定时间之后的日志 | 同上 |
-T <N> | 打印最近 N 行(不隐含 -d,继续等待) | 不设置 NONBLOCK |
7.2 -e / -m — 正则过滤与计数
// -e <regex>:使用 PCRE 正则匹配日志消息内容g_regex=newpcrecpp::RE(optarg);// -m <count>:达到 <count> 条匹配后退出// --print:配合 -e 和 -m,让不匹配的行也输出(但仍按匹配数计数)7.3 -c / -g / -G — 控制命令
// -c:清除日志if(g_outputFileName){// 输出到文件时:删除文件unlink(file);}else{// 正常模式:调用 liblog APIandroid_logger_clear(dev->logger);}// -g:获取缓冲区大小longsize=android_logger_get_log_size(dev->logger);longreadable=android_logger_get_log_readable_size(dev->logger);printf("%s: ring buffer is %ld%sb (%ld%sb consumed), ""max entry is %db, max payload is %db\n",dev->device,value_of_size(size),multiplier_of_size(size),value_of_size(readable),multiplier_of_size(readable),(int)LOGGER_ENTRY_MAX_LEN,(int)LOGGER_ENTRY_MAX_PAYLOAD);// -G <size>:设置缓冲区大小// 支持 K/M/G 后缀,如 -G 256K, -G 1Mandroid_logger_set_log_size(dev->logger,setLogSize);
-g输出示例:main: ring buffer is 256Kb (15Kb consumed), max entry is 5120b, max payload is 4069b
7.4 -L — 上次启动前的日志(pstore)
case'L':mode|=ANDROID_LOG_PSTORE;break;从 pstore 读取上次崩溃前的日志,而不连接 logd。
7.5 -f / -r / -n — 文件输出与轮转
// -f <file>:输出到文件(默认 stdout)g_outputFileName=optarg;// -r <kbytes>:每 <kbytes> KB 轮转一次(需配合 -f)g_logRotateSizeKBytes=...// -n <count>:最多保留 <count> 个轮转文件(默认 4)g_maxRotatedLogs=DEFAULT_MAX_ROTATED_LOGS;// 4轮转命名:file.01, file.02, ...
7.6 -B — 二进制输出
// -B:直接输出原始二进制数据,不解析voidprintBinary(structlog_msg*buf){size_t size=buf->len();TEMP_FAILURE_RETRY(write(g_outFD,buf,size));}7.7 -s — 静默模式
case's':// 等同于在过滤器末尾添加 *:Sandroid_log_addFilterRule(g_logformat,"*:s");break;7.8 --pid= — 按进程过滤
if(long_options[option_index].name==pid_str){getSizeTArg(optarg,&pid,1);}// 传递给 android_logger_list_alloc(mode, tail_lines, pid)// 在 liblog 层按 PID 过滤八、完整调用链(从 logcat 到 logd)
logcat main() │ ├── android_logger_list_alloc(mode, tail_lines, pid) │ └── calloc + 初始化 logger_list 结构 │ ├── android_logger_open(logger_list, log_id) │ └── 创建 socket(PF_UNIX, SOCK_SEQPACKET) │ connect("/dev/socket/logdr") ← logd 读取 socket │ 发送 "logid <id>" + "tail <N>" 命令 │ └── 主循环: android_logger_list_read(logger_list, &log_msg) │ └── recvmsg(logdr_fd) ← 从 logd 接收日志 │ ▼ processBuffer(dev, &log_msg) ├── android_log_processLogBuffer() ← 解析日志条目 ├── android_log_shouldPrintLine() ← 过滤检查 ├── regexOk() ← 正则匹配(-e) └── android_log_printLogLine() ← 格式化 + 写入 fdlogcat 通过 liblog 的
android_logger_list_read()API 从 logd 读取日志,底层通过/dev/socket/logdrsocket 通信。logd 端由LogReader线程响应,从LogBuffer中取出日志条目发送。
九、常用命令速查
# 基础用法logcat# 默认 -b main,system,crash,threadtime 格式logcat-vtime# 带时间戳logcat-vthreadtime# 默认格式(推荐)# 缓冲区选择logcat-bradio# 只看 radiologcat-bevents# 只看事件日志logcat-ball# 所有缓冲区# 过滤logcat MyTag:V *:S# 只看 MyTaglogcat *:E# 只看 Error 级别以上logcat-sMyTag# 同 MyTag:V *:Slogcat-e"regex_pattern"# 正则匹配消息内容logcat-m100# 最多输出 100 条logcat-e"error"-m50--print# 匹配50条,但所有行都显示# 时间/尾部logcat-d# dump 日志后退出logcat-t100# 最近 100 行logcat-t'01-15 14:30:00.000'# 指定时间之后logcat-T100# 最近 100 行(不退出,继续等待)# 输出控制logcat-c# 清除日志logcat-g# 查看缓冲区大小logcat-G512K# 设置缓冲区大小为 512KBlogcat-f/sdcard/log.txt# 输出到文件logcat-r1024-n5# 轮转 5 个文件,每个 1MBlogcat-B# 原始二进制输出logcat-L# 上次启动前的日志logcat-D# 显示缓冲区切换分隔线# 格式组合logcat-vthreadtime,color,usec# 带颜色 + 微秒精度logcat-vepoch,uid# Unix时间戳 + 显示UID# 综合示例logcat-vthreadtime-bmain-bsystem MyApp:D *:S十、本篇总结
- logcat 是单文件 C++ 程序(1327 行),命令行解析用
getopt_long - 格式化与过滤逻辑在
liblog/logprint.c中,logcat 通过 API 调用 - 默认读取
main + system + crash三个缓冲区,不是只读 main - 默认输出格式是
threadtime,不是 brief - 通过
android_logger_list_read()从 logd 的/dev/socket/logdr读取日志 - 格式支持 8 种基本格式 + 7 种修饰符,修饰符可以组合(如
-v threadtime,color,usec,year) - 过滤通过
android_log_shouldPrintLine()实现,支持 TAG:LEVEL 语法 - 支持 PCRE 正则过滤(
-e)和计数限制(-m) -c/-g/-G等控制命令通过 liblog API 发送到 logd-t和-T的区别:-t隐含-d(读完后退出),-T不退出
下一篇将分析日志缓冲区的容量管理、裁剪与统计机制。
