系列目录 :第一篇:全景图与架构概览 | 第二篇:logd守护进程---启动、初始化与Socket通信 | 第三篇:liblog库---日志写入的完整链路 | 第四篇:日志写入接口---Java层与Native层 | 第五篇:日志读取---logcat源码深度分析 | 第六篇:日志缓冲区管理---容量、裁剪与统计机制 | 第七篇:实战调试与常见问题分析
一、为什么要系统学习 Android 日志模块
每个 Android 开发者的第一行调试代码几乎都是 Log.d("TAG", "hello world")。这个简单的 API 调用背后,藏着一个从 Java 层到 Native 层、跨进程通信、缓冲区管理、裁剪策略逐层堆叠的精密系统。
当你遇到以下问题时,单纯依赖 logcat 的简单用法是不够的:
- "日志丢失" :为什么有些
Log.d明明执行了,logcat却看不到?是缓冲区满了,还是被 chatty 机制合并了? - "日志延迟":大量日志输出时,为什么 logcat 的显示总是慢半拍?
- "内核日志缺失" :
dmesg和logcat显示的日志各自来自哪里?它们之间有什么关系? - "SELinux 限制":为什么某些 Native 进程的日志打不出来?
- "崩溃日志取证":进程 crash 后,还没来得及写入 logd 的日志去哪了?pstore 机制如何兜底?
本系列以 AOSP 7(android-7.1.2_r36) 为基准,从 Log.d() 一直走到 logd 守护进程的缓冲区,逐层拆解 Android 日志子系统。
二、宏观架构:六层模型
┌──────────────────────────────────────────────────────┐
│ 应用层 / 框架层 │
│ Log.d() / Log.e() → android.util.Log │
│ Slog.d() / Slog.e() → android.util.Slog │
│ EventLog.writeEvent() → android.util.EventLog │
├──────────────────────────────────────────────────────┤
│ JNI 桥接层 │
│ android_util_Log.cpp → println_native() │
├──────────────────────────────────────────────────────┤
│ Native liblog 库 │
│ __android_log_buf_write() → logdWrite() │
├──────────────────────────────────────────────────────┤
│ Socket 传输层 │
│ /dev/socket/logdw (写入) │
│ /dev/socket/logdr (读取) │
│ /dev/socket/logd (控制) │
├──────────────────────────────────────────────────────┤
│ logd 守护进程 │
│ LogListener → LogBuffer → LogReader │
│ main / system / events / radio / crash / kernel │
├──────────────────────────────────────────────────────┤
│ 内核层 │
│ /dev/kmsg / pstore (ramoops) │
└──────────────────────────────────────────────────────┘
从"环形缓冲区"到"logd 守护进程"的演进
在 Android 5.0 之前,日志直接依赖内核 logger 驱动,每个缓冲区对应一个 /dev/log/main、/dev/log/system 等设备节点,应用程序和 logcat 通过 write() / read() 系统调用直接操作。
缺陷:
- 内核态管理太重------缓冲区策略写死在内核,修改困难
- 无法按 UID/PID 差异化裁剪
- 扩展新缓冲区必须改内核驱动
从 Android 5.0 起引入 logd 守护进程,将日志管理完全移到用户态。Android 7 中这套架构已成熟稳定------日志的写入、存储、裁剪、读取全部由 logd 负责,通信方式从设备节点改为 Unix domain socket。
三、核心组件"三剑客"
3.1 logd --- 日志管家(守护进程)
logd 是整个日志系统的大脑,由 init 在系统启动早期拉起:
bash
# system/core/logd/logd.rc
service logd /system/bin/logd
socket logd stream 0666 logd logd # 控制命令通道
socket logdr seqpacket 0666 logd logd # 日志读取通道
socket logdw dgram 0222 logd logd # 日志写入通道
group root system readproc
内部维护 七个 LogBuffer (对应 log_id_t 枚举):
| 缓冲区 | 默认大小 | 用途 | 典型生产者 |
|---|---|---|---|
| main | 256KB | 应用层日志 | Log.d()、ALOGD() |
| system | 256KB | 系统框架日志 | Slog.d() |
| events | 256KB | 系统事件(二进制) | EventLog.writeEvent() |
| radio | 256KB | 无线通信日志 | Rlog.d() |
| crash | 256KB | 崩溃日志 | 系统服务 crash |
| security | 256KB | SELinux 审计日志 | LogAudit 线程写入 |
| kernel | 256KB | 内核日志 | LogKlog 从 /dev/kmsg 读取 |
多个核心工作线程:
logd 进程
├── LogListener 线程 → 监听 logdw,接收日志写入
├── LogReader 线程 → 监听 logdr,服务 logcat 读取
├── CommandListener → 监听 logd,处理控制命令
├── LogAudit 线程 → 监听 NETLINK_AUDIT,处理 SELinux 审计日志
└── LogKlog 线程 → 从 /dev/kmsg 读取内核日志
3.2 liblog --- 日志读写库
连接日志生产者与 logd 的桥梁,所有日志接口最终都调用它。
system/core/liblog/
├── logger_write.c // 对外接口:__android_log_buf_write()
├── logger_read.c // 读端核心
├── logd_writer.c // logd 通道写实现(logdWrite)
├── pmsg_writer.c // pstore 内核兜底
├── logd_reader.c // 读取接口
└── event_tag_map.c // events 日志 tag 映射
核心设计:传输器抽象------正常路径走 logd_writer(socket → logd),内核 panic 时走 pmsg_writer(/dev/pmsg0)兜底。
3.3 logcat --- 日志读取工具
Android 7 中 logcat 是单个 C++ 文件:system/core/logcat/logcat.cpp
通过 socket logdr 连接 logd,指定缓冲区、过滤条件后循环读取,按 -v 参数格式化输出到 stdout。
四、七大日志缓冲区
4.1 main
最常用的缓冲区。应用层 Log.d()、Native 层 ALOGD() 写入,纯文本格式。
4.2 system
系统框架专用,Slog.d() 写入。记录 AMS、WMS 等系统级日志。普通应用无写入权限。
4.3 events
二进制格式 的事件缓冲区。通过 EventLog.writeEvent() 写入,用于记录 am_proc_start、am_anr、binder_sample 等系统关键事件。logcat -b events 时会解码为可读文本。
4.4 radio
无线通信日志,Rlog.d() 写入,用于调试 modem、RIL 等问题。
4.5 crash
Android 7 新增,专门存储系统服务崩溃日志(注意:应用层 crash 走 tombstone,不走这里)。
4.6 security
SELinux 审计日志缓冲区。存储 SELinux 权限检查拒绝事件,由 LogAudit 线程从 NETLINK_AUDIT socket 读取后写入。
4.7 kernel
logd 通过 KernelLogger 从 /dev/kmsg 读取内核环形缓冲区日志并转发。dmesg 可直接查看,logcat -b kernel 查看 logd 转发的版本。
五、一次日志写入的宏观路径
以 Log.d("MyTag", "hello") 为例:
T0 APP 调用 Log.d("MyTag", "hello")
↓
T1 Log.java → println_native(LOG_ID_MAIN, DEBUG, "MyTag", "hello")
↓ (JNI 调用)
T2 android_util_Log.cpp → println_native()
↓ 解析参数,拿到 bufID = LOG_ID_MAIN
↓ 调用 __android_log_buf_write(bufID, ...)
T3 liblog/logger_write.c → __android_log_buf_write()
↓ 格式化日志头(android_log_header_t)
↓ 依次调用每个 transport 的 write():
├── logdWrite() → socket send(logdw, ...)
│ ↓
│ ┌────────────────────────────────────────┐
│ │ logd 守护进程 │
│ │ LogListener 线程 → recv(logdw) │
│ │ → 解析日志头 │
│ │ → LogBuffer->log() 写入环形缓冲区 │
│ │ → LogReader 通知等待中的客户端 │
│ └────────────────────────────────────────┘
│
└── pmsgWrite() → /dev/pmsg0 (panic 兜底)
关键数据结构:logger_entry_v4
Socket 传输使用 二进制 header + 文本消息体 的格式。Android 7 中使用的版本是 logger_entry_v4:
源码路径 :system/core/include/log/logger.h
c
struct logger_entry_v4 {
uint16_t len; /* 消息体长度 */
uint16_t hdr_size; /* header 大小 */
int32_t pid; /* 写日志的进程 PID */
uint32_t tid; /* 写日志的线程 ID */
uint32_t sec; /* 时间戳(秒) */
uint32_t nsec; /* 时间戳(纳秒) */
uint32_t lid; /* log_id(main/system/events 等) */
uint32_t uid; /* 写日志的进程 UID */
char msg[0]; /* 消息体(柔性数组) */
} __attribute__((__packed__));
六、一次日志读取的宏观路径
以 adb shell logcat -v time MyTag:* *:S 为例:
T0 adb shell logcat -v time MyTag:* *:S
↓
T1 logcat/logcat.cpp → main()
↓ 解析 -b → log_ids = [LOG_ID_MAIN]
↓ 解析 -v → 格式 = FORMAT_TIME
↓ 解析过滤 → MyTag:*, default:silent
T2 创建 android_logger_list
↓ socket 连接 logdr
T3 发送控制命令:"tail" / "logid"
T4 while (1) {
android_logger_list_read() ← 阻塞读取
↓
(logd 端) LogReader 线程
│ 从 LogBuffer 取出下一条日志
│ socket send(logdr) → 回传
▼
应用过滤条件
↓ 匹配 → formatBuf() 格式化输出
↓ 不匹配 → 跳过
}
七、关键源码文件索引
logd 守护进程
| 文件 | 说明 |
|---|---|
system/core/logd/main.cpp |
logd 入口、参数解析 |
system/core/logd/LogBuffer.cpp |
环形缓冲区实现 |
system/core/logd/LogBufferElement.h |
日志条目数据结构 |
system/core/logd/LogListener.cpp |
监听 logdw socket |
system/core/logd/LogReader.cpp |
监听 logdr socket |
system/core/logd/CommandListener.cpp |
监听 logd socket(控制命令) |
system/core/logd/LogStatistics.cpp |
UID/PID/TAG 维度统计 |
system/core/logd/LogKlog.cpp |
内核日志转发 |
system/core/logd/LogAudit.cpp |
SELinux 审计日志 |
liblog 库
| 文件 | 说明 |
|---|---|
system/core/liblog/logger_write.c |
__android_log_buf_write() |
system/core/liblog/logger_read.c |
读取接口 |
system/core/liblog/logd_writer.c |
logd 通道写实现 |
system/core/liblog/pmsg_writer.c |
pstore 兜底 |
system/core/include/log/log.h |
ALOGD 等宏 |
system/core/include/log/logger.h |
核心结构体 |
logcat 工具
| 文件 | 说明 |
|---|---|
system/core/logcat/logcat.cpp |
命令行解析、读取、格式化 |
Java 层
| 文件 | 说明 |
|---|---|
frameworks/base/core/java/android/util/Log.java |
应用层日志接口 |
frameworks/base/core/java/android/util/Slog.java |
系统框架日志接口 |
frameworks/base/core/java/android/util/EventLog.java |
事件日志接口 |
frameworks/base/core/jni/android_util_Log.cpp |
Log.java 的 JNI 实现 |
八、各篇预告
| 篇目 | 聚焦 |
|---|---|
| 第二篇 | logd 启动流程、四个 Socket 的创建与权限、LogListener/LogReader 线程初始化 |
| 第三篇 | liblog 写入链路的逐函数剖析:__android_log_buf_write → logd_dgram_write |
| 第四篇 | Java 层四大接口(Log/Slog/EventLog/Rlog)和 Native 层宏(ALOGD等)的源码 |
| 第五篇 | logcat 的参数解析、缓冲区选择、过滤匹配、格式化输出 |
| 第六篇 | LogBuffer 环形数据结构、按 UID 裁剪策略、LogStatistics 统计 |
| 第七篇 | 日志丢失/不输出/延迟的根因排查,结合 bugreport、tombstone 的综合调试 |