Android 7系统日志(一):全景图与架构概览

系列目录第一篇:全景图与架构概览 | 第二篇:logd守护进程---启动、初始化与Socket通信 | 第三篇:liblog库---日志写入的完整链路 | 第四篇:日志写入接口---Java层与Native层 | 第五篇:日志读取---logcat源码深度分析 | 第六篇:日志缓冲区管理---容量、裁剪与统计机制 | 第七篇:实战调试与常见问题分析


一、为什么要系统学习 Android 日志模块

每个 Android 开发者的第一行调试代码几乎都是 Log.d("TAG", "hello world")。这个简单的 API 调用背后,藏着一个从 Java 层到 Native 层、跨进程通信、缓冲区管理、裁剪策略逐层堆叠的精密系统。

当你遇到以下问题时,单纯依赖 logcat 的简单用法是不够的:

  • "日志丢失" :为什么有些 Log.d 明明执行了,logcat 却看不到?是缓冲区满了,还是被 chatty 机制合并了?
  • "日志延迟":大量日志输出时,为什么 logcat 的显示总是慢半拍?
  • "内核日志缺失"dmesglogcat 显示的日志各自来自哪里?它们之间有什么关系?
  • "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() 系统调用直接操作。

缺陷:

  1. 内核态管理太重------缓冲区策略写死在内核,修改困难
  2. 无法按 UID/PID 差异化裁剪
  3. 扩展新缓冲区必须改内核驱动

从 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_startam_anrbinder_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_writelogd_dgram_write
第四篇 Java 层四大接口(Log/Slog/EventLog/Rlog)和 Native 层宏(ALOGD等)的源码
第五篇 logcat 的参数解析、缓冲区选择、过滤匹配、格式化输出
第六篇 LogBuffer 环形数据结构、按 UID 裁剪策略、LogStatistics 统计
第七篇 日志丢失/不输出/延迟的根因排查,结合 bugreport、tombstone 的综合调试