1. 概述
本文将深入介绍 Google 设计的 Java 日志框架 Flogger。它提供了一种流式(fluent)API,让日志记录更简洁、高效且具备更强的表达力。
如果你厌倦了传统日志框架中冗长的条件判断和性能损耗,Flogger 可能会成为你项目中的新宠。
2. 为什么选择 Flogger?
市面上已有 Log4j、Logback 等成熟日志框架,Flogger 的出现并非重复造轮子,而是为了解决一些实际痛点。下面我们从几个关键维度来对比分析。
2.1. 可读性 ✅
Flogger 的流式调用风格极大提升了日志语句的可读性和表达能力。
比如我们想每 10 次循环记录一次日志:
传统写法:
int i = 0;
// ...
if (i % 10 == 0) {
logger.info("This log shows every 10 iterations");
i++;
}
而使用 Flogger,一行搞定:
logger.atInfo().every(10).log("This log shows every 10 iterations");
虽然看起来参数略多,但这种链式调用语义清晰、逻辑内聚,避免了分散的 if 判断,代码更干净,也更容易维护。
2.2. 性能优化 ✅
Flogger 在性能设计上非常讲究,核心原则是:避免在日志未启用时执行不必要的对象 toString() 操作。
推荐写法(延迟 toString):
User user = new User();
logger.atInfo().log("The user is: %s", user);
上面的写法中,user.toString()
只有在日志真正输出时才会被调用。而下面两种写法 ❌ 是典型的“踩坑”操作:
// ❌ 错误:提前调用 toString()
logger.atInfo().log("The user is: %s", user.toString());
// ❌ 错误:字符串拼接导致立即 toString()
logger.atInfo().log("The user is: %s" + user);
这两种方式无论日志级别是否开启,都会触发 toString()
,可能带来严重性能问题,尤其在高频调用场景下。
2.3. 可扩展性 ✅
Flogger 提供了良好的扩展机制。虽然核心功能已足够强大,但你可以通过自定义 Logger 类来封装业务相关的日志行为。
例如,我们想为用户操作日志统一添加 userId
和 username
:
logger.at(INFO).forUserId(id).withUsername(username).log("Message: %s", param);
要实现这种 DSL 风格,你需要:
- 创建一个
UserLogger
类继承AbstractLogger
- 在类中实现
forUserId()
和withUsername()
等自定义方法 - 基于
FluentLogger
的实现进行扩展(可直接复制其结构作为起点)
⚠️ 注意:扩展需要编写支持类,不是简单的配置就能完成。
2.4. 调用效率 ✅
这是 Flogger 的一大亮点。传统日志框架广泛使用 varargs
(可变参数),这会带来两个问题:
- 每次调用都要创建
Object[]
数组 - 基本类型需要自动装箱(如 int → Integer)
即使日志被禁用,这些开销依然存在,尤其在 debug 日志密集的循环中,性能损耗明显。
Flogger 的解决方案非常简单粗暴:彻底抛弃 varargs,改用流式链式调用。
比如:
logger.atInfo().withCause(e).log("Message: %s", arg);
这种方式将日志的三个关注点解耦:
- 日志级别(
atInfo()
) - 附加元数据(
withCause()
) - 消息模板与参数(
log()
)
通过组合少量固定参数的方法,避免了方法爆炸(combinatorial explosion),同时消除了装箱和数组创建的开销。
3. 依赖配置
Flogger 的接入非常简单,只需引入两个核心依赖:
<dependencies>
<dependency>
<groupId>com.google.flogger</groupId>
<artifactId>flogger</artifactId>
<version>0.4</version>
</dependency>
<dependency>
<groupId>com.google.flogger</groupId>
<artifactId>flogger-system-backend</artifactId>
<version>0.4</version>
<scope>runtime</scope>
</dependency>
</dependencies>
flogger
:核心 APIflogger-system-backend
:默认基于java.util.logging
的后端实现
引入后即可直接使用。
4. 流式 API 实战
首先定义一个静态 logger 实例:
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
4.1. 避免日志现场的无效计算 ✅
Flogger 强烈建议:不要在日志语句中执行耗时操作。
假设有一个耗时的方法:
public static String collectSummaries() {
longRunningProcess();
int items = 110;
int s = 30;
return String.format("%d seconds elapsed so far. %d items pending processing", s, items);
}
错误写法(每次都会执行):
logger.atFine().log("stats=%s", collectSummaries());
正确做法是使用 LazyArgs.lazy()
延迟计算:
logger.atFine().log("stats=%s", LazyArgs.lazy(() -> collectSummaries()));
✅ 优势:
- 日志未启用时,lambda 不会执行
- 仅创建一个轻量级 lambda 实例,几乎无开销
虽然也可以手动判断:
if (logger.atFine().isEnabled()) {
logger.atFine().log("summaries=%s", collectSummaries());
}
但这是多余的 ❌,Flogger 内部已做优化,且这种方式无法处理限流日志。
4.2. 异常处理 ✅
Flogger 提供了优雅的异常日志记录方式:
try {
int result = 45 / 0;
} catch (RuntimeException re) {
logger.atInfo().withStackTrace(StackSize.FULL).withCause(re).log("Message");
}
withStackTrace()
:控制堆栈深度,支持SMALL
,MEDIUM
,LARGE
,FULL
withCause()
:关联异常对象
在默认后端中,堆栈会以 LogSiteStackTrace
形式展示,其他后端可自定义处理方式。
4.3. 日志级别与配置 ✅
Flogger 支持多种日志级别:
logger.atInfo().log("Info Message");
logger.atWarning().log("Warning Message");
logger.atSevere().log("Severe Message");
logger.atFine().log("Fine Message");
logger.atFiner().log("Finer Message");
logger.atFinest().log("Finest Message");
logger.atConfig().log("Config Message");
可通过 LoggerConfig
动态调整级别:
LoggerConfig.of(logger).setLevel(Level.FINE);
4.4. 限流日志 ✅
高频日志容易刷屏,Flogger 提供了两种限流机制:
按调用次数限流
每 40 次执行记录一次:
IntStream.range(0, 100).forEach(value -> {
logger.atInfo().every(40).log("This log shows every 40 iterations => %d", value);
});
输出:
Sep 18, 2019 5:04:02 PM com.baeldung.flogger.FloggerUnitTest lambda$givenAnInterval_shouldLogAfterEveryTInterval$0
INFO: This log shows every 40 iterations => 0 [CONTEXT ratelimit_count=40 ]
Sep 18, 2019 5:04:02 PM com.baeldung.flogger.FloggerUnitTest lambda$givenAnInterval_shouldLogAfterEveryTInterval$0
INFO: This log shows every 40 iterations => 40 [CONTEXT ratelimit_count=40 ]
Sep 18, 2019 5:04:02 PM com.baeldung.flogger.FloggerUnitTest lambda$givenAnInterval_shouldLogAfterEveryTInterval$0
INFO: This log shows every 40 iterations => 80 [CONTEXT ratelimit_count=40 ]
按时间间隔限流
每 10 秒最多记录一次:
IntStream.range(0, 1_000_0000).forEach(value -> {
logger.atInfo().atMostEvery(10, TimeUnit.SECONDS).log("This log shows [every 10 seconds] => %d", value);
});
输出:
Sep 18, 2019 5:08:06 PM com.baeldung.flogger.FloggerUnitTest lambda$givenATimeInterval_shouldLogAfterEveryTimeInterval$1
INFO: This log shows [every 10 seconds] => 0 [CONTEXT ratelimit_period="10 SECONDS" ]
Sep 18, 2019 5:08:16 PM com.baeldung.flogger.FloggerUnitTest lambda$givenATimeInterval_shouldLogAfterEveryTimeInterval$1
INFO: This log shows [every 10 seconds] => 3545373 [CONTEXT ratelimit_period="10 SECONDS [skipped: 3545372]" ]
Sep 18, 2019 5:08:26 PM com.baeldung.flogger.FloggerUnitTest lambda$givenATimeInterval_shouldLogAfterEveryTimeInterval$1
INFO: This log shows [every 10 seconds] => 7236301 [CONTEXT ratelimit_period="10 SECONDS [skipped: 3690927]" ]
5. 集成现有日志后端
Flogger 最大的优势之一是兼容性好,可以无缝接入已有日志体系。
5.1. 集成 Slf4j ✅
- 添加依赖:
<dependency>
<groupId>com.google.flogger</groupId>
<artifactId>flogger-slf4j-backend</artifactId>
<version>0.4</version>
</dependency>
- 注册后端工厂:
System.setProperty(
"flogger.backend_factory", "com.google.common.flogger.backend.slf4j.Slf4jBackendFactory#getInstance");
之后所有 Flogger 日志都会通过 Slf4j 转发,沿用现有配置(如 Logback)。
5.2. 集成 Log4j ✅
- 添加依赖:
<dependency>
<groupId>com.google.flogger</groupId>
<artifactId>flogger-log4j-backend</artifactId>
<version>0.4</version>
<exclusions>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>apache-log4j-extras</artifactId>
<version>1.2.17</version>
</dependency>
- 注册后端:
System.setProperty(
"flogger.backend_factory", "com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance");
搞定!日志将由 Log4j 处理,配置完全复用。
6. 总结
Flogger 并非简单替代品,而是一个在性能、可读性、扩展性上都经过深思熟虑的日志框架。
它的流式 API 让日志语句更清晰,延迟计算和无 varargs 设计极大降低了性能损耗,同时支持 Slf4j、Log4j 等主流后端,便于在现有项目中平滑迁移。
如果你追求高性能日志,又希望代码简洁,Flogger 绝对值得在项目中尝试。
示例代码已托管至 GitHub:https://github.com/baeldung/tutorials(路径:logging-modules/flogger)