mcpField
E
extracting-logs-with-logcat

extracting-logs-with-logcat

github.com/skydoves开发33988 次安装22 周前更新
代码仓库

关于 extracting-logs-with-logcat

这是一份面向 Android 开发者的 `adb logcat` 操作指南,涵盖实时读取、筛选、格式化和持久化设备日志,并介绍 CI 故障捕获及发布版日志剔除。

主要功能

  1. 01实时读取或转储日志
  2. 02选择日志缓冲区并筛选
  3. 03设置格式、时间和 PID 过滤
  4. 04将日志写入文件并轮转
  5. 05使用 R8 规则剔除调试日志

适用场景

  1. 01在 CI 测试失败时保存设备日志
  2. 02过滤应用或系统组件日志
  3. 03采集结构化 JSON 日志
  4. 04排查进程重启后的日志记录
  5. 05移除发布版本中的 `Log.d` 调用

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/skydoves/android-testing-skills --skill extracting-logs-with-logcat

安装后在 Claude Code 中输入/extracting-logs-with-logcat确认已加载。

详细介绍

使用 logcat 提取日志 — 读取设备日志

本技能涵盖 adb logcat 的端到端用法:流式输出与转储、缓冲区选择、过滤表达式、格式标志、PID 和时间过滤、文件轮转,以及在发布版本中剔除 Log.d 的 R8 规则。配套的 CI 失败时捕获模式见 ../../automation/scripting-adb-for-ci/SKILL.md。

何时使用此技能

  • CI 上的测试失败;开发者需要将设备日志转储到构件归档中。
  • 开发者的 adb logcat | grep MyApp 淹没了 SSH 会话——在 logcat 端进行过滤即可解决。
  • 某个场景只会在 system_server 或 crash 缓冲区中复现,而不会出现在默认的 main 缓冲区中。
  • 开发者想要结构化日志(-v json),以便机器读取。
  • Log.d("Sensitive", "...") 调用似乎泄漏到了发布版本中——缺少 R8 -assumenosideeffects 规则。
  • 应用被 Doze/ANR 终止后日志消失——需要使用 --pid 并循环重新解析 PID。

何时不使用此技能

  • 捕获屏幕截图或视频。请使用 ../../capture/capturing-screenshots-and-screenrecord/SKILL.md。
  • 从设备拉取通用文件。请使用 ../../transfer/extracting-test-artifacts/SKILL.md。
  • 整套脚本,包括重试、端口转发和多设备并行分发。请使用 ../../automation/scripting-adb-for-ci/SKILL.md。
  • 执行手势或更改设置。请使用 ../../control/injecting-input-and-state/SKILL.md。

前提条件

  • adb get-state 返回 device。请参阅 ../../devices/connecting-to-devices/SKILL.md。
  • 使用 -v json:设备 API 30+(Android 11+)。搭配 -v UTC -v year 可获得明确无歧义的时间戳。
  • 按 PID 过滤:adb shell pidof -s <pkg> 需要 API 24+(toybox pidof -s);在较旧版本上使用 adb shell ps -A | grep <pkg>。

三种基本操作

命令行为
adb logcat将设备日志流式输出到 stdout;持续运行,直到被中断。
adb logcat -d“转储日志并退出。”快照模式,非常适合 CI。
adb logcat -c“清除日志缓冲区。”复现错误前先运行此命令。

(引自 developer.android.com/tools/logcat。)

惯用的 CI 捕获模式:

adb logcat -c                           # clear before scenario
./run-scenario.sh                       # reproduce
adb logcat -d > artifacts/log.txt       # dump after

缓冲区选择

-b <name> 用于选择要读取的内核/用户空间环形缓冲区。缓冲区说明见 developer.android.com/tools/logcat:

缓冲区内容
main默认的应用端缓冲区。不包含系统/崩溃日志。
system框架 / system_server 消息。
crashTombstone 和未处理异常输出。
events结构化/二进制系统事件缓冲区。搭配 -v descriptive 可解码标签名称。
radio无线电/电话相关消息。
kernel内核缓冲区。
all所有缓冲区。
default隐式集合:main、system、crash。

多个 -b 标志和逗号分隔列表都可使用:

adb logcat -b radio
adb logcat -b main -b radio -b events
adb logcat -b main,radio,events

events 缓冲区适合查找 am_*、wm_*、input_focus、notification_*——这些由框架为检测用途发出,而非供人阅读。使用以下命令解码标签名称:

adb logcat -b events -v descriptive

优先级阶梯与过滤表达式

过滤规范由以空格分隔的 tag:priority 对组成。* 匹配所有标签。

字母含义
V详细
D调试
I信息
W警告
E错误
F致命
S“静默(最高优先级,不输出任何内容)”

设置 *:S 会静默所有内容,随后之前指定的任何 tag:P 都会以优先级 P 或更高级别重新启用该标签。仅显示应用日志的规范写法:

adb logcat ActivityManager:I MyApp:D *:S

“抑制所有日志,但保留 ActivityManager(Info 及以上)和 MyApp(Debug 及以上)”——developer.android.com/tools/logcat。

其他常用写法:

adb logcat *:W                          # warnings and above, all tags
adb logcat *:E                          # errors and above (very common in CI)

如果当前工作目录中有名为 S 的文件,zsh/bash 会在未加引号时对 *:S 执行通配符展开——编写脚本时请加引号:

adb logcat "ActivityManager:I MyApp:D *:S"

也可以通过环境变量将同一过滤器设为主机默认值:

export ANDROID_LOG_TAGS="ActivityManager:I MyApp:D *:S"

格式标志 — -v <format>

格式输出内容
brief"显示优先级、标签和 PID"
long"所有元数据字段,消息之间以空行分隔"
process"仅 PID"
raw"不含元数据的原始日志消息"
tag"仅优先级和标签"
thread"旧版格式,显示优先级、PID 和 TID"
threadtime默认。"日期、时间、优先级、标签、PID 和 TID"
time"日期、时间、优先级、标签和 PID"

格式修饰符可与 -v 叠加(可用逗号组合或重复指定):

修饰符效果
color按优先级着色。
descriptive解码事件日志标签名称。
epoch显示自 1970-01-01 起的秒数。
monotonic显示自上次启动以来的 CPU 秒数。
printable转义二进制内容。
uid显示日志进程的 UID 或 Android ID。
usec以微秒精度显示时间。
UTC以 UTC 显示时间。
year在显示的时间中添加年份。
zone添加本地时区。
adb logcat -v json -v UTC -v year       # Android 11+; structured stream, unambiguous time
adb logcat -b all -v color -d           # color dump, all buffers

默认行示例(developer.android.com/tools/logcat):

I/ActivityManager(  585): Starting activity: Intent { action=android.intent.action.MAIN ... }

brief 的格式为:<priority>/<tag>(<PID>): <message>。threadtime 会添加日期、时间和 TID。

时间和 PID 筛选器

  • -T '<date> <time>' — 从给定时间戳及之后的第一条记录开始。格式与 threadtime 相同,例如 '01-01 12:00:00.000'。
  • -T <count> — 输出缓冲区中的最后 N 条记录,然后继续流式输出。
  • -t <count> — 输出最后 N 条记录,然后退出(也就是说,-t 相当于会自动退出的 -T)。
  • --pid <pid> — 仅输出单个进程的记录。可与 pidof 结合使用:
adb logcat --pid=$(adb shell pidof -s com.example.app)

pidof -s 会返回单个 pid;不带 -s 时会返回以空格分隔的列表,--pid 无法直接接受该列表。如果应用可能被终止并重启(Doze、ANR、崩溃),请使用循环重新解析 PID:

while true; do
  PID=$(adb shell pidof -s com.example.app 2>/dev/null)
  [ -n "$PID" ] && adb logcat --pid="$PID"
  sleep 1
done

缓冲区轮转与持久化

对于长时间捕获,可通过轮转将内容持久化到文件:

标志含义
-f <file>写入 <file>,而不是标准输出。**注意:**使用 adb logcat -f 时,文件位于主机端;使用 adb shell logcat -f 时,路径位于设备端。
-r <kbytes>每 N KB 轮转一次输出。需要 -f。
-n <count>最多保留 <count> 个轮转文件(logfile、logfile.1,……)。
-G <size>调整所选 -b 环形缓冲区的内核日志缓冲区大小(例如 1M、16M)。在 logd 的整个运行期间保持有效。
-g打印缓冲区大小和当前使用量,然后退出。用于诊断“为什么缺少日志?”的低开销方法。
adb logcat -G 16M                                     # grow the main buffer
adb logcat -g                                         # inspect sizes
adb logcat -b main -f /data/local/tmp/app.log \
           -r 1024 -n 8                               # 8x ~1MB rotated files (device-side)
adb shell logcat -b main -f /data/local/tmp/app.log \
           -r 1024 -n 8                               # same, but the device runs it

-G 调整的是该缓冲区的全局大小,并会在 logd 重启后继续生效(直到重启设备)。不要在内存受限的设备上设置为 16 MB 后就忘记;如果 CI 集群由多人共用,请在测试套件运行后用 -G 256K 释放内存。

在发布版本中移除 Log.d — R8 规则

除非通过代码缩减将其移除,否则 android.util.Log 调用会保留在发布版 APK 中。proguard-rules.pro 中的标准写法(R8/ProGuard):

-assumenosideeffects class android.util.Log {
    public static *** v(...);
    public static *** d(...);
    public static *** i(...);
    public static boolean isLoggable(java.lang.String, int);
}

-assumenosideeffects 允许 R8 将列出的方法视为纯空操作,因此所有调用(以及参数为常量时的字符串构造器表达式)都会被移除。生产环境诊断应保留 w、e、wtf。使用 Timber 时:移除由 BuildConfig.DEBUG 控制的 DebugTree 初始化,并依赖将警告和错误转发到 Crashlytics 的 ReleaseTree。

对于 Compose,不要对 kotlin.jvm.internal.Intrinsics 使用 -assumenosideeffects ——这会移除编译器依赖的空值检查。

模式

模式:错误做法 — 使用 adb logcat | grep MyApp 进行筛选
# WRONG
adb logcat | grep MyApp
# WRONG because: the device sends every log line over the USB/TCP transport, then the host
# discards 99% via grep. On a busy device this saturates the transport and the grep can lag
# behind real time. Worse, `grep` runs without -F, so a tag that contains regex metacharacters
# (`.`, `(`, `+`) silently mismatches.
# RIGHT — filter at the logcat level
adb logcat MyApp:D *:S

设备只会输出匹配的行;传输不会拥塞。

模式:错误做法 — 认为 -T <count> 会退出
# WRONG
adb logcat -T 100 > tail.txt
# Hangs forever — -T <count> prints the last 100 lines THEN CONTINUES STREAMING.
# RIGHT — `-t` is `-T` with auto-exit
adb logcat -t 100 > tail.txt
模式:用于机器摄取的结构化日志
adb logcat -v json -v UTC -v year -d -b all > run.jsonl

每行都是一个 JSON 对象——通过管道传给 jq:

jq -c 'select(.priority == "ERROR")' run.jsonl

(设备需运行 Android 11 或更高版本才能使用 -v json。)

模式:失败时捕获 — 清除、运行、转储
adb logcat -c
./run-scenario.sh
RC=$?
if [ "$RC" -ne 0 ]; then
  adb logcat -d -v threadtime               > artifacts/logcat.txt
  adb logcat -d -b crash    -v threadtime   > artifacts/crash.txt
  adb logcat -d -b events   -v descriptive  > artifacts/events.txt
fi
exit "$RC"

-c 会在运行前清除日志,因此转储只会捕获该场景的日志。

模式:绑定 PID 的流,可在进程重启后继续工作
while true; do
  PID=$(adb shell pidof -s com.example.app 2>/dev/null)
  if [ -n "$PID" ]; then
    adb logcat --pid="$PID" -v threadtime
  fi
  sleep 1
done

外层循环会在 Doze、ANR 或崩溃导致应用被杀并重启后,重新解析 PID。

模式:-f 的主机端与设备端
# host-side rotation (file lives on the developer machine)
adb logcat -b main -f ~/logs/app.log -r 1024 -n 8

# device-side rotation (file lives in /data/local/tmp on the device)
adb shell logcat -b main -f /data/local/tmp/app.log -r 1024 -n 8
adb pull /data/local/tmp/app.log ~/logs/app.log     # later

强制规则

  • 必须在 logcat 层级进行过滤(例如 MyApp:D *:S),而不是通过主机端的 grep 过滤。在设备上过滤可避免传输通道饱和。
  • 必须在 zsh/bash 中为包含 * 的过滤字符串加引号(例如 "MyApp:D *:S")——如果当前工作目录中存在名为 S 的文件,通配符会被展开。
  • 必须使用 -t <count> 实现“读取末尾日志后退出”。-T <count> 会在初始转储后继续流式输出。
  • 必须在 CI 中使用 -d 进行快照转储。流式运行 adb logcat 需要显式终止。
  • 必须在复现需要附加日志转储的 Bug 前调用 adb logcat -c。否则,转储会包含无关的历史记录。
  • 必须区分 adb logcat -f <file>(主机端路径)与 adb shell logcat -f <file>(设备端路径)。两种 shell 形式的行为并不相同。
  • 必须在发布曾调用 Log.d 记录敏感数据的 APK 时,为 android.util.Log 的 v/d/i(以及 isLoggable)添加 R8 -assumenosideeffects 规则。保留 w/e/wtf。
  • 不得对 kotlin.jvm.internal.Intrinsics 使用 -assumenosideeffects——这会移除 Kotlin 编译器所依赖的空值检查语义。
  • 不得在 Android 11 以下的设备上依赖 -v json——它会静默输出空流。
  • **优先:**使用 -v threadtime,year,UTC(或 -v threadtime -v year -v UTC),以便在 CI 产物中获得明确无歧义的绝对时间戳。
  • **优先:**捕获缓慢发生的 Bug 时,使用 -G 16M 调整 main 缓冲区大小,并在清理阶段恢复默认值。

验证

  • scripts/ 或 .github/workflows/ 中没有脚本将 adb logcat 的输出直接传递给 grep 进行过滤。所有过滤器都作为 logcat 参数传入。
  • CI 失败时捕获的转储至少包含 main、crash 和 events 缓冲区。
  • 发布 APK 不会输出 Log.d 调用(通过 apkanalyzer dex packages app-release.apk | grep 'android.util.Log' 验证,结果仅包含 e/w/wtf)。
  • shell 脚本中包含 *:S 的过滤字符串均已加引号。
  • 所有 --pid 调用都使用 pidof -s <pkg>(单数),并在应用可能重启时通过循环重新解析 PID。
  • CI 中每个长时间运行的 adb logcat 会话都设有墙钟时间上限(timeout 600s adb logcat ...),以免卡住的会话占满磁盘。

参考资料

  • developer.android.com/tools/logcat — 缓冲区表、优先级梯级、过滤表达式、-v threadtime/json/descriptive、格式修饰符、-T/-t/--pid/-r/-n/-G/-g 标志、行格式架构。
  • developer.android.com/tools/adb — adb logcat 主机端简写与 adb shell logcat 的区别。
  • developer.android.com/studio/build/shrink-code — R8 -assumenosideeffects 和 android.util.Log 剥离规则。
  • developer.android.com/reference/android/util/Log — Log.v/d/i/w/e/wtf/isLoggable 签名。
  • 研究笔记 tasks/research/A3-adb-observability-automation.md — 完整的 logcat 功能、-f 主机端与设备端的细微区别、R8 保留规则的理由、CI 失败时捕获日志的模式。
  • 同级技能:../../architecture/understanding-adb-architecture/SKILL.md — 服务端 / 守护进程、传输。
  • 同级技能:../../devices/connecting-to-devices/SKILL.md — 设备状态、wait-for-device。
  • 同级技能:../../devices/connecting-over-wifi/SKILL.md — adb pair / adb connect。
  • 同级技能:../../apps/installing-and-managing-apps/SKILL.md — 安装要捕获其日志的 APK。
  • 同级技能:../../tests/running-instrumented-tests-via-adb/SKILL.md — am instrument -w -r 与 logcat 的交互。
  • 同级技能:../../control/injecting-input-and-state/SKILL.md — 执行手势,并在 logcat 中查看其影响。
  • 同级技能:../../capture/capturing-screenshots-and-screenrecord/SKILL.md — 配套的屏幕截图捕获。
  • 同级技能:../../transfer/extracting-test-artifacts/SKILL.md — 从设备拉取轮转后的日志文件。
  • 同级技能:../../automation/scripting-adb-for-ci/SKILL.md — 完整的 CI 失败时捕获日志 / 重试 / 清理模式。
  • 跨集合:../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md — 与 logcat 交错显示的检测日志。
  • 跨集合:../../../instrumentation/scenarios/launching-activities-with-activityscenario/SKILL.md — 在 events 缓冲区中显示的 Activity 生命周期事件。
  • 跨集合:../../../fundamentals/strategies/applying-testing-strategies/SKILL.md — 何时适合将日志捕获作为可观测性层。

评论

支持 Markdown