日志读取—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 ← 日志读取 APIandroid_logger_list_read system/core/include/log/logprint.h ← 格式化/过滤 API 头文件 system/core/include/log/log_read.h ← 日志读取 API 头文件关键点logcat 本身不直接操作 socket而是通过liblog提供的 APIandroid_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){devdevicesnewlog_device_t(main,false);g_devCount1;if(android_name_to_log_id(system)LOG_ID_SYSTEM){devdev-nextnewlog_device_t(system,false);g_devCount;}if(android_name_to_log_id(crash)LOG_ID_CRASH){devdev-nextnewlog_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_MAIN0radio→ LOG_ID_RADIO1events→ LOG_ID_EVENTS2system→ LOG_ID_SYSTEM3crash→ LOG_ID_CRASH4// kernel 和 security 不暴露给普通 logcat3.3 特殊值 “all” 和 “default”// -b 解析逻辑第843-861行while((optargstrtok(optarg,,:; \t\n\r\f))!NULL){if(strcmp(optarg,default)0){idMask|(1LOG_ID_MAIN)|(1LOG_ID_SYSTEM)|(1LOG_ID_CRASH);}elseif(strcmp(optarg,all)0){idMask(unsigned)-1;// 全部缓冲区}else{log_id_t log_idandroid_name_to_log_id(optarg);idMask|(1log_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可多次 -blogcat-ball# 所有缓冲区logcat-bdefault# main system crash同默认四、-v 参数 — 输出格式4.1 格式枚举logprint.htypedefenum{FORMAT_OFF0,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 各格式示例输出格式示例输出briefD/MyTag(12345): hello worldprocessD(12345) hello worldtagD/MyTag: hello worldthreadD(12345:0x3039) hello worldrawhello worldtime01-15 14:30:00.123 D/MyTag(12345): hello worldthreadtime01-15 14:30:00.123 12345 12345 D MyTag: hello worldlong[ 01-15 14:30:00.123 12345:12345 D/MyTag ]hello world多行含空行分隔color同 brief但级别字符带 ANSI 颜色usec时间戳精度为微秒默认毫秒epoch1234567.890 D/MyTag(12345): hello worldmonotonic从启动开始计时printable不可打印字符显示为\xXX4.4 格式可以组合# 多个修饰符可以叠加logcat-vthreadtime,color,usec,year,zone五、过滤机制5.1 过滤 APIlogprint.h / logprint.clogcat 不直接实现过滤逻辑而是调用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命令行未指定时使用内核 cmdlineandroidboot.logcat-Q模式专用5.4 processBuffer() — 实际过滤与输出流程staticvoidprocessBuffer(log_device_t*dev,structlog_msg*buf){AndroidLogEntry entry;// 步骤1解析日志消息if(dev-binary){// events/security 缓冲区用 EventTagMap 解码二进制格式errandroid_log_processBinaryLogBuffer(buf-entry_v1,entry,eventTagMap,binaryMsgBuf,sizeof(binaryMsgBuf));}else{// 普通缓冲区直接解析 text 格式errandroid_log_processLogBuffer(buf-entry_v1,entry);}// 步骤2过滤 — 调用 logprint 库的 APIif(android_log_shouldPrintLine(g_logformat,entry.tag,entry.priority)){// 步骤3正则过滤-e 参数boolmatchregexOk(entry);g_printCountmatch;if(match||g_printItAnyways){// 步骤4格式化并输出 — 调用 logprint 库的 APIbytesWrittenandroid_log_printLogLine(g_logformat,g_outFD,entry);}}// 步骤5检查是否需要文件轮转-r 参数if(g_logRotateSizeKBytes0...){rotateLogs();}}关键formatBuf()函数在 logcat.cpp 中不存在。格式化由android_log_printLogLine()完成该函数内部调用android_log_formatLogLine()生成字符串后再写入 fd。六、日志读取核心循环6.1 liblog 读取 APIlogcat 通过 liblog 的 API 读取日志不直接操作 socket// 1. 分配 logger_liststructlogger_list*logger_list;if(tail_time!log_time::EPOCH){logger_listandroid_logger_list_alloc_time(mode,tail_time,pid);}else{logger_listandroid_logger_list_alloc(mode,tail_lines,pid);}// 2. 为每个缓冲区打开 loggerfor(devdevices;dev;devdev-next){dev-loggerandroid_logger_open(logger_list,android_name_to_log_id(dev-device));}// 3. 主循环 — 阻塞读取while(!g_maxCount||(g_printCountg_maxCount)){structlog_msglog_msg;intretandroid_logger_list_read(logger_list,log_msg);// ... 处理 ...}6.2 log_device_t 结构structlog_device_t{constchar*device;// 缓冲区名称main, system, ...boolbinary;// 是否为二进制格式events/securitystructlogger*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 标志-ddump 日志后退出非阻塞ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK-t N打印最近 N 行后退出隐含 -d同上-t time打印指定时间之后的日志同上-T N打印最近 N 行不隐含 -d继续等待不设置 NONBLOCK7.2 -e / -m — 正则过滤与计数// -e regex使用 PCRE 正则匹配日志消息内容g_regexnewpcrecpp::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获取缓冲区大小longsizeandroid_logger_get_log_size(dev-logger);longreadableandroid_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 4069b7.4 -L — 上次启动前的日志pstorecaseL:mode|ANDROID_LOG_PSTORE;break;从 pstore 读取上次崩溃前的日志而不连接 logd。7.5 -f / -r / -n — 文件输出与轮转// -f file输出到文件默认 stdoutg_outputFileNameoptarg;// -r kbytes每 kbytes KB 轮转一次需配合 -fg_logRotateSizeKBytes...// -n count最多保留 count 个轮转文件默认 4g_maxRotatedLogsDEFAULT_MAX_ROTATED_LOGS;// 4轮转命名file.01, file.02, ...7.6 -B — 二进制输出// -B直接输出原始二进制数据不解析voidprintBinary(structlog_msg*buf){size_t sizebuf-len();TEMP_FAILURE_RETRY(write(g_outFD,buf,size));}7.7 -s — 静默模式cases:// 等同于在过滤器末尾添加 *:Sandroid_log_addFilterRule(g_logformat,*:s);break;7.8 --pid — 按进程过滤if(long_options[option_index].namepid_str){getSizeTArg(optarg,pid,1);}// 传递给 android_logger_list_alloc(mode, tail_lines, pid)// 在 liblog 层按 PID 过滤八、完整调用链从 logcat 到 logdlogcat 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,crashthreadtime 格式logcat-vtime# 带时间戳logcat-vthreadtime# 默认格式推荐# 缓冲区选择logcat-bradio# 只看 radiologcat-bevents# 只看事件日志logcat-ball# 所有缓冲区# 过滤logcat MyTag:V *:S# 只看 MyTaglogcat *:E# 只看 Error 级别以上logcat-sMyTag# 同 MyTag:V *:Slogcat-eregex_pattern# 正则匹配消息内容logcat-m100# 最多输出 100 条logcat-eerror-m50--print# 匹配50条但所有行都显示# 时间/尾部logcat-d# dump 日志后退出logcat-t100# 最近 100 行logcat-t01-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不退出下一篇将分析日志缓冲区的容量管理、裁剪与统计机制。