系列目录第一篇全景图与架构概览| 第二篇logd守护进程—启动、初始化与Socket通信 | 第三篇liblog库—日志写入的完整链路 | 第四篇日志写入接口—Java层与Native层 | 第五篇日志读取—logcat源码深度分析 | 第六篇日志缓冲区管理—容量、裁剪与统计机制 | 第七篇实战调试与常见问题分析前六篇讲透了原理本篇用这些原理解决实际问题。所有案例均来自真实工程场景。一、问题定位速查表现象可能原因排查方法参考原理篇logcat 没有输出日志级别被过滤、缓冲区空了logcat -v threadtime -b all *:V第五篇日志大量丢失缓冲区溢出、裁剪过度logcat -g,logcat -S第六篇“chatty” 消息频繁高频重复日志被裁剪合并检查循环日志代码grep chatty第六篇应用日志不出现误写了 system/events 缓冲区检查 Log/Slog 使用确认 bufID第四篇开机日志丢失logd 启动晚于日志产生检查/sys/fs/pstore/或dmesg第二篇日志时间错乱时钟跳变或 monotonic/realtime 切换检查CLOCK_MONOTONIC与persist.logd.timestamp第一篇logcat 卡住logd 慢速 reader 被阻塞ps -T -p $(pidof logd)查看线程状态第二/六篇内核日志看不到klogd 转发未工作cat /proc/kmsg或dmesg第二篇二、案例一开发中突然看不到日志现象应用正常运行但logcat输出突然停止。重启 logcat 也没用。排查步骤# 步骤1查看缓冲区是否满了adb logcat-g# 输出示例main: ring buffer is 256Kb (250Kb consumed), max entry is 5120b, max payload is 4069b# 如果 consumed 接近 ring buffer is说明缓冲区快满了# 步骤2查看是谁在大量写日志adb logcat-S# 找出 SIZE 最大的 UID 和 TAG# 步骤3如果缓冲区满了临时增大# ★ 注意属性名是 persist.logd.size.buffer不是 logd.size.bufferadb shell setprop persist.logd.size.main 512K# 发送 SIGHUP 让 logd 重新加载配置★ 是 SIGHUP不是 SIGUSR1adb shellkill-HUP$(pidof logd)# 步骤4如果是某个进程滥用日志adb logcat-vthreadtime-bmain *:V|grepchatty# 查找 chatty 消息定位被裁剪合并的日志来源原理分析LogBuffer::prune()的裁剪策略优先裁剪黑名单naughtyUID/PID 的条目其次裁剪占用超过缓冲区 12.5% 的最坏 UID条目保留 dropped 占位符最后从最旧条目开始过期白名单nice条目最后才删除如果缓冲区满了且日志写入速度 logcat 读取速度日志就会被裁剪。慢速 reader 会被 logd 触发跳过triggerSkip极端情况下会被释放release。解决方案// 方案1减少不必要的日志输出if(Log.isLoggable(TAG,Log.DEBUG)){Log.d(TAG,heavy debug info: expensiveToString());}// 方案2合理使用日志级别Log.v(TAG,极详细调试);// 发布版关闭Log.d(TAG,一般调试信息);// 开发阶段Log.i(TAG,值得记录的事件);// 保留Log.w(TAG,异常但可恢复);// 保留Log.e(TAG,错误);// 保留三、案例二chatty 消息淹没了关键日志现象logcat 输出中频繁出现 I chatty:uid10123(com.example.app)0x3039 expire47lines格式说明I INFO 级别chatty 固定 taguid10123 写入 UID(com.example.app) 包名0x3039 线程 TID十六进制expire 47 lines 被裁剪合并了 47 条相同日志。原因应用中有循环日志代码voidonSensorChanged(SensorEventevent){// 高频传感器回调每秒 100 次Log.d(TAG,sensor value: event.values[0]);// 每条日志完全相同 → 裁剪时被设置为 dropped → chatty 合并计数}排查# 查看 chatty 来源adb logcat-vthreadtime|grepchatty# 按 PID 过滤★ logcat 没有 --uid 选项只有 --pidadb logcat-vthreadtime--pid$(adb shell pidof com.example.app)解决方案// 方案1用采样率控制privateintcounter0;voidonSensorChanged(SensorEventevent){if(counter%1000){Log.d(TAG,sensor value: event.values[0]);}}// 方案2使用 VERBOSE 级别发布版自动关闭Log.v(TAG,sensor value: event.values[0]);// 方案3用 IF_ALOGVNative 层IF_ALOGV(){ALOGV(sensor value: %f,event.values[0]);}四、案例三开机日志丢失问题现象设备启动过程中的日志如 init 阶段、service 启动日志在 logcat 中看不到。原因时间线 T0: 内核启动 → printk 输出日志到内核 __log_buf T1: init 进程启动 → 日志暂存logd 还未启动 T2: servicemanager 启动 T3: logd 启动 → 创建 LogBuffer → 开始接收日志 T4: zygote 启动 → Log.d() 可以正常记录 T0-T2 阶段的日志只能通过内核日志dmesg或 pstore 查看 T3 之后的日志logcat 正常可见排查# 检查内核日志dmesgadb shelldmesg|head-50# 检查 pstore上次崩溃后保留的日志adb shellls/sys/fs/pstore/# console-ramoops内核日志# pmsg-ramoops-0用户态日志pmsg_writer 写入的# 查看是否有上次的重启日志adb shellcat/sys/fs/pstore/console-ramoops|head-50# ★ logcat -L 读取 pstore 中的日志adb logcat-L解决方案# 1. 增大内核日志缓冲区# 在内核命令行添加log_buf_len1M# 2. 确保 pstore 配置已启用# CONFIG_PSTOREy, CONFIG_PSTORE_RAMy# 3. 抓取 logd 相关的启动事件adb logcat-bevents|greplogd原理参考第三篇的pmsg_writer系统崩溃时日志通过write(/dev/pmsg0)写入 pstore。重启后logcat -L可以读取上次的日志logd 没有-L参数该功能由 logcat 的ANDROID_LOG_PSTORE模式实现。五、案例四系统服务日志不出现现象修改了 framework 代码添加Slog.d(TAG, xxxx)但logcat -b system看不到。排查# 步骤1确认日志是否真的写入了搜索所有缓冲区adb logcat-ball|grepxxxx# 步骤2检查 SELinux 是否阻止adb shelldmesg|grepavc.*denied.*logd# 步骤3检查进程 UIDadb shellps|grepservice_name# 系统服务 UID 通常为 1000 (system)# 步骤4确认写入了正确的缓冲区# Slog.d → system 缓冲区 (LOG_ID_SYSTEM3)# Log.d → main 缓冲区 (LOG_ID_MAIN0)常见错误// ❌ 错误用了 Log 而非 SlogLog.d(TAG,system service message);// 去了 main 缓冲区// ✅ 正确Slog.d(TAG,system service message);// 去了 system 缓冲区六、案例五events 日志的 tag 编号问题现象logcat -b events输出的 tag 是数字而非名称。原因events 日志使用/system/etc/event-log-tags文件将 tag 编号映射为名称。logcat读取时通过EventTagMap做映射。如果运行时映射表未更新例如新增了自定义 tag 编号就会出现数字 tag。排查# 查看当前的 tag 映射文件adb shellcat/system/etc/event-log-tags|greptag_number# 查看 logcat 是否能正确解析adb logcat-bevents-vthreadtime|grep数字tag解决方案# 在 event-log-tags 文件中添加对应条目# 格式tag编号 tag名称 (字段名|类型|字节数),...echo123456 my_custom_event (Event Value|3)/system/etc/event-log-tags# 重启 logcat 即可生效logcat 重启时重新加载 EventTagMap# 不需要重启 logd七、日志系统的性能影响分析logd 内存占用# ★ logd 是 native 守护进程不能用 dumpsys meminfo# 正确方式查看 /proc/$(pidof logd)/statusadb shellcat/proc/$(pidof logd)/status|grep-EVmRSS|VmSize# 或用 procrankadb shell procrank|greplogd典型内存占用缓冲区部分缓冲区默认大小说明main256KB可配置system256KB可配置events256KB可配置radio256KB可配置crash256KB可配置合计~1.28MB仅缓冲区不含日志条目开销实际内存占用 缓冲区大小 每个LogBufferElement对象的开销元数据 消息体。在日志密集场景下实际内存可能达到 2-3MB。写入性能开销开销构成从调用Log.d()到 logd 完成写入字符串格式化Java 层printlns分块 Native 层vsnprintf~5-10μs锁竞争liblog 端log_init_lock和 logd 端mLogElementsLock1-5μsSocket 发送writev()写入/dev/socket/logdw5-20μslogd 端处理epoll 唤醒 锁 时间排序插入 统计更新10-30μs总耗时约20-65μs在正常负载下对应用影响微乎其微。但高频日志每秒数万条会显著增加 CPU 占用。八、SELinux 与日志权限日志相关的 SELinux 上下文# logd 进程的 SELinux 上下文adb shellps-Z|greplogd# logdw socket 的 SELinux 上下文adb shellls-Z/dev/socket/logd*# 查看日志相关的 AVC 拒绝记录adb shelldmesg|grepavc.*denied.*logd常见 SELinux 问题# 问题自定义 native 服务无法写入日志# 典型错误信息# avc: denied { write } for commmy_service pathsocket:[12345]# devsockfs scontextu:r:my_service:s0 tcontextu:r:logd:s0# 解决方案添加 SELinux 策略# allow my_service logd_socket:sock_file write;# 或使用更精细的宏unix_socket_send(my_service, logdw, logd)九、调试技巧集锦技巧1实时监控特定进程# 按 PID 过滤★ 注意是 --pid不是 --uidadb logcat--pid$(adb shell pidof com.example.app)# 同时监控多个缓冲区adb logcat-vthreadtime-bmain-bsystem-bevents技巧2查看被裁剪的日志# 搜索 chatty 消息裁剪合并标记adb logcat-vthreadtime-bmain|grepchatty# 统计 chatty 出现频率adb logcat-d-bmain|grep-cchatty技巧3对比各缓冲区# 同时打开四个窗口# 窗口1mainadb logcat-vtime-bmain MyTag:* *:S# 窗口2systemadb logcat-vtime-bsystem MyTag:* *:S# 窗口3events注意看二进制事件解码adb logcat-vthreadtime-bevents# 窗口4内核adb shellcat/proc/kmsg技巧4分析日志大户# 统计某个 TAG 的出现频率adb logcat-d-vraw|grep-oP(?/)[^\(]|sort|uniq-c|sort-rn|head-20# 用 logcat -S 直接查看统计最准确adb logcat-S技巧5崩溃日志分析# 搜索 FATAL 级别日志adb logcat-vthreadtime-bcrash# 搜索 ANR 相关事件adb logcat-vthreadtime-bevents|grepam_anr# 搜索 Native Crashadb logcat-vthreadtime|grep-A20FATAL SIGNAL技巧6日志持久化到文件# 将日志保存到文件adb logcat-vthreadtime-f/data/local/tmp/mylog.txt# 持续记录限制大小轮转 10 个文件每个 1MBadb logcat-vthreadtime-r1024-n10-f/data/local/tmp/logcat.txt十、logd 运行时配置# 查看所有 logd 相关属性adb shell getprop|greplogd# ★ 动态调整缓冲区大小注意属性名有 persist. 前缀adb shell setprop persist.logd.size.main 512K adb shell setprop persist.logd.size.system 512K# 发送 SIGHUP 让 logd 重新加载配置★ 是 SIGHUP不是 SIGUSR1adb shellkill-HUP$(pidof logd)# 或者用 logcat -G 直接设置无需重启 logdadb logcat-G512K# ★ 启用统计功能属性名是 logd.statistics布尔值adb shell setprop logd.statisticstrue# 启用日志级别控制adb shell setprop log.tag.MyTag DEBUGlogd 支持的系统属性一览属性类型默认值说明persist.logd.sizenumber256K全局缓冲区默认大小persist.logd.size.buffernumber256K每个 buffer 独立大小ro.logd.sizenumber256K编译时默认值persist 为空时使用ro.logd.statisticsboolsvelte启用 logcat -S 统计persist.logd.securityboolfalse启用 security 缓冲区ro.logd.kernelboolsvelte启用 klogd 内核日志转发persist.logd.filterstring~! ~1000/!裁剪过滤规则persist.logd.timestampstringrealtime时间戳类型monotonic/realtimero.config.low_ramboolfalse低内存设备日志缓冲区降为 64K十一、与其他日志系统的协同logd dropbox# DropBoxManager 是 ServiceManager 中的一个 Java 服务# 它记录 crash/anr/watchdog 等关键事件# 查看 dropbox 条目adb shell dumpsys dropboxlogd tombstone# Native crash 会在 /data/tombstones/ 产生 tombstone 文件adb shellls-la/data/tombstones/# tombstone 相关的崩溃信息也同时写入 logd 的 crash 缓冲区adb logcat-bcrashlogd pstore# 系统崩溃kernel panic时pmsg 通道的日志刷入 pstore# 重启后通过 logcat -L 读取adb logcat-L# 或直接读取文件adb shellcat/sys/fs/pstore/pmsg-ramoops-0十二、源码调试环境搭建关键源码路径system/core/logd/ # logd 守护进程全部源码 system/core/logcat/ # logcat 工具源码 system/core/liblog/ # liblog 库源码写入/读取/格式化 frameworks/base/core/java/android/util/Log.java # Java 层 Log frameworks/base/core/java/android/util/Slog.java # Java 层 Slog frameworks/base/core/java/android/util/EventLog.java # Java 层 EventLog frameworks/base/core/jni/android_util_Log.cpp # Log JNI 桥接 frameworks/base/core/jni/android_util_EventLog.cpp # EventLog JNI 桥接 system/core/include/log/ # 日志相关头文件 system/core/rootdir/init.rc # logd service 定义在 logd 中添加调试日志// ★ logd 本身不能使用 ALOGD会导致循环依赖应使用内核日志#includecutils/klog.hKLOG_ERROR(logd,prune: uid%u size%zu\n,uid,size);// 输出到内核日志通过 dmesg 或 cat /proc/kmsg 查看十三、本篇总结与系列回顾篇主题核心内容一架构概览六层架构、五个缓冲区、两条数据流二logd 守护进程启动时序、Socket 设计、epoll 事件循环三liblog 库完整写入链路、传输器链表、双写兜底四写入接口Log/Slog/EventLog 与 ALOGD/SLOGD/RLOGD 宏五logcat 源码参数解析、格式切换、过滤机制六缓冲区管理环形链表、三段式裁剪、chatty 合并、统计七实战调试常见问题诊断、调试技巧、性能分析从「会用日志」到「理解日志」通过这七篇文章希望你在面对以下场景时有清晰的思路日志丢失 → 查裁剪策略看logcat -S统计第六篇logcat 没输出 → 查过滤器和缓冲区选择第五篇自定义日志不生效 → 查缓冲区路由和 SELinux 权限第四篇、第八节系统崩溃日志恢复 → 查 pstore/pmsg 通道第三篇、第四篇案例三日志性能影响 → 查写入链路开销第三篇、第七节修改 logd 行为 → 查具体模块源码 系统属性第二篇、第六篇、第十节chatty 消息过多 → 查循环日志代码降低日志频率第六篇 chatty 机制