第2章 采集缓存日志与数据

2.1 现象识别:什么情况需要采集缓存数据?

缓存问题很少直接报红,更多表现为“构建变慢”“缓存未命中”“构建结果不可复现”等隐性症状。当出现以下任一现象时,应立即启动数据采集流程:

  • 缓存命中率断崖下降:构建扫描中缓存命中率低于50%或持续走低。
  • 本地构建与CI构建结果不一致:相同代码在本地和CI上产生不同产出。
  • 任务输出被反复重算:每次构建都重新执行同一Task,即使输入未变。
  • 缓存目录异常膨胀:~/.gradle/caches 占用空间超过预期值。
  • 构建日志中出现“CACHE MISS”或“NO CACHE”警告。

采集时机很重要:必须在问题复现的同一构建中采集数据,不要先尝试修复再回溯日志。保持项目状态、JDK版本、Gradle版本与问题环境一致。

2.2 数据采集方法

缓存排查需要三类核心数据:详细日志(记录每个Task的缓存决策)、构建扫描(可视化分析缓存命中/未命中)、缓存统计(量化数据观察趋势)。三者互为补充,缺一不可。

2.2.1 启用 --info 与 --debug 日志级别

默认Gradle输出 --lifecycle 级别日志,不包含缓存决策细节。必须显式提升日志级别:

  • --info:输出每个Task的缓存状态(HIT / MISS / NO CACHE),足以覆盖80%的缓存诊断。
  • --debug:输出Task输入/输出文件哈希、缓存键计算过程,适合深入分析“为什么未命中”。

诊断策略:先用 --info 快速扫描,若发现异常再对特定Task追加 --debug,避免日志爆炸。

典型用法:

# 仅查看缓存命中结果
./gradlew assembleDebug --info

# 全量调试日志(重定向到文件便于搜索)
./gradlew assembleDebug --debug 2>&1 | tee build_debug.log

2.2.2 导出构建扫描(Build Scan)

Gradle Build Scan 提供Web仪表盘,一键查看每个Task的缓存状态、耗时、输入快照。导出步骤:

  1. 添加扫描插件或在构建命令后加 --scan:
    ./gradlew assembleDebug --scan
  2. 构建完成后,终端输出一个唯一URL,例如:
    Publishing build scan...
    https://ge.gradle.org/s/example-scan-uuid
  3. 打开该链接,在 “Performance” → “Cache” 面板查看命中率、未命中Task列表及原因。
  4. 如需保存到本地,安装 Gradle Enterprise 或使用 --build-cache-debug 生成JSON报告。

构建扫描不包含完整的Task输入哈希细节,需要与 --debug 日志配合使用:扫描给出“未命中Task列表”,日志给出“具体哪些输入文件变了”。

2.2.3 收集缓存统计(Cache Statistics)

Gradle内置缓存统计API,可在构建结束时输出结构化数据。有两种方式:

方式命令/配置输出内容
运行时参数
./gradlew --build-cache-stats
总命中数/总请求数、字节命中率、本地/远程缓存请求分布
Gradle Enterprise APIPOST到GE服务按Task、按缓存密钥分组的详细统计
自定义插件实现 BuildCacheService 监听器任意自定义统计(如特定文件类型的命中率)

推荐始终使用 --build-cache-stats 收集基础指标:

./gradlew clean assembleDebug --build-cache-stats
# 输出示例:
# Build cache statistics
#   Total cache operations: 42
#   Cache hits: 30 (71.4%)
#   Cache misses: 12
#   Data from cache: 245 MB
#   Data stored to cache: 89 MB

2.3 原因定位:从采集的数据中初步诊断

根据采集到的三类数据,使用下面的判断规则快速定位问题类型:

数据特征可能原因下一步动作
--info日志中大量“NO CACHE”且未配置build cache远程缓存未启用或网络不可达检查 settings.gradle.kts 中 buildCache 配置
构建扫描中缓存命中率低,且未命中Task的输入包含时间戳/随机值Task输入声明不完整(如使用了 @InputFiles 但未排除无关文件)用 --debug 查看Task输入文件列表,移除动态文件
构建扫描中所有Task均为MISS,且本地和远程缓存状态一致Gradle版本升级导致缓存键格式变化清理 ~/.gradle/caches/journal-* 后重试
--build-cache-stats 显示高命中率但构建仍然缓慢缓存还原速度慢(例如远程缓存延迟高)检查网络延迟或切换到本地SSD缓存
多次构建中缓存统计波动巨大并发构建冲突或增量编译与缓存交叉影响单线程重试对比,添加 --no-parallel

2.4 修复动作:确保数据采集的正确性

在进入更深入的原因定位前,先排除采集本身的问题:

  • 确认日志文件完整:检查是否因磁盘空间不足导致日志截断,使用 wc -l build_debug.log 对比预期行数。
  • 构建扫描是否过期:免费版Build Scan仅保留30天,及时导出PDF或截图。
  • 缓存统计是否覆盖了所有Task:--build-cache-stats 默认只统计 CacheableTask,不包含未启用缓存的Task。
  • 多项目构建:确保在根项目执行命令,子项目各自独立启用缓存。

2.5 复测:验证数据采集成功

执行一个简单的“空构建”验证采集工作是否正常:

# 1. 清理上次构建
./gradlew clean

# 2. 运行一次构建,开启完整采集
./gradlew assembleDebug --info --scan --build-cache-stats

# 3. 检查输出是否包含以下三个关键信息
#    - "Build cache state for task:..." (每行一个Task)
#    - 构建扫描URL
#    - "Build cache statistics" 区块

若缺少任意一项,按以下顺序排查:

  • 缺少info日志 → 检查是否误用了 -q 或 --quiet 覆盖日志级别。
  • 缺少扫描URL → 确认Gradle版本 ≥ 4.3,且未使用 --no-scan。
  • 缺少缓存统计 → 确认Gradle版本 ≥ 6.0,且未使用自定义缓存配置禁用统计。

2.6 预防:将采集配置纳入开发流程

避免每次排查都临时加参数,建议在项目中预置以下配置:

  • 在 gradle.properties 中设置默认日志备份:
    org.gradle.logging.level=info
    org.gradle.caching.debug=true
  • 在CI pipeline中自动导出构建扫描:添加 --scan -Pgradle.enterprise.url=...。
  • 定期运行缓存统计脚本:每周执行 ./gradlew --build-cache-stats 并将结果写入监控仪表盘。
  • 培训团队:要求开发者遇到构建缓慢时先采集这3种数据再提问,减少沟通成本。
缓存数据采集流程 感知异常 构建变慢/缓存未命中 ① 启用日志 --info / --debug ② 导出构建扫描 --scan → Web URL ③ 收集缓存统计 --build-cache-stats ④ 综合定位 日志+扫描+统计交叉验证 ⑤ 修复 & 复测 未解决则重新采集 采集原则:先复现原样采集,不修改任何缓存配置,保留完整上下文。 三者缺一不可:日志看决策,扫描看趋势,统计看量化。

本章要点回顾:缓存数据采集是排查的起点,必须在问题复现的同一构建中同时启用 --info(或 --debug)、--scan 和 --build-cache-stats。将采集命令预置到项目脚本和CI中,能大幅缩短定位周期。