25、诊断与日志:logcat 高级用法,车载日志系统(ELK 栈),故障排查实战

做车载系统开发,最怕什么?

怕车在路上跑着,某个功能突然挂了。更怕的是,日志拿不到,或者拿到了看不懂。

我在做 Automotive 项目时,遇到过好几次这样的场景:测试工程师反馈说“中控屏黑屏了”,但 logcat 里全是正常信息。后来才发现,是日志级别没调对,关键错误被淹没了。从那以后,我对日志系统就特别上心。

今天咱们就聊聊,怎么用好 logcat,怎么搭建车载日志系统,以及怎么高效排查故障。

25.1 logcat 高级用法:别只会 adb logcat

很多人用 logcat,就是敲个 adb logcat 然后盯着屏幕看。其实这远远不够。

25.1.1 按标签和优先级过滤

车载系统里,进程多、日志杂。你不加过滤,根本看不完。

# 只看 CarService 的 error 级别日志
adb logcat -s CarService:E

# 同时看多个标签
adb logcat -s CarService:E SystemUI:W

我个人习惯,先开一个窗口看全局日志,再开一个窗口专门盯关键模块。这样不容易漏掉上下文。

25.1.2 缓冲区选择

Android 有多个日志缓冲区:main、system、events、crash。默认只输出 main 和 system。

# 看 events 日志(包含 AMS、WMS 等系统事件)
adb logcat -b events

# 看 crash 日志
adb logcat -b crash

有一次,一个车载应用闪退,main 日志里啥都没有。我切换到 -b crash,立刻看到了崩溃堆栈。嗯,这个技巧很实用。

25.1.3 日志时间戳与格式化

车载日志需要精确到毫秒,方便和 CAN 总线数据对齐。

# 显示完整时间戳
adb logcat -v threadtime

# 显示 UTC 时间(方便跨时区分析)
adb logcat -v epoch

我建议用 threadtime,它同时显示线程 ID,排查多线程问题很有帮助。

25.1.4 日志持久化

车机不像手机,你不能随时插线抓日志。所以,日志必须能写到文件里。

# 将日志重定向到文件
adb logcat -f /sdcard/logcat.txt

# 限制文件大小,自动轮转
adb logcat -r 1024 -n 5 -f /sdcard/logcat_rotating.txt

这里有个坑:-r 参数的单位是 KB。我曾经设成 -r 1024,以为能存 1GB,结果只存了 1MB。后来查文档才发现,1024 是 1024 KB,也就是 1MB。所以,想要 100MB,得写 -r 102400

25.2 车载日志系统:ELK 栈

单台车抓日志还行,但你要管理一个车队呢?

这时候就需要 ELK 栈了。ELK 是 Elasticsearch、Logstash、Kibana 的缩写。说白了,就是把日志集中起来,方便搜索和可视化。

25.2.1 整体架构

我画了一张图,帮你理解车载日志怎么从车端流向云端。

车载日志 ELK 栈架构 车端 logcat / dmesg 车载日志采集 Agent 上传 云端 Logstash 接收 解析 / 过滤 存储 Elasticsearch 索引 / 存储 全文搜索 查询 Kibana 可视化 / 仪表盘 工程师 浏览器访问 关键点 1. 车端日志压缩后上传,节省流量 2. Logstash 解析日志格式,提取关键字段 3. Elasticsearch 建立索引,支持毫秒级搜索

你看,车端采集日志,上传到云端 Logstash,Logstash 解析后存入 Elasticsearch,最后用 Kibana 做可视化。工程师在浏览器里就能搜到任意一台车的日志。

25.2.2 Logstash 配置示例

Logstash 的核心是配置文件。它告诉 Logstash:从哪读数据,怎么处理,往哪写。

input {
  # 从 Kafka 读取车端日志
  kafka {
    bootstrap_servers => "kafka:9092"
    topics => ["vehicle_logs"]
    codec => "json"
  }
}

filter {
  # 解析 logcat 格式
  grok {
    match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{DATA:tag}: %{GREEDYDATA:msg}" }
  }

  # 添加车辆 VIN 字段
  mutate {
    add_field => { "vin" => "%{[@metadata][vin]}" }
  }

  # 过滤掉低级别日志(只保留 WARN 及以上)
  if [level] not in ["WARN", "ERROR", "FATAL"] {
    drop {}
  }
}

output {
  elasticsearch {
    hosts => ["http://elasticsearch:9200"]
    index => "vehicle-logs-%{+YYYY.MM.dd}"
  }
}

这个配置里,我用了 grok 插件来解析 logcat 格式。你想想看,如果不解析,日志就是一行字符串,没法按级别、标签搜索。解析之后,你就可以在 Kibana 里搜 level:ERROR AND tag:CarService,瞬间定位问题。

25.2.3 Kibana 仪表盘

Kibana 不只是搜索,还能做仪表盘。我习惯建三个面板:

  • 错误趋势图:按小时统计 ERROR 日志数量,异常飙升一目了然。
  • Top 10 错误标签:哪个模块报错最多,排个序。
  • 车辆健康度:每台车最近 24 小时是否有 FATAL 日志。

有一次,仪表盘显示某车型的“空调控制”模块错误率突然升高。我们查了代码,发现是某个传感器数据格式变更,导致解析异常。如果没有仪表盘,这种问题可能要等用户投诉才能发现。

25.3 故障排查实战

理论说完了,咱们来点实战。我分享三个真实案例。

25.3.1 案例一:中控屏黑屏

现象:车辆启动后,中控屏一直黑屏,但声音正常。

排查步骤

  1. 先看 logcat,过滤 SystemUISurfaceFlinger
  2. 发现 SurfaceFlinger 报错:ERROR: BufferQueue has been abandoned
  3. 进一步查,发现是某个第三方应用持有了 Surface 但没有释放。
  4. 杀掉该应用,屏幕恢复。

这个问题的根因是应用没有正确处理生命周期。我们在 ELK 里加了一条规则:如果 BufferQueue abandoned 出现,自动告警。

25.3.2 案例二:导航卡顿

现象:导航过程中,地图滑动不流畅,有掉帧。

排查步骤

  1. adb logcat -s Choreographer:I 查看帧率。
  2. 发现掉帧时,伴随大量 GC_FOR_ALLOC 日志。
  3. 说明内存分配频繁,触发垃圾回收。
  4. adb shell dumpsys meminfo 查看内存,发现地图应用缓存了太多瓦片。
  5. 优化缓存策略后,卡顿消失。

这里有个技巧:Choreographer 日志会告诉你跳过了多少帧。如果连续跳帧超过 3 帧,用户就能感知到卡顿。

25.3.3 案例三:蓝牙连接不稳定

现象:手机连接车机蓝牙后,偶尔断连。

排查步骤

  1. 抓取 Bluetooth 相关日志:adb logcat -s BluetoothAdapter:E BluetoothManager:W
  2. 发现断连前有 bt_vendor: firmware crash 日志。
  3. 这是蓝牙固件崩溃了。需要更新固件。
  4. 同时,我们在 ELK 里添加了蓝牙固件版本字段,方便追踪哪些车还在用旧固件。

我曾经遇到过一个问题:蓝牙日志里全是 D 级别,根本看不到错误。后来发现是日志级别被某个系统服务改写了。所以,排查时一定要确认日志级别是否正确。

25.4 避坑指南

注意:日志别乱打

车载系统对性能敏感。日志打太多,会影响 UI 响应。我建议:

  • 生产环境只保留 WARN 及以上级别。
  • DEBUG 日志用 isLoggable 控制开关。
  • 循环日志不要打,比如每 100ms 打印一次传感器数据,用统计值代替。

小技巧:日志带上时间戳

车端和云端的时间可能不同步。我习惯在日志里加上 System.currentTimeMillis(),这样不管在哪,都能对齐时间线。

核心要点总结

  • logcat 要会用 -s-b-v 参数,别裸跑。
  • ELK 栈是车载日志的标配,车端采集、云端分析。
  • 故障排查要结合 logcat 和系统服务(如 dumpsys)。
  • 日志级别要控制好,别让生产环境变成日志洪流。

好了,这一章的内容就到这里。日志系统是车载开发的“眼睛”,用好它,能省下大量排查时间。下一章咱们聊聊性能优化,到时候见。

公众号:蓝海资料掘金营,微信 deep3321