第23章:日志与调试——让构建过程不再黑盒
说实话,我刚用Gradle那会儿,最头疼的就是构建失败时那一堆堆的堆栈信息。你想想看,几百行输出,真正有用的就那么几行。后来我才明白,日志和调试不是事后诸葛亮,而是构建脚本开发的核心技能。
这一章,我就把自己这些年积累的日志配置、调试技巧、异常处理经验,一股脑儿分享给你。
23.1 日志级别:从安静到话痨
Gradle的日志系统,说白了就是六个级别。我习惯把它们分成两组:
| 级别 | 命令行参数 | 用途 |
|---|---|---|
| ERROR | -q / --quiet | 只看错误信息 |
| WARN | -w / --warn | 警告+错误 |
| LIFECYCLE | (默认) | 构建生命周期信息 |
| INFO | -i / --info | 详细信息 |
| DEBUG | -d / --debug | 调试信息(最详细) |
我个人最常用的组合是:
- 日常构建:默认LIFECYCLE级别,干净利落
- 排查依赖问题:
--info,看依赖解析过程 - 调试自定义Task:
--debug,但只针对特定包
23.2 自定义日志输出:让日志说人话
默认的日志输出格式,说实话不太友好。我习惯在build.gradle里加一些自定义配置。
23.2.1 使用logger对象
在Task里,你可以直接用logger对象输出:
task checkConfig {
doLast {
logger.lifecycle("开始检查配置文件...")
logger.info("当前环境: ${project.property('env')}")
logger.debug("详细参数: ${project.properties}")
if (someCondition) {
logger.warn("配置项缺失,使用默认值")
}
}
}
嗯,这里要注意:logger.lifecycle是默认可见的,logger.info需要加-i才能看到。我刚开始总搞混,后来养成了习惯——关键步骤用lifecycle,细节用info。
23.2.2 自定义日志格式
如果你觉得默认格式太啰嗦,可以自己定义:
// 在build.gradle顶部
gradle.services.get(LoggingManagerInternal).level = LogLevel.INFO
// 自定义输出格式
gradle.addListener(new TaskExecutionListener() {
void beforeExecute(Task task) {
println "[${new Date().format('HH:mm:ss')}] 开始: ${task.name}"
}
void afterExecute(Task task, TaskState state) {
if (state.failure) {
println "[${new Date().format('HH:mm:ss')}] 失败: ${task.name}"
} else {
println "[${new Date().format('HH:mm:ss')}] 完成: ${task.name}"
}
}
})
避坑指南:我曾经在自定义日志里用了System.out.println,结果发现输出顺序和实际执行顺序对不上。后来改用logger对象,问题就解决了。记住:在Gradle里,永远用logger,别用System.out。
23.3 调试构建脚本:找到问题根源
构建脚本出问题,最怕的就是「明明没报错,但结果不对」。我总结了三个调试手段。
23.3.1 使用--stacktrace
这个参数能打印完整的堆栈信息:
gradle build --stacktrace
但说实话,堆栈信息太长了。我一般配合--info一起用:
gradle build -i --stacktrace 2>&1 | grep -E "(ERROR|Caused by|at org.gradle)"
23.3.2 打印变量值
最简单的调试方式,就是在关键位置打印变量:
task debugDependencies {
doLast {
// 打印所有依赖
configurations.each { config ->
println "配置: ${config.name}"
config.allDependencies.each { dep ->
println " -> ${dep.group}:${dep.name}:${dep.version}"
}
}
// 打印项目属性
println "项目属性:"
project.properties.each { key, value ->
println " ${key} = ${value}"
}
}
}
23.3.3 使用Gradle内置的调试工具
Gradle 7.0+ 提供了--scan参数,能生成构建扫描报告:
gradle build --scan
这个报告会告诉你:
- 每个Task的执行时间
- 依赖解析的详细信息
- 缓存命中情况
- 插件加载顺序
23.4 异常处理:优雅地处理构建失败
构建脚本里的异常处理,和普通Java代码不太一样。你不能随便try-catch,因为Gradle有自己的异常处理机制。
23.4.1 使用GradleException
当检测到配置错误时,我习惯抛出GradleException:
task validateConfig {
doFirst {
def requiredProps = ['jdkVersion', 'outputDir']
requiredProps.each { prop ->
if (!project.hasProperty(prop)) {
throw new GradleException(
"缺少必要属性: ${prop}。请在gradle.properties中配置"
)
}
}
}
}
23.4.2 使用StopExecutionException
如果你想停止当前Task,但不影响其他Task:
task checkDiskSpace {
doLast {
def freeSpace = new File('/').freeSpace
if (freeSpace < 1024 * 1024 * 1024) { // 小于1GB
throw new StopExecutionException(
"磁盘空间不足,当前剩余: ${freeSpace / 1024 / 1024 / 1024}GB"
)
}
println "磁盘空间充足"
}
}
23.4.3 全局异常处理
你可以注册一个全局的异常处理器:
gradle.addBuildListener(new BuildAdapter() {
void buildFinished(BuildResult result) {
if (result.failure) {
println "构建失败,原因: ${result.failure.message}"
// 可以在这里发送通知、记录日志等
}
}
})
23.5 知识体系总览
下面这张图,是我对日志与调试的整体理解:
这张图把日志与调试分成了四个维度:日志级别控制输出量,自定义输出让信息更可读,调试手段帮你定位问题,异常处理则让构建更健壮。这四个维度缺一不可。
23.6 实战:一个完整的日志配置示例
最后,我给你看一个我项目里实际在用的日志配置:
// build.gradle
import org.gradle.internal.logging.LoggingManagerInternal
// 1. 设置日志级别
gradle.startParameter.logLevel = LogLevel.INFO
// 2. 自定义日志输出
gradle.addListener(new TaskExecutionListener() {
private long startTime
void beforeExecute(Task task) {
startTime = System.currentTimeMillis()
logger.lifecycle "▶ 执行: ${task.path}"
}
void afterExecute(Task task, TaskState state) {
def duration = System.currentTimeMillis() - startTime
if (state.failure) {
logger.error "✗ 失败: ${task.path} (${duration}ms)"
} else if (state.skipped) {
logger.warn "○ 跳过: ${task.path}"
} else {
logger.lifecycle "✓ 完成: ${task.path} (${duration}ms)"
}
}
})
// 3. 异常处理
gradle.addBuildListener(new BuildAdapter() {
void buildFinished(BuildResult result) {
if (result.failure) {
logger.error "构建失败,请检查日志"
// 可以在这里做清理工作
}
}
})
这个配置我用了两年多,效果很好。每次构建都能看到清晰的执行流程,出问题也能快速定位。
最后说一句:日志和调试不是锦上添花,而是构建脚本开发的必备技能。你花10分钟配置好日志,未来可能省下10小时的排查时间。这笔账,怎么算都划算。
公众号:蓝海资料掘金营,微信deep3321