日志抓取与 logcat 高级过滤技巧

日志是调试 SystemUI 崩溃的“第一手证据”,但光会 adb logcat 远远不够。你想想看,几百行日志里可能只有三五条是真正的线索。怎么快速锁定目标?怎么保证抓到的日志不丢帧?这章我就把压箱底的经验抖出来。

抓日志,别只会敲 adb logcat

很多新人上来就一句 adb logcat,然后屏幕刷刷刷刷……看完眼都花了。我个人习惯先把日志重定向到文件:

adb logcat -v threadtime > /tmp/crash.log

为什么要加 -v threadtime?因为 默认格式没有毫秒和线程 ID,一旦崩溃涉及多线程交互,你根本没法对齐事件顺序。我记得有次排查 SystemUI 的 ANR,就是靠 threadtime 格式才看出某个 binder 线程卡了 3 秒。

还有个小细节:抓日志前建议先把旧日志清一下,避免干扰:

adb logcat -c

不过注意,-c 只清 main 缓冲区。如果你想连 system 和 crash 缓冲区一起清,要指定 -b all。

⚠️ 警告:别在生产用户手机上 adb logcat -c,这会丢失用户之前的崩溃日志。我一般在开发机上才这么干。

缓冲区选不对,等于白抓

logcat 有多个缓冲区,默认只抓 main 和 system。但 SystemUI 崩溃很多时候会出现在 crash 或 events 缓冲区里。你看下面这张图就清楚了:

Logcat 缓冲区与过滤层级 原始 Android 日志 main system crash events 等级过滤 (V/D/I/W/E/F) 标签过滤 (tag) PID 过滤 正则/关键字 过滤后的日志流 → 文件或控制台 💡 核心原则:先选对缓冲区,再逐级缩小范围 SystemUI 崩溃优先看 crash 缓冲区,其次是 system

所以,抓 SystemUI 崩溃的正确姿势是:

adb logcat -b crash -b system -v threadtime > crash.log

缓冲区可以同时抓多个,用 -b 连续指定。我之前踩过坑——只抓了 main 缓冲区,结果 logcat 里全是应用日志,崩溃堆栈被刷没了。后来改成 -b crash -b system,问题一次就定位了。

logcat 高级过滤,三板斧

第一板斧:按等级压噪

SystemUI 正常的调试日志很多,但崩溃通常只在 Error / Fatal 级别出现。我一般直接这样:

adb logcat -b crash -v threadtime *:E

这会把所有低于 Error 的日志都去掉。但要注意:有些关键警告是 W 级别的(比如内存不足的前兆),所以我的习惯是 先抓 *:W,如果量还是太大就缩到 *:E。

第二板斧:按标签锁定模块

SystemUI 的日志标签大多以 SystemUI 开头,也可能是具体的类名:

adb logcat -b crash -v threadtime SystemUI:E

如果想同时看多个标签,用逗号隔开:

adb logcat -b crash SystemUI:W StatusBar:W NavigationBar:W

这里我有个小建议:不要只过滤 Error,有时候 前几行 Warning 才是真正的根因,Error 只是后果。我在项目中遇到过,一次 SystemUI 重启的堆栈指向是 NullPointer,但真正的原因在 2 秒前有一条 “KeyguardViewMediator: Trying to show keyguard while not allowed!” 的 Warning。

第三板斧:正则与 PID 精确打击

有时崩溃日志不包含标准标签,比如 native crash 会出现在 crash 缓冲区,但 tag 是 DEBUG。这时候正则就派上用场了:

adb logcat -b crash -v threadtime -e "signal|fatal|SIGSEGV"

-e 后面跟正则表达式,大小写敏感。另一个技巧是抓特定进程:先找到 SystemUI 的 PID:

adb shell pidof com.android.systemui

然后直接按 PID 过滤:

adb logcat -b crash -v threadtime --pid 1234

这样输出里除了 SystemUI 自己的日志,还包含它调用的系统服务日志?不,--pid 只打印该进程自己打印的日志,对跨进程 binder 调用要小心。我建议 先按 PID 抓,再配合 -b crash 看有没有跨进程的异常。

🔑 重点:高级过滤命令速查表
目标命令
抓 crash + system 缓冲区,带时间戳adb logcat -b crash -b system -v threadtime
只显示 Error 及以上*:E
只看 SystemUI 的 ErrorSystemUI:E
正则匹配崩溃关键词-e "NullPointer|RuntimeException"
按进程 PID 过滤--pid 1234
实时保存到文件并显示adb logcat > log.txt &

实时过滤与日志旋转

崩溃往往在几秒内发生,手动盯着 logcat 太累。我在调试时常用 grep 做管道过滤:

adb logcat -b crash -v threadtime | grep -i "systemui\|fatal"

但要注意:管道后的 grep 默认行缓冲,速度跟不上时可能会丢日志。更靠谱的做法是用 python 脚本,像这样:

import subprocess
import sys

proc = subprocess.Popen(['adb', 'logcat', '-b', 'crash', '-v', 'threadtime'],
                        stdout=subprocess.PIPE, bufsize=1, universal_newlines=True)
for line in proc.stdout:
    if 'SystemUI' in line and 'E/' in line:
        sys.stdout.write(line)
        sys.stdout.flush()

这段脚本我一直在用,bufsize=1 保证行缓冲,不会漏日志。

还有一个痛点:日志文件越来越大。我个人习惯加 --rotate-kbytes 参数(Android 10+ 支持):

adb logcat -b crash -v threadtime --rotate-kbytes 1024 -f /sdcard/crash.log

每 1MB 自动滚动一个新文件,文件名自动追加序号。不过要注意 这个参数只在 device 端的 logcat 生效,PC 端 adb logcat 的 -f 是指向手机文件。我建议还是直接重定向到电脑,更可靠。

💡 个人经验:抓 SystemUI 崩溃前,最好先 adb shell dumpsys battery unplug 模拟意外断电?不用,但可以 adb shell settings put global sysui_tracing_default_enabled 1 开启 SystemUI 的内部 trace,这样即使 logcat 没及时抓到,trace 文件里也有线索。

避坑指南——我曾经犯过的错

  • 坑 1:日志被截断——我遇到过 SystemUI 崩溃堆栈特别长,默认 logcat 单行 4096 字符被截断。后来加 -v long 或 --print-long 才看到完整堆栈。
  • 坑 2:时间戳不准——不同设备的时钟不同步,如果你同时用 PC 和 device 的日志,一定要用 -v epoch 转成 UNIX 时间戳再比对。我曾因为差了 3 秒,误判了崩溃顺序。
  • 坑 3:抓了天量日志——有一次忘记加等级过滤,抓了 2GB 文本,打开就卡死。现在我的习惯:先加 *:W,觉得线索不足再降级到 *:V。
  • 坑 4:权限不够——某些系统日志(如 kernel log)需要 root 或工程机。如果 adb logcat 提示 Permission denied,试试 adb root 或换用 logcat -b kernel -b crash 的拆分。
⚠️ 警告:别在生产用户手机上长时间 adb logcat,否则 logd 服务会积压大量待消费日志,导致系统变慢甚至触发 watchdog。我一般只抓 30 秒内复现的场景,够用即可。

小结:调试第一关

日志抓取是调试的“脏活”,但脏活做好了,后续分析就事半功倍。记住三个要点:
选对缓冲区 → 压噪到适当的等级 → 用标签或正则缩小范围。
这章讲的是“怎么抓”,下一层咱们再聊“怎么读”——堆栈解析、时序重建,那一部分更有意思。嗯,就先到这,去试试我上面那些命令,拿个模拟崩溃验证一下,你会发现自己突然就“会抓日志”了。