Logcat 日志轮转:解决日志文件过大问题的策略

做 Android 系统开发的,谁没被日志坑过?

我记得刚接手一个车机项目时,客户反馈说设备跑了一周后,系统响应越来越慢。我连上电脑一看,好家伙,logcat 日志文件已经撑到了 2 个多 G。存储空间报警不说,连 logcat 自己读文件都卡得要命。那次之后,我彻底搞明白了日志轮转的重要性。

说白了,日志轮转就是给日志文件“减肥”。不让它无限制地膨胀下去。你想想看,一个日志文件从开机写到关机,如果不加控制,几天就能吃掉几个 G 的空间。这在嵌入式设备上简直是灾难。

为什么日志会变得这么大?

原因其实很简单。Android 系统里各种服务、应用、内核都在往 logcat 里吐数据。特别是开启了 logcat -b all 之后,所有缓冲区的内容全量输出。我见过一个调试中的系统,每秒能产生几百条日志。一小时下来就是上百万条。

嗯,这里要注意。日志量大不一定是坏事。但如果不做轮转,后果很直接:

  • 磁盘空间被吃光——系统可能因此崩溃或无法写入新日志
  • 日志查询变慢——文件太大,grep 一下要等半天
  • 关键信息被淹没——你想看崩溃前的日志,结果文件尾部全是无关信息

核心原则:日志轮转不是删除日志,而是让日志“有生有死”。旧的自动归档,新的继续写入。归档文件达到数量上限后,最老的被清理。

Logcat 自带的轮转机制

其实 Android 的 logcat 命令本身就支持轮转。我个人习惯在生产环境或者长时间跑测试时,一定会加上这几个参数。

# 基本轮转:每个文件 10MB,保留 4 个归档
logcat -f /data/log/app.log -r 10240 -n 4

参数说明:

  • -r 10240:每个日志文件达到 10240 KB(即 10MB)时轮转
  • -n 4:保留 4 个历史归档文件

这样跑起来后,你会看到类似这样的文件列表:

/data/log/app.log          # 当前正在写入的日志
/data/log/app.log.1        # 最近一次轮转的归档
/data/log/app.log.2
/data/log/app.log.3
/data/log/app.log.4        # 最老的归档,下次轮转时被删除

我在项目中遇到过一个问题:某次测试跑了三天,发现日志文件只有 app.log.1 和 app.log.2,后面的没生成。排查了半天,原来是磁盘空间不足,轮转时创建新文件失败了。所以啊,轮转参数设得再合理,也得保证磁盘有富余空间。

小技巧:如果你用 logcat -g 查看缓冲区大小,会发现默认值其实不大。但 -f 输出到文件时,文件大小只受 -r 参数和磁盘空间限制。两者不是一回事,别搞混了。

轮转策略怎么选?

不同的场景,策略不一样。我一般按下面这张表来选:

场景 推荐策略 说明
日常开发调试 不轮转,或保留 2-3 个归档 日志量小,够用就行
长时间稳定性测试 每个文件 5-10MB,保留 5-10 个 平衡空间与历史信息
生产环境/用户设备 每个文件 1-2MB,保留 3-5 个 严格控制空间占用
抓取崩溃前日志 使用 logd 的 persist 功能 系统级轮转,更可靠

你可能会问:为什么生产环境要设这么小?因为用户设备上的存储空间很金贵。我曾经在一个低端机上遇到过,日志文件把用户照片的存储空间挤没了,那投诉可不是闹着玩的。

系统级轮转:logd 的配置

如果你用的是 Android 自带的 logd 守护进程,它其实有一套更完善的轮转机制。logd 把日志存在内存缓冲区里,但同时也支持写入 pmsg 或者 persistent storage。

不过说实话,logd 的持久化轮转配置比较底层。我建议普通开发者直接用 logcat -f 配合 -r-n 就够了。除非你在做系统定制,需要修改 /etc/logd.conf 或者 init.rc 里的 logd 参数。

注意:如果使用 logcat -f 重定向到文件,logcat 进程本身可能会因为文件 I/O 变慢而丢日志。高并发场景下,建议用 logwrapper 或者直接让 logd 处理持久化。

实战:写一个自动轮转脚本

有时候内置的轮转不够灵活。比如我想按日期轮转,或者按日志级别分开存储。这时候写个脚本更靠谱。

下面这个脚本是我在项目里用过的,加了点注释:

#!/system/bin/sh
# 自定义日志轮转脚本
LOG_DIR=/data/log
MAX_SIZE=5120  # 5MB
MAX_FILES=6

while true; do
    # 检查当前日志文件大小
    SIZE=$(stat -c%s "$LOG_DIR/current.log" 2>/dev/null || echo 0)
    if [ "$SIZE" -gt "$MAX_SIZE" ]; then
        # 轮转:把旧文件往后推
        for i in $(seq $((MAX_FILES-1)) -1 1); do
            if [ -f "$LOG_DIR/current.log.$i" ]; then
                mv "$LOG_DIR/current.log.$i" "$LOG_DIR/current.log.$((i+1))"
            fi
        done
        # 把当前文件变成 .1
        mv "$LOG_DIR/current.log" "$LOG_DIR/current.log.1"
        # 重新创建空文件
        touch "$LOG_DIR/current.log"
        echo "[$(date)] Log rotated" >> "$LOG_DIR/current.log"
    fi
    sleep 30  # 每30秒检查一次
done

这个脚本每 30 秒检查一次文件大小。超过 5MB 就轮转,最多保留 6 个归档。你可以把它做成一个服务,开机自启动。

我曾经踩过一个坑:脚本里用 stat 获取文件大小,但某些精简版 Android 系统没有这个命令。后来我改用 ls -lawk 解析,兼容性更好。

轮转时的注意事项

嗯,这里有几个坑,我一个个说:

  • 不要边写边删——如果 logcat 正在往文件里写,你手动删了文件,logcat 不会自动创建新文件。它会继续往已删除的文件描述符里写,你看不到新内容。
  • 权限问题——日志文件通常归 logdshell 用户所有。轮转脚本如果以 root 运行,要注意文件权限别搞乱了。
  • 时间戳冲突——如果轮转太快(比如每几秒就轮转一次),归档文件的时间戳可能一样,导致覆盖。我建议轮转间隔至少几分钟。

一句话总结:日志轮转的核心是“空间换时间”。用有限的磁盘空间,保留最有价值的日志历史。参数设置要根据实际日志产生速率来调,别拍脑袋定。

知识体系图

下面这张图帮你理清日志轮转的整体逻辑:

日志轮转核心逻辑 日志源 应用 / 系统 / 内核 logcat 进程 读取缓冲区 / 输出到文件 轮转决策 检查 -r 参数 / 文件大小 未达到阈值 继续写入当前文件 达到阈值 触发轮转 轮转动作 current.log → current.log.1 删除最老的归档(超过 -n 数量) ✅ 日志文件大小受控,历史可追溯

从图上可以看得很清楚:日志从源头出来,经过 logcat 进程,到达轮转决策点。如果文件没超阈值,就继续写;超了,就触发轮转——把当前文件往后推,删掉最老的。就这么简单。


好了,关于日志轮转的核心策略就这些。说白了就是三个参数:文件多大时轮转、保留几个归档、日志往哪写。调好这三个参数,日志文件就不会再“野蛮生长”了。

我的建议:刚开始做项目时,先按保守参数来(比如 5MB、保留 3 个)。跑几天看看日志增长速度,再逐步调整。别一上来就设 100MB 保留 50 个,那跟不轮转没啥区别。