24、Logcat 日志丢失问题:为什么日志会丢失?如何避免?

做 Android 开发这么多年,我遇到过最让人抓狂的问题之一,就是日志莫名其妙丢了。

明明代码里写了一大堆 Log.d,跑起来一看,控制台干干净净。或者更气人的——关键崩溃日志刚好被截断,只留下半句话。

嗯,今天我们就来聊聊这个事。日志为什么会丢?怎么才能不让它丢?

日志丢失的常见原因

说白了,Logcat 不是无限容量的。它是个环形缓冲区,满了就覆盖旧数据。你想想看,系统每天要处理多少日志?如果每个 App 都疯狂输出,内核日志、事件日志、主日志全挤在一起,缓冲区很快就满了。

我总结了几种最常见的丢失场景:

  • 缓冲区溢出:默认每个缓冲区只有 256KB(旧设备)或 1MB(新设备),高频日志几秒就撑爆
  • 日志级别过滤:系统默认只保留 V/D/I/W/E 中的部分级别,低优先级日志可能被丢弃
  • 进程被杀:App 被系统回收时,未刷盘的日志直接消失
  • USB 断开:adb logcat 依赖 USB 连接,断开瞬间的日志会丢失
  • 多进程竞争:多个进程同时写日志,内核来不及处理就丢弃

核心结论:日志丢失不是 Bug,是特性。Logcat 的设计目标就是「尽力而为」,不是「可靠传输」。

缓冲区机制详解

先看看 Logcat 的缓冲区是怎么工作的。Android 有四个主要缓冲区:

缓冲区名称 存储内容 默认大小
main 普通应用日志(Log.d / Log.i 等) 256KB ~ 1MB
system 系统服务日志 256KB ~ 1MB
events 事件日志(如 ActivityManager) 256KB
crash 崩溃日志 256KB

每个缓冲区都是环形结构。写入新日志时,如果空间不够,就覆盖最旧的那条。这就是为什么你经常看到日志中间缺了一段——不是没产生,是被挤掉了。

我个人习惯在调试阶段把缓冲区调大,或者直接输出到文件。后面会讲具体怎么做。

如何检测日志是否丢失?

你可能想问:我怎么知道日志丢了?其实 Logcat 自己会告诉你。

当你运行 adb logcat 时,如果看到类似这样的输出:

--------- beginning of /dev/log/main
--------- beginning of /dev/log/system
--------- beginning of /dev/log/events
--------- beginning of /dev/log/crash

这表示 Logcat 重新连接到了缓冲区。中间的「---------」分割线,意味着你丢失了之前的一部分日志。

还有一种情况:你发现日志的时间戳有跳跃。比如 10:00:00 之后直接跳到 10:00:05,中间那 5 秒的日志就是丢了。

小技巧:用 adb logcat -v time 可以显示精确到毫秒的时间戳,方便你判断是否有跳跃。

避免日志丢失的实用方法

好了,问题分析完了,咱们聊聊怎么解决。我根据不同的场景,整理了以下几种方案:

1. 增大缓冲区大小

这是最直接的方法。通过 logcat -b 指定缓冲区,再用 -G 设置大小:

# 查看当前缓冲区大小
adb logcat -g

# 设置 main 缓冲区为 4MB
adb logcat -b main -G 4M

# 设置所有缓冲区为 2MB
adb logcat -G 2M

注意:这个设置是临时的,设备重启后会恢复默认值。如果你需要永久修改,得改内核参数或者用 init.rc 脚本。

2. 将日志输出到文件

我个人最推荐的方式。直接写到文件,不依赖 USB 连接,也不受缓冲区限制。

# 实时输出到文件(推荐)
adb logcat -v threadtime -f /sdcard/logcat.txt

# 或者用重定向(更灵活)
adb logcat -v threadtime > logcat_$(date +%Y%m%d_%H%M%S).txt

-f 参数时,日志直接写入设备内部存储。即使 USB 断开,文件还在。我曾经靠这个方式抓到了客户现场的一个偶现崩溃——日志文件完整记录了崩溃前的所有操作。

3. 过滤不必要的日志

减少日志量,缓冲区就不容易满。用 -sgrep 只保留你关心的:

# 只显示 TAG 为 "MyApp" 且级别 >= WARNING 的日志
adb logcat -s MyApp:W

# 或者用 grep 过滤
adb logcat | grep "MyApp"

嗯,这里要注意:-s 是静默模式,会屏蔽所有未指定的 TAG。如果你只想看某个 TAG,同时保留其他日志的 WARNING 以上级别,可以用:

adb logcat *:W MyApp:V

4. 使用 logcat 的持久化模式

Android 8.0 之后,系统提供了 logd 的持久化功能。你可以把日志写入 /data/misc/logd/ 目录:

# 开启持久化(需要 root)
setprop persist.logd.logpersistd logcatd

# 指定缓冲区
setprop persist.logd.logpersistd.buffer main,system,crash

这样即使设备重启,日志也不会丢。不过这个功能一般只用于工程机或调试版本,量产机上不建议开,会消耗存储空间。

5. 代码层面控制日志输出

在 App 代码里,我习惯加一个日志开关。调试版本全量输出,发布版本只保留关键日志:

public class LogUtil {
    private static final boolean DEBUG = BuildConfig.DEBUG;
    
    public static void d(String tag, String msg) {
        if (DEBUG) {
            Log.d(tag, msg);
        }
    }
    
    public static void w(String tag, String msg) {
        Log.w(tag, msg);
    }
    
    public static void e(String tag, String msg) {
        Log.e(tag, msg);
    }
}

这样既保证了调试时的信息量,又避免了发布版本日志刷爆缓冲区。

避坑指南:我曾经在项目里遇到过一个问题——日志文件写满了 /sdcard,导致 App 无法正常写入数据。后来我加了一个日志文件大小限制和自动清理逻辑,才彻底解决。如果你用文件输出,一定要考虑磁盘空间管理。

核心知识体系

下面这张图总结了日志丢失的原因和应对策略,你可以对照着理解:

Logcat 日志丢失问题全景图 日志丢失原因 • 缓冲区溢出(环形覆盖) • 日志级别过滤(低优先级丢弃) • 进程被杀 / USB 断开 • 多进程竞争写入 解决方案 • 增大缓冲区(-G 参数) • 输出到文件(-f / 重定向) • 过滤日志(-s / grep) • 持久化模式(logd) 如何检测日志丢失? • 观察 "--------- beginning of" 分割线 • 检查时间戳是否有跳跃 • 使用 logcat -g 查看缓冲区使用率 核心原则:Logcat 是「尽力而为」机制,关键日志请使用文件输出

总结

日志丢失这件事,说大不大,说小不小。调试阶段丢几条日志,顶多多跑几次。但线上问题排查时,关键日志丢了,可能就得花几天时间复现。

我个人建议养成两个习惯:

  • 调试时:用 adb logcat -v threadtime > log.txt 输出到文件,别依赖控制台
  • 发布时:代码里加日志开关,只保留 WARNING 以上级别,或者用 LogUtil 封装

嗯,今天就聊到这里。记住一句话:日志是开发者的眼睛,别让眼睛瞎了。