3. 日志系统基础:logcat命令详解、日志级别、Tag过滤、日志缓冲区管理与持久化

日志,说白了就是 SystemUI 崩溃时的第一道防线。你想想看,一个异常堆栈抛出来,如果连日志都没抓到,那跟睁眼瞎有啥区别?我个人习惯是,每次调试 SystemUI 之前,先打开 logcat 窗口,手指悬在键盘上——这和医生开机就得看心电监护仪一个道理。

3.1 日志级别与 Tag——给日志贴标签、分等级

Android 日志有 6 个级别,从低到高分别是:

级别值含义常见用法
VERBOSEV叨叨开发阶段全输出,发布要关掉
DEBUGD调试比如状态栏布局变化时的中间信息
INFOI普通启动、配置加载等关键节点
WARNW警告资源紧张、预期外的分支
ERRORE错误崩溃前的业务逻辑错误
FATALF致命进程必死,看到这级别基本玩完

Tag 是什么?你可以把它理解成日志的「科室名」。SystemUI 内部有几十个 Tag,随便列几个常用的:

  • SystemUI —— 顶层大总管
  • StatusBar —— 状态栏专属
  • NotificationListener —— 通知监听
  • KeyguardViewMediator —— 锁屏核心
  • RecentsPanelView —— 最近任务
  • QuickSettings —— 快捷设置面板

我在项目中遇到过这样一个坑:一条关键的错误日志死活看不到,后来发现是另一个进程刷了无数条 V 级别日志,把缓冲区挤爆了。嗯,从那以后我每次定位问题都先看一眼“日志洪水”到底是谁在发。

核心原则:你必须用合适的级别来写日志,别全程 Log.d。线上产品包里 V/D 会被编译期关掉,千万别把关键信息写在 Verbose 里。

3.2 logcat 命令详解——调试员的瑞士军刀

基本用法谁都会,但真正用好 logcat 还需要知道一些关键选项。我列个表,你直接收藏就行:

选项说明典型用法
-v <format>输出格式threadtime(最常用,含线程信息)
brief(短格式)
long(完整元数据)
-s静默模式,仅显示指定 Tagadb logcat -s SystemUI:V *:S
-b <buffer>指定缓冲区main system events crash radio
多个用逗号,adb logcat -b all
-c清空缓冲区调试前先清空,避免干扰
-g查看缓冲区大小和剩余adb logcat -g
-f输出到文件adb logcat -f /data/local/tmp/log.txt
-d一次性输出并退出配合 grep 可在非 root 设备使用

我最常用的组合拳:

adb logcat -v threadtime -b all -s SystemUI:V *:S

这个命令的意思是:按线程时间格式输出,从所有缓冲区捞日志,只显示 Tag 为 SystemUI 的内容,其他 Tag 全部静默。你想想看,这样就不会被其他进程的噪音打扰了。

还有一个更粗暴的:直接打日志到本地文件,然后慢慢分析。

adb logcat -v threadtime -b all -f /data/local/tmp/sysui_crash.log
我的小习惯:如果我要同时看多个关联 Tag,会用 adb logcat -s "SystemUI|KeyguardViewMediator" 这种正则匹配。不过要注意,Android 原生的 -s 不支持通配符,但你可以用 grep -E 代替,比如:
adb logcat | grep -E "SystemUI|KeyguardViewMediator|StatusBar"

3.3 日志缓冲区管理——别让日志凭空消失

Android 的日志不是直接往文件写的,而是通过 logd 守护进程丢到一个环形缓冲区。我见过很多新手朋友跑半天 logcat ,发现关键日志没了,其实是被后来的日志覆盖了。

缓冲区有这几个:

  • main:普通应用日志(Log.d/v/i/w/e 默认写到这里)
  • system:系统级日志(一般不用,除非你明确指定)
  • events:事件日志(ANR、输入事件、开机过程等)
  • crash:崩溃日志(native crash、tombstone)
  • radio:RIL 相关(电话、网络)
  • kernel:内核日志(需 root 或 eng 版本)

怎么看当前缓冲区情况?

$ adb logcat -g
main: ring buffer is 256 Kb (151 Kb consumed, 104 Kb free)
system: ring buffer is 256 Kb (0 Kb consumed, 256 Kb free)
events: ring buffer is 256 Kb (12 Kb consumed, 243 Kb free)
crash: ring buffer is 256 Kb (3 Kb consumed, 252 Kb free)
radio: ring buffer is 256 Kb (0 Kb consumed, 256 Kb free)

你看,main 缓冲区才 256KB,稍微一刷就满了。我之前调试一个 Notification 闪烁问题,就是因为这个缓冲区太小,关键日志还没来得及看就被推掉了。

怎么持久化?我的做法是:

adb logcat -v threadtime -b main -b system -b events -f /data/local/tmp/fulllog.txt &

记得在 root 权限下运行,否则 /data 目录你可能没写权限。线上设备我建议用 -b crash 单独保存崩溃日志:

adb logcat -v threadtime -b crash -f /data/anr/tombstone_$(date +%s).log
注意:logcat 持久化到文件时,logd 仍然会写环形缓冲区,两者互不干扰。但如果你同时开了多个 logcat 进程读同一个缓冲区,会互相抢数据,导致丢日志。所以一次只开一个读进程。

3.4 实战技巧与避坑

  1. 日志被吞了怎么办?
    我曾经在项目里遇到一个诡异的 StatusBar 崩溃,每次堆栈都少一行。后来发现是因为日志量太大,logd 把低优先级的日志丢弃了。解法:把关键日志级别提到 W 以上,或者用 logcat -b events 找事件日志辅助定位。

  2. pidcat 是好东西
    开源工具 pidcat.py 可以根据进程 PID 过滤日志,比 Tag 更精准。比如只看 SystemUI 自己的日志:
    pidcat com.android.systemui
    它还会自动按 Tag 高亮,非常直观。

  3. dumpsys 日志配合使用
    遇到 ANR 或无响应时,记得先抓一份 dumpsys activity 和 dumpsys window,里面经常有 logcat 里看不到的信息。比如输入事件没响应,可以用 dumpsys input 看 InputDispatcher 队列。

  4. 定时自动轮转
    线上持久化日志时,别忘了加大小限制。可以用 logrotate 或者自己写个脚本,每 10 分钟切一个文件,避免单文件撑爆磁盘。

Logcat 日志体系知识图谱 日志级别 V/D/I/W/E/F Tag 过滤与筛选 缓冲区 main/system/events/crash logcat 命令详解 -v -s -b -c -g -f -d 输出格式 threadtime / brief / long 持久化与文件 -f /data/local/tmp/xxx.log 实战技巧与避坑 pidcat · dumpsys配合 · logrotate · 缓冲区大小监控 💡 先定 Tag 和级别,再选缓冲区,最后决定持久化方案——这是调试的黄金流程。
一句话总结:别只看 main 缓冲区的 W/E 日志,多试试 events 和 crash 缓冲区,很多时候锁屏问题、触摸无响应都能在 events 里找到线索。我在项目里就靠 adb logcat -b events | grep "touch" 定位过一次输入事件丢包的问题。

好了,这一章的内容基本上覆盖了你平时调试 SystemUI 最核心的日志手法。记住:日志是崩溃分析的侦察兵,而 logcat 是你手里的望远镜。多用、多试、多总结,慢慢你就能从日志中嗅到崩溃的蛛丝马迹。