23、Logcat 与 logd 守护进程:深入理解日志系统的守护进程

聊到 Android 日志系统,很多人第一反应就是 logcat 命令。但说实话,logcat 只是个“前台演员”。真正在幕后默默干活的,是 logd 这个守护进程。

我刚开始接触 Android 系统时,也以为日志就是写写文件、读读缓冲区。直到有一次线上设备日志丢失,我才意识到——logd 才是整个日志系统的核心。今天我们就来扒一扒它。

logd 是什么?

logd 的全称是 Log Daemon(日志守护进程)。它在系统启动时由 init 进程拉起,一旦启动就常驻后台。它的职责很简单:接收日志、管理缓冲区、响应客户端请求

说白了,所有应用和系统组件写的日志,最终都会送到 logd 手里。它再根据日志级别、tag、缓冲区类型,决定存哪里、存多久、给谁看。

核心要点: logd 是 Android 日志系统的“中央处理器”。没有它,logcat 就是个空壳。

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。一旦写满,新日志会覆盖最旧的日志。

我的经验: 在低内存设备上,我习惯把 main 缓冲区调小到 128KB,把 crash 缓冲区调到 512KB。因为崩溃日志比普通日志更有价值,丢了太可惜。

日志写入流程

应用调用 Log.d() 时,背后发生了什么?我画了一张图帮你理解:

日志写入流程 应用 (App) liblog (客户端库) logdw socket Log.d() write() logd 守护进程 接收、解析、分类 内核传递 环形缓冲区 main/system/events/crash 写入 logcat 客户端 读取日志 读取 整个流程:App → liblog → logdw socket → logd → 环形缓冲区 → logcat 读取 应用层 客户端库 守护进程 缓冲区 读取端

流程其实很清晰:

  1. 应用调用 Log.d(TAG, "message")
  2. liblog 库将日志打包成 log_msg 结构体
  3. 通过 logdw socket 发送给 logd
  4. logd 解析日志头,提取 pid、tid、时间戳、tag、优先级
  5. 根据日志 tag 和优先级,决定写入哪个缓冲区
  6. 写入环形缓冲区,更新读索引
注意: 如果 logd 进程挂掉或 socket 通信异常,日志会直接丢弃。系统不会阻塞等待。我曾经遇到过 logd 被 OOM killer 干掉的情况,结果所有应用日志全部丢失,排查问题花了整整两天。

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 的输出会告诉你每个缓冲区的大小、已用空间、丢弃的日志条数等信息。我在排查日志丢失问题时,第一个命令就是这个。

小技巧: 如果怀疑 logd 有问题,可以试试 logcat -b all -g。它会显示每个缓冲区的配置和当前使用率。如果某个缓冲区使用率长期接近 100%,说明日志量太大,需要调整大小或减少日志输出。

常见问题与避坑

最后分享几个我踩过的坑:

  • 日志丢失:logd 缓冲区满了之后,旧日志会被覆盖。如果发现日志不连续,先检查缓冲区大小是否够用
  • logd 崩溃:logd 本身是 native 进程,如果崩溃,init 会自动重启它。但重启期间所有日志都会丢失
  • 权限问题:普通应用只能写日志,不能读。如果调试时需要读取系统日志,记得用 adb 或申请 READ_LOGS 权限
  • 日志延迟:高负载下,logd 处理日志可能变慢。我遇到过游戏场景下日志延迟超过 1 秒的情况,最后通过减少日志输出频率解决

嗯,关于 logd 的内容就讲到这里。它虽然是个“幕后工作者”,但理解它的工作原理,能帮你更好地诊断日志相关的问题。下次遇到日志丢了、慢了、乱了,你就知道该从哪里入手了。


公众号:蓝海资料掘金营,微信 deep3321