logcat 分析 SKILL
何时使用
触发场景:
- 用户提供了 logcat 文本文件,描述了一个 framework 层问题(黑屏、卡顿、崩溃、ANR、异常重启、窗口切换异常等)
- 用户想了解某个时间段内系统/应用发生了什么
不做的事:
- 不处理日志下载、解压、多层分包合并(由上游步骤处理)
- 不做 ANR traces 文件深入分析
- 不做 native tombstone 解析
- 不支持 threadtime 以外的格式
- 不处理 kernel 日志
⚠️ 硬性约束(违反会导致上下文崩溃或分析丢失)
文件读取红线
- ❌ 绝对禁止 Read
.logcat-analysis/events.jsonl与.logcat-analysis/index.jsonl。这两个文件单个就有数百万 token 级,一次 Read 直接撑爆上下文。需要查内容时必须用query_by_pid.py/query_by_time.py/grep。 - ❌ 禁止 Read 大于 5 MB 的
activity_timeline.json/process_timeline.json。先ls -lh .logcat-analysis/看大小,超标就改用grep '"event": "..."'过滤。 - ✅ 始终可读:
sources.json、signals_high.json、signals_summary.md、analysis_log.md。 - ✅
signals_summary.md= 步骤 3 stdout 的持久摘要(per-rule 命中数 + 头 10 条信号),上下文被压缩后回头cat即可恢复,不必再读signals.json整体。
进度持久化(核心)
.logcat-analysis/analysis_log.md 是分析进度的唯一可信来源,让任何中断(上下文压缩、会话切断、人工暂停)都能续接。
- ✅ 每轮分析开始前:
cat .logcat-analysis/analysis_log.md读取已确认事实/已排除方向/待查问题,再决定下一步。 - ✅ 以下节点必须立即 Edit
analysis_log.md:- 步骤 1(含 1.1 / 1.2 子节)完成后 → 写"分析上下文"段
- 步骤 4 进入 playbook、形成初步假设后 → 写"当前假设"+"待查问题"
- 步骤 4 中每次 query 拿到关键证据后 → 追加"已确认的事实"(query 脚本 stderr 也会打印 REMINDER 提示)
- 排除某方向时 → 追加"已排除的方向"
- 切换假设方向前 → 把当前未结论的内容归位到对应段
- ✅ 每次更新"已确认的事实"段后,回头扫一遍"待查问题"段,已答的删除。否则 待查问题 段会和事实段重复,造成下一次续接时混乱(以为还有事要查,其实已经答了)。
- ❌ 不要把假设链/证据/排查方向只留在对话里。压缩或中断后无法恢复。
query 节制
- 默认
--max-lines 200,每次 query 只带一个具体问题。 - 同一假设的多次扩展 query 之间,必须先把上一轮结论 Edit 进
analysis_log.md再发下一轮,避免证据只活在临时上下文里。
输入
必需:
files:一个或多个 logcat 文本文件路径(threadtime 格式,支持多文件按顺序合并处理)
可选:
year:日志的年份(默认当前年;从文件名或日志内容推断,例如文件名含20260416则 year=2026)output_dir:中间产物目录,默认.logcat-analysis/event_time:强烈推荐提供。问题发生的中心时间点(如14:08:30),是窗口选取和后续查询的锚点;若用户不知道时间,进入全量模式,完成信号检测后再从 high signal 推断锚点target_pid/target_package:已知的问题进程(package name、PID 或自然语言描述均可,Step 1.5 会统一解析为 package name)
工作流
路径说明:以下命令使用
${CLAUDE_SKILL_DIR}环境变量,指向本 skill 的安装目录。Claude Code 在执行 bash 命令时 cwd 是 project root,而非 skill 目录,因此必须使用${CLAUDE_SKILL_DIR}才能正确定位 bundled 脚本和 playbook。
步骤 0:确认 event_time 并选取日志文件
窗口模式下 event_time 是整个分析的前提,在执行窗口选取前必须先确认:
- 若用户已提供时间点(如
14:08:52):直接进入文件选取 - 若用户提示词中未提供:先向用户询问,不要自行推断
- 若用户明确不知道时间:告知将对所有文件全量解析,跳过文件选取,手动指定
--files为目录下全部 logcat 文件;完成步骤 3 后再从 high signal 推断 event_time 锚点
确认 event_time 后,自动选取相关文件:
python3 ${CLAUDE_SKILL_DIR}/scripts/select_logs.py \
--dir <log_dir> \
--time "14:08:52" \
--year <YYYY> \
[--window-before 600] \
[--window-after 120]
- stdout:按时间排序的文件路径列表,直接用于步骤 1 的
--files - stderr:覆盖时间范围摘要(确认窗口是否合理)
- 若返回 ERROR(无文件覆盖该时间点):检查 event_time 是否正确,或日志是否缺失
默认窗口:事发前 10 分钟、事发后 2 分钟,覆盖大多数触发链。如问题涉及开机初始化或长时间积累,用 --window-before 1800 扩大。
步骤 1:解析日志 + 确认分析上下文
python3 ${CLAUDE_SKILL_DIR}/scripts/parse_logcat.py \
--files <步骤0输出的文件列表> \
--output-dir .logcat-analysis/ \
--year <YYYY>
产出:events.jsonl(结构化 event 记录)、index.jsonl(文本日志索引)、sources.json(元信息)
注意:
--year务必与日志实际年份一致,否则时间戳会错位- 多个文件按顺序处理,统一写入同一 work dir
完成 parse 后,必须做两个 post-check 才能进入步骤 2,目的是先锁定"什么时间 + 什么进程"作为后续 query 的过滤锚点。
1.1 验证 event_time 在 time_range 内
- 用户已提供时间点 → 核对
sources.json的time_range,若未覆盖立即告知用户停止分析 - 用户不知道时间(全量模式)→ 跳过此检查,等步骤 3 后从
signals_high.json推断最早 high 信号时间作为锚点
1.2 解析目标进程身份
| 输入形式 | 处理方式 |
|---|---|
明确 package name(如 com.flyme.auto.mediacontrol) |
直接使用 |
| 自然语言描述(如"音乐小窗"、"导航应用") | 在 process_timeline.json 中搜索候选进程名,列出后请用户确认 |
PID(如 pid=7579) |
grep '"pid": 7579' .logcat-analysis/events.jsonl | head -5 反查 process_name |
1.3 输出确认摘要并写入 analysis_log.md
分析上下文:event_time=<HH:MM:SS>(±<N>分钟窗口),target=<package_or_process_name>
立即 Edit .logcat-analysis/analysis_log.md 的"分析上下文"段,填入 event_time、target、时间窗口。这是中断恢复的锚点,不要跳过。
步骤 2:构建时间线
python3 ${CLAUDE_SKILL_DIR}/scripts/build_timeline.py --work-dir .logcat-analysis/
产出:activity_timeline.json(Activity 生命周期)、process_timeline.json(进程启动/死亡/被杀)
注意:
- 这两个时间线需要来自 system_server 的 logcat。如果日志只包含 app 进程日志,时间线将为空——这是正常的,继续后续步骤
- Android 12 起 Activity 生命周期 tag 已从
am_*改为wm_*前缀
步骤 3:检测信号
python3 ${CLAUDE_SKILL_DIR}/scripts/detect_signals.py --work-dir .logcat-analysis/
产出:signals.json(完整信号列表)、signals_high.json(仅 high severity,通常 <10 条)、signals_summary.md(持久摘要)
stdout = 实时反馈,
signals_summary.md= 持久摘要:脚本 stdout 打印 per-rule 命中数 + 头 10 条信号 JSON,可直接用于步骤 4 的 playbook 路由;同样的内容也写到signals_summary.md,上下文被压缩或会话中断后回头cat即可恢复,无需再读取 signals.json 整体。
当前覆盖的规则(18 条):
fatal_exception、anr_in_process、tombstone_written(high)window_freeze、force_finishing、watchdog、hal_died(high)proc_died_foreground(前台进程异常死亡,high)system_server_restart(system_server 进程死亡,high)input_anr(InputDispatcher 报告 ANR,high)lowmem_kill、low_memory_event、proc_died_background、selinux_denial(medium)binder_died(binder/HwBinder 通信失败,medium)skipped_frames(≥30 帧,medium)user_switch(用户切换生命周期,info)task_auto_restored(user switch 后 WM 静默恢复 task,info)
步骤 4:按 playbook 执行
主流程到此结束。完整的"读产物 → 形成假设 → 下钻 → 输出报告"由 playbook 驱动。
路由表
按 signals_summary.md(步骤 3 stdout / 文件)的命中信号选 playbook:
| 命中信号 | playbook |
|---|---|
fatal_exception / tombstone_written |
${CLAUDE_SKILL_DIR}/playbooks/crash.md |
anr_in_process / input_anr / watchdog(独立出现) |
${CLAUDE_SKILL_DIR}/playbooks/anr.md |
user_switch + task_auto_restored(或用户描述涉及切换/弹窗) |
${CLAUDE_SKILL_DIR}/playbooks/user-switch.md |
| 其他 / 多类信号同时出现 / 不确定 | ${CLAUDE_SKILL_DIR}/playbooks/overview.md |
多类信号同时命中时,按 high severity 优先;都是 high 时按时间最早的一类优先。
playbook 内部覆盖
每个 playbook 自带完整流程:定位锚点 → 读产物(sources.json / signals_summary.md / timeline)→ 形成假设 → 下钻 query → 输出 .logcat-analysis/report.md。SKILL.md 不再重复 query 三件套(trace_starter / query_by_pid / query_by_time)的命令样例,详见 overview.md 第 5 节。
输出约束
- 报告 Write 到
.logcat-analysis/report.md,对话里只输出 < 300 字精简版。 - 文件读取红线与
analysis_log.md持久化见前文「硬性约束」一节,此处不复述。
何时停止分析
证据链断裂或日志缺失时,立即停止 query 循环,在 report.md 写明"证据不足,需补充 X"。以下 4 类情况是明确的退出信号:
日志 time_range 不覆盖问题时间点
- 第 1 次:用
select_logs.py --window-before 1800扩大窗口重 parse - 扩窗 2 次后仍不覆盖 → 停。报告写"日志时间范围不覆盖事发时间,需补充 [事发时间] 前后的 logcat"
- 第 1 次:用
关键 tag 全部 0 命中
- playbook 提示的关键 tag(如 ANR 场景的
InputDispatcher/Binder、crash 场景的AndroidRuntime/DEBUG)在事发时间窗内全部 0 命中 → 停 - 报告写"事发时间窗内未捕获 [tag 列表],需确认 logcat buffer 是否完整 / 该 buffer 是否启用"
- playbook 提示的关键 tag(如 ANR 场景的
证据链断在 system_server 而日志只有 app
activity_timeline.json/process_timeline.json为空(步骤 2 已提示)+ 需要追"是谁启动 / 是谁杀进程"时 → 停- 报告写"需补充 system_server 进程的 logcat,当前日志只包含 app 进程"
playbook 走完仍无定论
- 经过 playbook 全部步骤 + overview.md 第 5 节"扩展文件集" + 第 6 节"扫描未知区域"后仍无清晰因果链 → 停
- 报告写"证据不足,可能根因方向:[A / B / C],需补充 [具体数据,如 traces.txt / tombstone / kernel log]"
退出条件触发时,不要继续盲扩 window-before 或盲发 query。承认日志限制比堆砌无效证据更有价值。
已知限制
- 仅支持 threadtime 格式
- 仅基于 Android 12(android-12.0.0_r34)的 event-log-tags schema;其他版本的 tag 字段数不同时可能出现字段串位
- 跨年日志需手动指定
--year - 多行 Java stack trace 不合并,每行独立索引
- 厂商定制 tag(AAOS OEM、MIUI 等)在 unknown_event_tags 中列出,不结构化解析
query_by_pid默认合并 index.jsonl + events.jsonl;query_by_time默认只搜索 index.jsonl,加--include-events可包含结构化事件