1. Overview
1.概述
In this tutorial, we’re going to talk about the Flogger framework, a fluent logging API for Java designed by Google.
在本教程中,我们将讨论Flogger框架,这是一个由Google设计的Java流畅日志API。
2. Why Use Flogger?
2.为什么使用Flogger?
With all the logging frameworks that are currently in the market, like Log4j and Logback, why do we need yet another logging framework?
目前市场上有那么多的日志框架,如Log4j和Logback,为什么我们还需要另一个日志框架?
It turns out Flogger has several advantages over other frameworks – let’s take a look.
事实证明,与其他框架相比,Flogger有几个优势–让我们来看看。
2.1. Readability
2.1.可读性
The fluent nature of Flogger’s API goes a long way to making it more readable.
Flogger的API的流畅性在很大程度上使其更具可读性。
Let’s look at an example where we want to log a message every ten iterations.
让我们看一个例子,我们想每10次迭代记录一条信息。
With a traditional logging framework, we’d see something like:
在传统的日志框架下,我们会看到类似的情况。
int i = 0;
// ...
if (i % 10 == 0) {
logger.info("This log shows every 10 iterations");
i++;
}
But now, with Flogger, the above can be simplified to:
但现在,有了Flogger,上述内容可以简化为。
logger.atInfo().every(10).log("This log shows every 10 iterations");
While one would argue that the Flogger version of the logger statement looks a bit more verbose than the traditional versions, it does permit greater functionality and ultimately leads to more readable and expressive log statements.
虽然人们会认为Flogger版本的日志语句看起来比传统的版本要啰嗦一些,但它确实允许更多的功能,并最终导致更可读和更有表达力的日志语句。
2.2. Performance
2.2.业绩
Logging objects are optimized as long we avoid calling toString on the logged objects:
只要我们避免在日志对象上调用toString,日志对象就会被优化。
User user = new User();
logger.atInfo().log("The user is: %s", user);
If we log, as shown above, the backend has the opportunity to optimize the logging. On the other hand, if we call toString directly, or concatenate the strings then this opportunity is lost:
如果我们记录,如上所示,后端有机会优化记录。另一方面,如果我们直接调用toString,或者串联字符串,那么这个机会就会丧失。
logger.atInfo().log("Ths user is: %s", user.toString());
logger.atInfo().log("Ths user is: %s" + user);
2.3. Extensibility
2.3.可扩展性
The Flogger framework already covers most of the basic functionality that we’d expect from a logging framework.
Flogger框架已经涵盖了我们期望从一个日志框架获得的大部分基本功能。
However, there are cases where we would need to add to the functionality. In these cases, it’s possible to extend the API.
然而,在有些情况下,我们会需要增加功能。在这些情况下,可以对API进行扩展。
Currently, this requires a separate supporting class. We could, for example, extend the Flogger API by writing a UserLogger class:
目前,这需要一个单独的支持类。例如,我们可以通过编写一个UserLogger 类来扩展Flogger API。
logger.at(INFO).forUserId(id).withUsername(username).log("Message: %s", param);
This could be useful in cases where we want to format the message consistently. The UserLogger would then provide the implementation for the custom methods forUserId(String id) and withUsername(String username).
这在我们想要统一消息格式的情况下可能很有用。然后,UserLogger将为自定义方法forUserId(String id)和withUsername(String username)提供实现。
To do this, the UserLogger class will have to extend the AbstractLogger class and provide an implementation for the API. If we look at FluentLogger, it’s just a logger with no additional methods, we can, therefore, start by copying this class as-is, and then build up from this foundation by adding methods to it.
要做到这一点,UserLogger类将必须扩展AbstractLogger类,并为API提供一个实现。如果我们看一下FluentLogger,它只是一个没有额外方法的记录器,因此,我们可以从原样复制这个类开始,然后在这个基础上通过添加方法来建立起来。
2.4. Efficiency
2.4.效率
Traditional frameworks extensively use varargs. These methods require a new Object[] to be allocated and filled before the method can be invoked. Additionally, any fundamental types passed in must be auto-boxed.
传统框架广泛地使用varargs。这些方法需要在方法被调用之前分配和填充一个新的Object[]。此外,传入的任何基本类型必须是自动装箱的。
This all costs additional bytecode and latency at the call site. It’s particularly unfortunate if the log statement isn’t actually enabled. The cost becomes more apparent in debug level logs that appear often in loops. Flogger ditches these costs by avoiding varargs totally.
这一切都要花费额外的字节码和调用站点的延迟。如果日志语句没有实际启用,那就特别不幸了。在经常出现在循环中的调试级日志中,成本变得更加明显。Flogger通过完全避免varargs来抛弃这些成本。
Flogger works around this problem by using a fluent call chain from which logging statements can be built. This allows the framework to only have a small number of overrides to the log method, and thus be able to avoid things like varargs and auto-boxing. This means that the API can accommodate a variety of new features without a combinatorial explosion.
Flogger通过使用流畅的调用链来解决这个问题,可以从中构建日志语句。这允许框架只对log方法进行少量重写,从而能够避免varargs和自动装箱等情况。这意味着API可以容纳各种新功能,而不会出现组合爆炸。
A typical logging framework would have these methods:
一个典型的日志框架会有这些方法。
level(String, Object)
level(String, Object...)
where level can be one of about seven log level names (severe for example), as well as having a canonical log method which accepts an additional log level:
其中level可以是大约七个日志级别名称之一(例如severe),以及有一个接受额外日志级别的规范日志方法。
log(Level, Object...)
In addition to this, there are usually variants of the methods that take a cause (a Throwable instance) that is associated with the log statement:
除此以外,通常还有一些方法的变体,它们采取一个与日志语句相关的原因(一个Throwable实例)。
level(Throwable, String, Object)
level(Throwable, String, Object...)
It’s clear that the API is coupling three concerns into one method call:
很明显,该API将三个关注点耦合到一个方法调用中。
- It’s trying to specify the log level (method choice)
- Trying to attach metadata to the log statement (Throwable cause)
- And also, specifying the log message and arguments.
This approach quickly multiplies the number of different logging methods needed to satisfy these independent concerns.
这种方法使满足这些独立关注点所需的不同记录方法的数量迅速倍增。
We can now see why it’s important to have two methods in the chain:
我们现在可以看到,为什么在这个链条上有两个方法是很重要的。
logger.atInfo().withCause(e).log("Message: %s", arg);
Let’s now take a look at how we can use it in our codebase.
现在让我们来看看我们如何在我们的代码库中使用它。
3. Dependencies
3.依赖性
It’s pretty simple to set up Flogger. We just need to add flogger and flogger-system-backend to our pom:
设置Flogger是非常简单的。我们只需要将flogger和flogger-system-backend加入我们的pom:
<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>
With these dependencies set up, we can now go on to explore the API that is at our disposal.
有了这些依赖关系,我们现在可以继续探索我们所掌握的API了。
4. Exploring the Fluent API
4.探索Fluent API
First off, let’s declare a static instance for our logger:
首先,让我们为我们的记录器声明一个静态实例。
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
And now we can start logging. We’ll start with something simple:
而现在我们可以开始记录了。我们将从简单的东西开始。
int result = 45 / 3;
logger.atInfo().log("The result is %d", result);
The log messages can use any of Java’s printf format specifiers, such as %s, %d or %016x.
日志信息可以使用Java的任何printf格式指定器,如 %s, %d或 %016x。
4.1. Avoiding Work at Log Sites
4.1.避免在伐木场工作
Flogger creators recommend that we avoid doing work at the log site.
Flogger的创造者建议我们避免在日志现场做工作。
Let’s say we have the following long-running method for summarising the current state of a component:
比方说,我们有以下长期运行的方法来总结一个组件的当前状态。
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);
}
It’s tempting to call collectSummaries directly in our log statement:
在我们的日志语句中直接调用collectSummaries是很诱人的。
logger.atFine().log("stats=%s", collectSummaries());
Regardless of the configured log levels or rate-limiting, though, the collectSummaries method will now be called every time.
无论配置的日志级别或速率限制如何,collectSummaries方法现在每次都会被调用。
Making the cost of disabled logging statements virtually free is at the core of the logging framework. This, in turn, means that more of them can be left intact in the code without harm. Writing the log statement like we just did takes away this advantage.
让禁用的日志语句的成本几乎为零,是日志框架的核心所在。这反过来又意味着更多的人可以在代码中不受伤害地保留。像我们刚才那样写日志语句就会失去这种优势。
Instead, we should do use the LazyArgs.lazy method:
相反,我们应该使用LazyArgs.lazymethod。
logger.atFine().log("stats=%s", LazyArgs.lazy(() -> collectSummaries()));
Now, almost no work is done at the log site — just instance creation for the lambda expression. Flogger will only evaluate this lambda if it intends to actually log the message.
现在,在日志站点几乎没有做任何工作–只是为lambda表达式创建实例。Flogger只有在打算实际记录消息时才会评估这个lambda。
Although it’s allowed to guard log statements using isEnabled:
虽然允许使用isEnabled来保护日志语句。
if (logger.atFine().isEnabled()) {
logger.atFine().log("summaries=%s", collectSummaries());
}
This is not necessary and we should avoid it because Flogger does these checks for us. This approach also only guards log statements by level and does not help with rate-limited log statements.
这是没有必要的,我们应该避免这样做,因为Flogger为我们做了这些检查。这种方法也只是按级别来守护日志语句,对限速的日志语句没有帮助。
4.2. Dealing With Exceptions
4.2.处理异常情况
How about exceptions, how do we handle them?
异常情况如何,我们如何处理它们?
Well, Flogger comes with a withStackTrace method that we can use to log a Throwable instance:
好吧,Flogger带有一个withStackTrace方法,我们可以用它来记录一个Throwable实例。
try {
int result = 45 / 0;
} catch (RuntimeException re) {
logger.atInfo().withStackTrace(StackSize.FULL).withCause(re).log("Message");
}
Where withStackTrace takes as an argument the StackSize enum with constant values SMALL, MEDIUM, LARGE or FULL. A stack trace generated by withStackTrace() will show up as a LogSiteStackTrace exception in the default java.util.logging backend. Other backends may choose to handle this differently though.
其中withStackTrace将StackSize枚举作为参数,常量值为SMALL、MEDIUM、LARGE或FULL。由withStackTrace()产生的堆栈跟踪将在默认的java.util.logging后端显示为LogSiteStackTrace异常。但其他后端可能会选择不同的方式来处理这个问题。
4.3. Logging Configuration and Levels
4.3.日志配置和级别
So far we’ve been using logger.atInfo in most of our examples, but Flogger does support many other levels. We’ll look at these, but first, let’s introduce how to configure the logging options.
到目前为止,我们在大多数的例子中都使用了logger.atInfo,但Flogger确实支持许多其他的级别。我们将看看这些,但首先,让我们介绍一下如何配置日志选项。
To configure logging, we use the LoggerConfig class.
为了配置日志,我们使用LoggerConfig类。
For example, when we want to set the logging level to FINE:
例如,当我们想将日志级别设置为FINE。
LoggerConfig.of(logger).setLevel(Level.FINE);
And Flogger supports various logging levels:
而且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");
4.4. Rate Limiting
4.4.速率限制
How about the issue of rate-limiting? How do we handle the case where we don’t want to log every iteration?
速率限制的问题如何处理?我们如何处理我们不想记录每个迭代的情况?
Flogger comes to our rescue with the every(int n) method:
Flogger通过every(int n)method来拯救我们。
IntStream.range(0, 100).forEach(value -> {
logger.atInfo().every(40).log("This log shows every 40 iterations => %d", value);
});
We get the following output when we run the code above:
当我们运行上面的代码时,我们得到以下输出。
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 ]
What if we want to log say every 10 seconds? Then, we can use atMostEvery(int n, TimeUnit unit):
如果我们想每10秒记录一次呢?那么,我们可以使用atMostEvery(int n, TimeUnit unit)。
IntStream.range(0, 1_000_0000).forEach(value -> {
logger.atInfo().atMostEvery(10, TimeUnit.SECONDS).log("This log shows [every 10 seconds] => %d", value);
});
With this, the outcome now becomes:
有了这个,现在的结果就变成了。
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. Using Flogger With Other Backends
5.将Flogger与其他后端一起使用
So, what if we would like to add Flogger to our existing application that is already using say Slf4j or Log4j for example? This could be useful in cases where we would want to take advantage of our existing configurations. Flogger supports multiple backends as we’ll see.
那么,如果我们想将Flogger添加到已经使用Slf4j或Log4j的现有应用程序,该怎么办?在我们想利用现有配置的情况下,这可能是有用的。Flogger支持多个后端,我们会看到。
5.1. Flogger With Slf4j
5.1.使用Slf4j的Flogger</span
It’s simple to configure an Slf4j back-end. First, we need to add the flogger-slf4j-backend dependency to our pom:
配置一个Slf4j后端很简单。首先,我们需要将flogger-slf4j-backend依赖性添加到我们的pom。
<dependency>
<groupId>com.google.flogger</groupId>
<artifactId>flogger-slf4j-backend</artifactId>
<version>0.4</version>
</dependency>
Next, we need to tell Flogger that we would like to use a different back-end from the default one. We do this by registering a Flogger factory through system properties:
接下来,我们需要告诉Flogger,我们想使用一个不同于默认的后端。我们通过系统属性注册一个Flogger工厂来做到这一点。
System.setProperty(
"flogger.backend_factory", "com.google.common.flogger.backend.slf4j.Slf4jBackendFactory#getInstance");
And now our application will use the existing configuration.
而现在我们的应用程序将使用现有的配置。
5.2. Flogger With Log4j
5.2.使用Log4j的Flogger
We follow similar steps for configuring Log4j back-end. Let’s add the flogger-log4j-backend dependency to our pom:
我们按照类似的步骤来配置Log4j后端。让我们把flogger-log4j-backend依赖性添加到我们的pom。
<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>
We also need to register a Flogger back-end factory for Log4j:
我们还需要为Log4j注册一个Flogger后端工厂。
System.setProperty(
"flogger.backend_factory", "com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance");
And that’s it, our application is now set up to use existing Log4j configurations!
就这样,我们的应用程序现在已经被设置为使用现有的Log4j配置了
6. Conclusion
6.结语
In this tutorial, we’ve seen how to use the Flogger framework as an alternative for the traditional logging frameworks. We’ve seen some powerful features that we can benefit from when using the framework.
在本教程中,我们已经看到了如何使用Flogger框架作为传统日志框架的替代。我们已经看到了一些强大的功能,在使用该框架时我们可以从中受益。
We’ve also seen how we can leverage our existing configurations by registering different back-ends like Slf4j and Log4j.
我们也看到了我们如何通过注册不同的后端如Slf4j和Log4j来利用我们现有的配置。
As usual, the source code for this tutorial is available over on GitHub.
像往常一样,本教程的源代码可以在GitHub上获得。