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 类来封装业务相关的日志行为。

例如,我们想为用户操作日志统一添加 userIdusername

logger.at(INFO).forUserId(id).withUsername(username).log("Message: %s", param);

要实现这种 DSL 风格,你需要:

  1. 创建一个 UserLogger 类继承 AbstractLogger
  2. 在类中实现 forUserId()withUsername() 等自定义方法
  3. 基于 FluentLogger 的实现进行扩展(可直接复制其结构作为起点)

⚠️ 注意:扩展需要编写支持类,不是简单的配置就能完成。

2.4. 调用效率 ✅

这是 Flogger 的一大亮点。传统日志框架广泛使用 varargs(可变参数),这会带来两个问题:

  • 每次调用都要创建 Object[] 数组
  • 基本类型需要自动装箱(如 int → Integer)

即使日志被禁用,这些开销依然存在,尤其在 debug 日志密集的循环中,性能损耗明显。

Flogger 的解决方案非常简单粗暴:彻底抛弃 varargs,改用流式链式调用。

比如:

logger.atInfo().withCause(e).log("Message: %s", arg);

这种方式将日志的三个关注点解耦:

  1. 日志级别(atInfo()
  2. 附加元数据(withCause()
  3. 消息模板与参数(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:核心 API
  • flogger-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 ✅

  1. 添加依赖:
<dependency>
    <groupId>com.google.flogger</groupId>
    <artifactId>flogger-slf4j-backend</artifactId>
    <version>0.4</version>
</dependency>
  1. 注册后端工厂:
System.setProperty(
  "flogger.backend_factory", "com.google.common.flogger.backend.slf4j.Slf4jBackendFactory#getInstance");

之后所有 Flogger 日志都会通过 Slf4j 转发,沿用现有配置(如 Logback)。

5.2. 集成 Log4j ✅

  1. 添加依赖:
<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>
  1. 注册后端:
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)


原始标题:Flogger Fluent Logging