系列目录:第一篇:全景图与架构概览 | 第二篇:logd守护进程—启动、初始化与Socket通信 | 第三篇:liblog库—日志写入的完整链路 | 第四篇:日志写入接口—Java层与Native层 | 第五篇:日志读取—logcat源码深度分析 | 第六篇:日志缓冲区管理—容量、裁剪与统计机制 | 第七篇:实战调试与常见问题分析
从源码角度解释 logcat 各种参数的工作原理。Android 7 中 logcat 是单个 C++ 文件,配合 logprint 库完成格式化与过滤。
一、logcat 源码概览
system/core/logcat/
├── logcat.cpp ← 主源文件(约 1300 行)
├── event.logtags ← events tag 映射表
├── logcatd.rc ← logcatd 服务 rc 文件
├── logpersist ← 日志持久化脚本
└── tests/ ← 测试
涉及的关键库文件:
system/core/liblog/logprint.c ← 格式化和过滤逻辑
system/core/liblog/logger_read.c ← 日志读取 API
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() 入口 — 三层结构
源码路径:system/core/logcat/logcat.cpp
int main(int argc, 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 默认缓冲区
源码路径:system/core/logcat/logcat.cpp
// 未指定 -b 时的默认值
if (!devices) {
dev = devices = new log_device_t("main", false);
g_devCount = 1;
if (android_name_to_log_id("system") == LOG_ID_SYSTEM) {
dev = dev->next = new log_device_t("system", false);
g_devCount++;
}
if (android_name_to_log_id("crash") == LOG_ID_CRASH) {
dev = dev->next = new log_device_t("crash", false);
g_devCount++;
}
}
默认读取
main + system + crash三个缓冲区,不是只读 main。
3.2 缓冲区名称映射
通过 android_name_to_log_id() 和 android_log_id_to_name() 做名称与 ID 的转换:
"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 不暴露给普通 logcat
3.3 特殊值 “all” 和 “default”
// -b 解析逻辑:支持逗号/冒号/分号分隔
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);
} else if (strcmp(optarg, "all") == 0) {
idMask = (unsigned)-1; // 全部缓冲区
} else {
log_id_t log_id = android_name_to_log_id(optarg);
idMask |= (1 << log_id);
}
}
3.4 binary 标记
events 和 security 缓冲区自动标记为 binary 模式,读取时走 android_log_processBinaryLogBuffer() 解码路径:
bool binary = !strcmp(name, "events") || !strcmp(name, "security");
3.5 常用命令
logcat # 默认 -b main,system,crash
logcat -b events # 只看事件日志
logcat -b main -b system # 同时看 main 和 system(可多次 -b)
logcat -b all # 所有缓冲区
logcat -b default # main + system + crash(同默认)
四、-v 参数 — 输出格式
4.1 格式枚举
源码路径:system/core/include/log/logprint.h
typedef enum {
FORMAT_OFF = 0,
FORMAT_BRIEF, // 基础格式
FORMAT_PROCESS, // 只显示进程
FORMAT_TAG, // 只显示 tag
FORMAT_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 默认格式
源码路径:system/core/logcat/logcat.cpp
// 默认是 threadtime,不是 brief
setLogFormat("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 -v threadtime,color,usec,year,zone
五、过滤机制
5.1 过滤 API
源码路径:system/core/include/log/logprint.h
// 添加过滤规则
int android_log_addFilterRule(AndroidLogFormat *p_format, const char *filterExpression);
int android_log_addFilterString(AndroidLogFormat *p_format, const char *filterString);
// 判断某条日志是否应该输出
int android_log_shouldPrintLine(AndroidLogFormat *p_format, const char *tag,
android_LogPriority pri);
logcat 不直接实现过滤逻辑,而是调用 logprint 库的上述 API。
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() — 实际过滤与输出流程
源码路径:system/core/logcat/logcat.cpp
static void processBuffer(log_device_t* dev, struct log_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 库的 API
if (android_log_shouldPrintLine(g_logformat, entry.tag, entry.priority)) {
// 步骤3:正则过滤(-e 参数)
bool match = regexOk(entry);
if (match || g_printItAnyways) {
// 步骤4:格式化并输出 — 调用 logprint 库的 API
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。这两个函数都定义在system/core/liblog/logprint.c中。
六、日志读取核心循环
6.1 liblog 读取 API
源码路径:system/core/liblog/logger_read.c
logcat 通过 liblog 的 API 读取日志,不直接操作 socket:
// 1. 分配 logger_list
struct logger_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. 为每个缓冲区打开 logger
for (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)) {
struct log_msg log_msg;
int ret = android_logger_list_read(logger_list, &log_msg);
// ... 处理 ...
}
6.2 log_device_t 结构
源码路径:system/core/logcat/logcat.cpp
struct log_device_t {
const char* device; // 缓冲区名称("main", "system", ...)
bool binary; // 是否为二进制格式(events/security)
struct logger *logger; // liblog 读取句柄
struct logger_list *logger_list;
bool printed; // 是否已打印过分隔线
log_device_t* next; // 链表指针
};
6.3 多缓冲区交替输出
当读取多个缓冲区时,logcat 在切换缓冲区时打印分隔线:
--------- 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 = new pcrecpp::RE(optarg);
// -m <count>:达到 <count> 条匹配后退出
// --print:配合 -e 和 -m,让不匹配的行也输出(但仍按匹配数计数)
7.3 -c / -g / -G — 控制命令
// -c:清除日志 — 调用 liblog API
android_logger_clear(dev->logger);
// -g:获取缓冲区大小
long size = android_logger_get_log_size(dev->logger);
long readable = 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, ...);
// -G <size>:设置缓冲区大小,支持 K/M/G 后缀
android_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)
// -n <count>:最多保留 <count> 个轮转文件(默认 4)
轮转命名:file.01, file.02, ...
7.6 -B — 二进制输出
// -B:直接输出原始二进制数据,不解析
void printBinary(struct log_msg *buf) {
size_t size = buf->len();
TEMP_FAILURE_RETRY(write(g_outFD, buf, size));
}
7.7 -s — 静默模式
// 等同于在过滤器末尾添加 *:S
android_log_addFilterRule(g_logformat, "*:s");
7.8 --pid= — 按进程过滤
// 传递给 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() ← 格式化 + 写入 fd
logcat 通过 liblog 的
android_logger_list_read()API 从 logd 读取日志,底层通过/dev/socket/logdrsocket 通信。logd 端由LogReader线程响应,从LogBuffer中取出日志条目发送。
九、常用命令速查
# 基础用法
logcat # 默认 -b main,system,crash,threadtime 格式
logcat -v time # 带时间戳
logcat -v threadtime # 默认格式(推荐)
# 缓冲区选择
logcat -b radio # 只看 radio
logcat -b events # 只看事件日志
logcat -b all # 所有缓冲区
# 过滤
logcat MyTag:V *:S # 只看 MyTag
logcat *:E # 只看 Error 级别以上
logcat -s MyTag # 同 MyTag:V *:S
logcat -e "regex_pattern" # 正则匹配消息内容
logcat -m 100 # 最多输出 100 条
logcat -e "error" -m 50 --print # 匹配50条,但所有行都显示
# 时间/尾部
logcat -d # dump 日志后退出
logcat -t 100 # 最近 100 行
logcat -t '01-15 14:30:00.000' # 指定时间之后
logcat -T 100 # 最近 100 行(不退出,继续等待)
# 输出控制
logcat -c # 清除日志
logcat -g # 查看缓冲区大小
logcat -G 512K # 设置缓冲区大小为 512KB
logcat -f /sdcard/log.txt # 输出到文件
logcat -r 1024 -n 5 # 轮转 5 个文件,每个 1MB
logcat -B # 原始二进制输出
logcat -L # 上次启动前的日志
logcat -D # 显示缓冲区切换分隔线
# 格式组合
logcat -v threadtime,color,usec # 带颜色 + 微秒精度
logcat -v epoch,uid # Unix时间戳 + 显示UID
# 综合示例
logcat -v threadtime -b main -b system MyApp:D *:S
十、本篇总结
- logcat 是单文件 C++ 程序(约 1300 行),命令行解析用
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不退出
下一篇将分析日志缓冲区的容量管理、裁剪与统计机制。
399

被折叠的 条评论
为什么被折叠?



