日志系统与抓取方法:logcat、kernel log、tombstone、anr trace、dmesg的抓取与分析
做系统稳定性,说白了就是跟各种日志打交道。我经常跟团队里的小朋友说,日志就是系统的「黑匣子」,飞机失事要找黑匣子,手机死机要找日志。今天咱们就把这几种核心日志的抓取和分析方法,一次性讲透。
一、logcat:最常用的用户态日志
logcat 是 Android 最基础的日志工具。它抓的是用户空间(用户态)的日志,包括 App、Framework、系统服务等打印的信息。
1. 基本抓取命令
// 抓取所有日志,保存到文件
adb logcat -v threadtime > logcat.txt
// 按缓冲区抓取
adb logcat -b main -b system -b crash -v threadtime > logcat.txt
// 按 TAG 过滤
adb logcat -s ActivityManager:* WindowManager:* -v threadtime
我个人习惯用 -v threadtime 参数,它会把线程 ID 和时间戳都带上。排查问题时,时间顺序和线程归属太重要了。
2. 日志级别与过滤
| 级别 | 含义 | 使用场景 |
|---|---|---|
| V | Verbose(明细) | 开发调试,正式环境一般不抓 |
| D | Debug(调试) | 开发阶段排查问题 |
| I | Info(信息) | 正常流程记录 |
| W | Warning(警告) | 可能有问题,需要关注 |
| E | Error(错误) | 必须修复的问题 |
| F | Fatal(致命) | 会导致进程崩溃 |
嗯,这里要注意:很多同学一上来就抓 V 级别的日志,结果文件巨大,反而找不到关键信息。我建议先抓 E 和 W 级别,定位到具体模块后再放大级别。
二、kernel log:系统底层的「心电图」
Kernel log 记录的是内核空间的日志。当系统出现死机、重启、驱动异常时,logcat 可能已经来不及打印了,但 kernel log 往往还留着关键线索。
1. 抓取方法
// 通过 adb 抓取
adb shell dmesg > kernel_log.txt
// 实时查看
adb shell cat /proc/kmsg // 需要 root 权限
// 或者用 logcat 抓 kernel 缓冲区
adb logcat -b kernel -v threadtime > kernel_logcat.txt
我曾经遇到一个案子,手机不定时重启,logcat 里啥都没有。后来抓了 dmesg,发现是某个 GPIO 口的中断风暴导致内核 panic。你看,这种问题不到 kernel 层根本查不出来。
2. 关键信息解读
Kernel log 里最需要关注的是这几个关键词:
- panic:内核恐慌,系统会直接重启
- Oops:内核出错了,但不一定重启
- watchdog:看门狗超时,说明某个任务卡住了
- Out of memory:内存不足,开始杀进程
- Call trace:函数调用栈,告诉你代码走到哪崩了
三、tombstone:App 崩溃的「尸检报告」
Tombstone 是 native 层(C/C++)崩溃时生成的日志文件。Java 层的崩溃会记录在 logcat 里,但 native 层的崩溃,比如 so 库挂掉、JNI 调用出错,就会生成 tombstone。
1. 抓取位置
// tombstone 文件默认路径
/data/tombstones/
// 拉取到电脑
adb pull /data/tombstones/ ./tombstones/
// 查看最近一次崩溃
adb shell ls -lt /data/tombstones/
每个 tombstone 文件以 tombstone_XX 命名,XX 是数字编号。最新的崩溃会覆盖最旧的文件,所以出问题后要尽快拉取。
2. 分析要点
打开一个 tombstone 文件,你会看到类似这样的结构:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'xxx'
Revision: '0'
ABI: 'arm64'
Timestamp: 2024-01-15 14:30:22.123456789+0800
pid: 12345, tid: 12346, name: Binder:12345_1
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
x0 0000000000000000 x1 0000007f8c123456 ...
pc 0000007f8a123456 /system/lib64/libxxx.so
backtrace:
#00 pc 0000000000123456 /system/lib64/libxxx.so (function_name+100)
#01 pc 0000000000123789 /system/lib64/libxxx.so (another_func+200)
#02 pc 0000000000123901 /system/lib64/libxxx.so
核心看三点:
- signal 和 fault addr:SIGSEGV 是段错误,fault addr 为 0 说明是空指针
- pc 寄存器:程序计数器,指向崩溃时的指令地址
- backtrace:调用栈,从下往上看是调用顺序
四、ANR Trace:应用无响应的「现场快照」
ANR(Application Not Responding)发生时,系统会生成一份 trace 文件,记录所有线程的当前状态。说白了,就是系统在那一刻给所有线程拍了一张「合影」。
1. 抓取方法
// ANR trace 文件路径
/data/anr/traces.txt
// 拉取到电脑
adb pull /data/anr/traces.txt ./
// 如果 ANR 刚发生,也可以直接看
adb shell cat /data/anr/traces.txt | grep "ANR in"
注意,traces.txt 是覆盖写入的。每次 ANR 都会覆盖上一次的内容。所以如果你遇到连续 ANR,只能抓到最后一次的 trace。
2. 分析主线程
ANR 的核心是主线程被阻塞了。打开 traces.txt,先找到主线程(通常叫 "main" 或 "UI Thread"):
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x...
| sysTid=12345 nice=0 cgrp=default sched=0/0 handle=0x...
| state=Blocked stack=0x7f8c000000-0x7f8c002000
at com.example.MyClass.myMethod(MyClass.java:100)
- waiting to lock <0x12345678> (a java.lang.Object)
at com.example.MyClass.anotherMethod(MyClass.java:50)
- locked <0x12345678> (a java.lang.Object)
at android.app.ActivityThread.handleMessage(ActivityThread.java:2000)
看到 Blocked 状态了吗?说明主线程在等一把锁。这时候要找到谁持有了这把锁。继续往下翻,找到持有锁的线程:
"Binder:12345_2" prio=5 tid=15 Sleeping
at java.lang.Thread.sleep(Native Method)
- locked <0x12345678> (a java.lang.Object)
你看,Binder 线程持有了锁,但它自己在 sleep。主线程只能干等着。这就是典型的死锁或锁竞争问题。
五、dmesg:内核日志的「实时快照」
其实 dmesg 和 kernel log 是一回事。dmesg 是 Linux 标准的命令,用来读取内核环形缓冲区的内容。Android 里也完全兼容。
1. 常用参数
// 查看所有日志
dmesg
// 只看最后 100 行
dmesg | tail -100
// 带时间戳
dmesg -T
// 实时监控
dmesg -w
-T 参数会把内核时间戳转换成人类可读的时间格式。我建议每次都加上,不然看到的是系统启动后的秒数,换算起来太痛苦了。
2. 实战场景
什么时候必须看 dmesg?我总结了几种情况:
- 系统重启:看最后几行有没有 panic 或 watchdog 信息
- 外设异常:比如摄像头打不开、WiFi 连不上,dmesg 里通常有驱动报错
- 功耗问题:看是否有设备没有进入休眠,比如 "wake lock" 相关的日志
- 存储问题:I/O 错误、文件系统损坏等
六、综合抓取策略
实际项目中,我一般会同时抓取多种日志。因为单一日志往往只能看到问题的一个侧面。
1. 一键抓取脚本
#!/bin/bash
# 抓取所有关键日志
timestamp=$(date +%Y%m%d_%H%M%S)
mkdir -p bugreport_$timestamp
# 1. logcat
adb logcat -b main -b system -b crash -v threadtime -d > bugreport_$timestamp/logcat.txt
# 2. kernel log
adb shell dmesg > bugreport_$timestamp/dmesg.txt
# 3. tombstone
adb pull /data/tombstones/ bugreport_$timestamp/tombstones/
# 4. ANR trace
adb pull /data/anr/traces.txt bugreport_$timestamp/
# 5. 系统信息
adb shell cat /proc/meminfo > bugreport_$timestamp/meminfo.txt
adb shell cat /proc/cpuinfo > bugreport_$timestamp/cpuinfo.txt
echo "日志已保存到 bugreport_$timestamp"
2. 分析顺序
拿到一堆日志后,我习惯按这个顺序排查:
- 先看 dmesg:确认内核有没有 panic 或硬件异常
- 再看 tombstone:有没有 native 层崩溃
- 然后看 ANR trace:主线程是否被阻塞
- 最后看 logcat:结合时间戳,看崩溃前后的用户态日志
好了,日志系统这块就讲这么多。说白了,抓日志是体力活,分析日志才是技术活。多练、多积累,你也能从一堆乱码中一眼看出问题所在。下次咱们聊聊具体怎么分析这些日志,比如怎么从 backtrace 定位到代码行号,怎么用 addr2line 解析符号表。到时候见。