23、Logcat 与 logd 守护进程:深入理解日志系统的守护进程
聊到 Android 日志系统,很多人第一反应就是 logcat 命令。但说实话,logcat 只是个“前台演员”。真正在幕后默默干活的,是 logd 这个守护进程。
我刚开始接触 Android 系统时,也以为日志就是写写文件、读读缓冲区。直到有一次线上设备日志丢失,我才意识到——logd 才是整个日志系统的核心。今天我们就来扒一扒它。
logd 是什么?
logd 的全称是 Log Daemon(日志守护进程)。它在系统启动时由 init 进程拉起,一旦启动就常驻后台。它的职责很简单:接收日志、管理缓冲区、响应客户端请求。
说白了,所有应用和系统组件写的日志,最终都会送到 logd 手里。它再根据日志级别、tag、缓冲区类型,决定存哪里、存多久、给谁看。
logd 的启动与初始化
我们来看看 logd 是怎么启动的。它的启动脚本在 init.rc 中定义,大致长这样:
service logd /system/bin/logd
class core
socket logd stream 0666 logd logd
socket logdr stream 0666 logd logd
socket logdw stream 0222 logd logd
onrestart restart logd
注意这里创建了三个 socket:
- logd:控制 socket,用于配置管理
- logdr:读取 socket,logcat 通过它读取日志
- logdw:写入 socket,应用通过它写入日志
我当年第一次看到这三个 socket 时,心想“至于搞这么复杂吗?”后来才明白,这种设计是为了读写分离、权限隔离。写入 socket 权限宽松(0222),读取和控制 socket 权限严格(0666)。这样普通应用只能写,不能读别人的日志——隐私保护就是这么来的。
日志缓冲区管理
logd 内部维护了多个环形缓冲区。每个缓冲区对应一种日志类型:
| 缓冲区名称 | 对应日志类型 | 典型内容 |
|---|---|---|
| main | 应用日志 | 应用层 Log.d / Log.i 等 |
| system | 系统日志 | 系统服务、WMS、AMS 等 |
| events | 事件日志 | 二进制格式,用于统计和调试 |
| crash | 崩溃日志 | Java 崩溃、Native 崩溃 |
| kernel | 内核日志 | 通过 /proc/kmsg 获取 |
每个缓冲区都是环形队列,大小固定。默认情况下,main 和 system 各 256KB,events 256KB,crash 256KB。一旦写满,新日志会覆盖最旧的日志。
日志写入流程
应用调用 Log.d() 时,背后发生了什么?我画了一张图帮你理解:
流程其实很清晰:
- 应用调用
Log.d(TAG, "message") - liblog 库将日志打包成
log_msg结构体 - 通过
logdwsocket 发送给 logd - logd 解析日志头,提取 pid、tid、时间戳、tag、优先级
- 根据日志 tag 和优先级,决定写入哪个缓冲区
- 写入环形缓冲区,更新读索引
logd 的日志过滤与裁剪
logd 不是无脑存日志的。它内部有一套过滤机制:
- 基于 tag 过滤:某些 tag 的日志可以设置最低级别,低于该级别的不写入
- 基于 uid 过滤:可以为特定应用设置日志配额,防止某个应用刷屏
- 日志裁剪:单条日志超过 4KB 会被截断(我记得 Android 12 以后放宽到了 8KB)
你想想看,如果没有这些限制,某个应用疯狂写日志,几分钟就能把缓冲区撑爆。其他应用的日志全被覆盖,那调试起来就太痛苦了。
logd 与 logcat 的交互
我们平时用 logcat 命令,其实它就是个客户端。它通过 logdr socket 向 logd 发起读取请求。logd 收到请求后,把缓冲区里的日志一条条推送给 logcat。
这里有个细节:logd 支持“尾随模式”(tail mode)。你执行 logcat -s 时,logd 不会把历史日志全发给你,而是只发新产生的日志。这种模式通过 socket 的 poll 机制实现,logd 在缓冲区有新数据时主动通知 logcat。
我个人觉得这个设计很巧妙。它避免了 logcat 轮询缓冲区造成的 CPU 浪费,也保证了日志的实时性。
logd 的调试与监控
如果你想看看 logd 内部的状态,可以用以下命令:
# 查看 logd 内存使用
dumpsys logd
# 查看缓冲区统计信息
logcat -b all -S
# 查看 logd 进程信息
ps -A | grep logd
dumpsys logd 的输出会告诉你每个缓冲区的大小、已用空间、丢弃的日志条数等信息。我在排查日志丢失问题时,第一个命令就是这个。
logcat -b all -g。它会显示每个缓冲区的配置和当前使用率。如果某个缓冲区使用率长期接近 100%,说明日志量太大,需要调整大小或减少日志输出。
常见问题与避坑
最后分享几个我踩过的坑:
- 日志丢失:logd 缓冲区满了之后,旧日志会被覆盖。如果发现日志不连续,先检查缓冲区大小是否够用
- logd 崩溃:logd 本身是 native 进程,如果崩溃,init 会自动重启它。但重启期间所有日志都会丢失
- 权限问题:普通应用只能写日志,不能读。如果调试时需要读取系统日志,记得用 adb 或申请 READ_LOGS 权限
- 日志延迟:高负载下,logd 处理日志可能变慢。我遇到过游戏场景下日志延迟超过 1 秒的情况,最后通过减少日志输出频率解决
嗯,关于 logd 的内容就讲到这里。它虽然是个“幕后工作者”,但理解它的工作原理,能帮你更好地诊断日志相关的问题。下次遇到日志丢了、慢了、乱了,你就知道该从哪里入手了。
公众号:蓝海资料掘金营,微信 deep3321