- 静态分析
- 代码质量
- 开发工具
【免费下载链接】error-prone
Catch common Java mistakes as compile-time errors
本文是 Error Prone 内置检查器FloggerLogString的完整技术指南。该检查器位于 Flogger 日志框架的配套检查器家族中,其职责是:当开发者把非编译期常量表达式传给LoggingApi.log(String)时,在编译期直接报错,并给出六类可落地的修复建议。读完本文,你将掌握 Flogger 日志消息的正确写法、@CompileTimeConstant注解的使用边界、检查器的底层匹配逻辑,以及从源码与测试用例中验证修复方案的完整思路。
检查器要解决的问题:为什么log(String)只能收常量
Flogger(com.google.common.flogger)的LoggingApi.log(String)重载有一个关键约定:传入的字符串必须是编译期常量。这与 Flogger 的设计哲学一致——把日志消息模板与参数分离,所有参数通过格式化占位符(%s、%d等)注入,从而避免字符串拼接带来的性能开销、格式串混乱和日志注入风险。
因此,Error Prone 内置的 FloggerLogString 检查器会在编译期拦截此类写法,并给出错误提示:
Arguments to log(String) must be compile-time constants or parameters annotated with @CompileTimeConstant. If possible, use Flogger's formatting log methods instead.
该检查器由com.google.errorprone.bugpatterns.flogger.FloggerLogString类实现(见 FloggerLogString.java),标记为@BugPattern(summary = "...", severity = ERROR),即默认按 ERROR 级别生效。它在 BuiltInCheckerSuppliers.java 中与FloggerFormatString、FloggerLogVarargs、FloggerSplitLogStatement等 Flogger 系列检查器一起被注册为默认启用的内置检查器。
检查器如何工作:两条匹配规则
从源码看,FloggerLogString只做两件事(FloggerLogString.java):
- 命中
log(String)调用:使用instanceMethod().onDescendantOf("com.google.common.flogger.LoggingApi").named("log").withParameters("java.lang.String")匹配所有LoggingApi子类型上的log(String)方法调用(包括FluentLogger的atInfo().log(...)、atWarning().log(...)等链式调用)。 - 参数不是编译期常量:用
CompileTimeConstantExpressionMatcher.instance()校验第一个参数;只有两者同时成立才报错。
什么算"编译期常量"
CompileTimeConstantExpressionMatcher(见 CompileTimeConstantExpressionMatcher.java)对"编译期常量表达式"的定义如下:
- Java 编译器能在编译期确定常量值的任何表达式(含
constValue可计算的字面量、static final常量字段); null字面量;- 单个标识符,且该标识符是声明为
final并带有@CompileTimeConstant注解的方法形参或类/枚举字段。
此外,该 matcher 还递归支持:条件表达式(两个分支都满足)、switch 表达式(每个 case 为常量表达式或抛异常)、字符串+拼接(两侧均满足)、括号表达式,以及ImmutableList.of(...)/ImmutableSet.of(...)这类元素全部为常量的不可变集合工厂调用。注意:普通方法形参、未标注的局部变量、obj.toString()这类方法调用返回值都不属于编译期常量。
@CompileTimeConstant注解本身声明在com.google.errorprone.annotations包下(见 CompileTimeConstant.java),@Target({PARAMETER, FIELD}),可标注方法形参与类字段,约束调用方必须传入满足上述条件的表达式。
六类推荐修复方式(原文档核心内容)
当你命中 FloggerLogString 报错时,按优先级依次尝试以下修复。
1. 拆掉String.format()调用
如果参数是String.format(...)调用,直接拆掉它,把格式串与参数交给 Flogger 的格式化log方法:
// 修复前 log(String.format("format %s", arg)); // 修复后 log("format %s", arg);Flogger 的log(String format, Object... args)本身就是 printf 风格格式化,无需外层再包一层String.format。
2. 去掉toString()
如果参数是obj.toString(),直接删掉toString(),改调log(Object)重载:
// 修复前 log(obj.toString()); // 修复后 log(obj); // 等价于 log("%s", obj)log(Object)与log("%s", obj)语义等价,Flogger 会调用对象自身的toString()惰性格式化。
3. 给形参加@CompileTimeConstant
如果参数是方法形参,且该参数确实只应接收编译期常量,就给形参补上注解(形参需为final):
// 修复前 void logMessage(String message) { logger.atInfo().log(message); } // 修复后 void logMessage(@CompileTimeConstant final String message) { logger.atInfo().log(message); }注解生效后,该形参的所有调用点都会被 Error Prone 强制要求传入编译期常量,属于"把契约显式化"的做法。
4. 处理"实际上有效 final"的局部常量变量
如果参数是一个初始化为常量值的 effective final 局部变量,有两种修法:
- 显式加上
final修饰符,使其成为真正的编译期常量引用:
// 修复前 String message = "hello"; logger.atInfo().log(message); // 修复后 final String message = "hello"; logger.atInfo().log(message);- 如果该变量只在当前这一条 log 语句中使用,直接把常量值内联进去:
logger.atInfo().log("hello");5. 字符串拼接改为格式化参数
如果参数是"字面量 + 变量"的字符串拼接,改用 Flogger 的格式化写法,把变量移到占位符后面:
// 修复前 log("foo " + bar + " baz"); // 修复后 log("foo %s baz", bar);注意,字符串拼接本身能否通过检查取决于两侧是否都是常量:测试用例中log("hello " + CONSTANT)、log(CONSTANT + localFinal)(localFinal为final局部常量)都是合法的,而log("foo " + s)(s为普通形参)会被报错(见 FloggerLogStringTest.java)。
6. 异常场景改用withCause(exception).log()
如果参数是exception.toString()或exception.getMessage(),不要把它拼进日志消息,而应使用 Flogger 的因果链 APIwithCause(exception).log()(可不带消息,或带常量消息):
// 修复前 logger.atWarning().log(e.toString()); // 修复后 logger.atWarning().withCause(e).log();把异常作为 cause 传入,日志系统会自动带上完整堆栈,比手动调用toString()/getMessage()更有诊断价值。这一修复方向与 Error Prone 的另一内置检查器 FloggerLogWithCause 的指导一致(参见 FloggerLogWithCause.md):该检查器针对"catch 块中仅log("Failed!")未携带异常"的写法,建议改为logger.atWarning().withCause(e).log("Failed!")。相关源码在 FloggerFormatString.java,当格式化参数多于占位符且最后一个实参是 Throwable 子类型时,会自动建议withCause修复。
兜底方案:"%s"格式串
如果上述方案都不可行(例如无法修改方法签名、变量值确实动态),最省事的做法是把任意表达式包进"%s"占位符:
// 任意写法 log(expr); // 兜底写法 log("%s", expr);"%s"是编译期常量格式串,expr作为参数传入格式化重载,既绕过检查,又不丢失任何信息。
测试用例:验证检查器行为的"正反两面"
仓库中的 FloggerLogStringTest.java 用CompilationTestHelper完整覆盖了检查器行为,可作为理解规则边界的权威参考:
- positive 用例(应报错):普通形参
log(s)、"foo " + s拼接,诊断消息包含Arguments to log(String) must be compile-time constants; - negative 用例(不应报错):字符串字面量
log("hello")、@CompileTimeConstant形参、static final String CONSTANT、final局部变量,以及两侧均为常量的拼接组合(如log("hello " + CONSTANT)、log(s + localFinal + CONSTANT)等十余种),还包括log((String) null)。
这些用例印证了上文"编译期常量"判定的全部边界情形,是排查误报/漏报时的第一手参考资料。
与相邻检查器的协作
FloggerLogString 只是 Error Prone Flogger 检查器家族的一员,实际项目中通常配合使用:
- FloggerFormatString(FloggerFormatString.java):校验
log格式化重载中 printf 风格格式串的合法性,如占位符与参数数量匹配、%s与实参类型匹配,并在参数多余且末位是异常时建议withCause; - FloggerLogWithCause(FloggerLogWithCause.md):引导在异常处理路径中使用
withCause(e)携带上下文; - FloggerLogVarargs、FloggerSplitLogStatement:分别针对 varargs 传参与跨语句拆分 log 调用的问题。
在 BuiltInCheckerSuppliers.java 中可以看到这些检查器相邻注册。开发者应把它们视为一套"Flogger 用法规范"整体启用,效果最佳。
如何使用
FloggerLogString 是 Error Prone 的默认启用检查器,无需额外配置。只要项目已接入 Error Prone 编译插件(通过 Maven/Gradle/Bazel 配置-Xplugin:ErrorProne等),编译时便会自动执行本检查。若因历史代码存量需要临时关闭,可在命令行加-Xep:FloggerLogString:OFF,或使用-Xep:FloggerLogString:WARN降级为告警;完整开关参数可参考 Error Prone 的ErrorProneOptions文档。本文所有代码示例中的 Flogger API 均为com.google.common.flogger.FluentLogger,用法见测试用例中的FluentLogger.forEnclosingClass()初始化方式。
小结
FloggerLogString 检查器把"日志消息必须是编译期常量"这条约定落到了编译期强制。通过本文梳理的六类修复(拆String.format、去toString、加@CompileTimeConstant、final局部常量、格式化拼接、withCause)外加"%s"兜底,你可以系统性地消除此类编译错误,写出更高效、更安全、更易读的 Flogger 日志代码。
- 静态分析
- 代码质量
- 开发工具
【免费下载链接】error-prone
Catch common Java mistakes as compile-time errors
相关推荐
Error Prone HashtableContains 检查器实战:告别 contains() 语义陷阱,编译期强制区分 containsKey 与 containsValue
Error Prone HashtableContains 检查器实战:告别 contains 语义陷阱,编译期强制区分 containsKey 与 conta
静态分析代码质量开发工具Error Prone 检查器实战:DateFormatConstant——把线程不安全的日期格式化常量挡在编译期
Error Prone 检查器实战:DateFormatConstant——把线程不安全的日期格式化常量挡在编译期 导读 DateFormatConstant
静态分析代码质量开发工具Error Prone 的 FallThrough 检查器:用编译期诊断强制 switch 穿透注释规范
Error Prone 的 FallThrough 检查器:用编译期诊断强制 switch 穿透注释规范 导读 本文围绕 Error Prone 的 FallT
静态分析代码质量开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考