Java Fluent Logging with Flogger

Flogger, developed, used and recommended by Google, is a fluent style logging API for Java. Apart from fluency, flogger offers many more other features than existing logging frameworks that we will learn in this tutorial.

1. Flogger Dependencies

Flogger, similar to SLF4J, acts as an abstraction and uses the underlying logging framework as implementation. We can use Flogger with Java Logging API, Log4j2 and even SLF4J. By default, flogger uses Java Util Logging API (JUL).

1.1. Flogger Core

To add flogger into an application, we need to add com.google.flogger:flogger and com.google.flogger:flogger-system-backend dependencies that provide the core classes and interfaces for writing the log messages.

<dependency>
    <groupId>com.google.flogger</groupId>
    <artifactId>flogger</artifactId>
    <version>0.7.4</version>
</dependency>

<!-- The Java Util Logging backend -->

<dependency>
    <groupId>com.google.flogger</groupId>
    <artifactId>flogger-system-backend</artifactId>
    <version>0.7.4</version>
</dependency>

Flogger APIs internally depends on flogger-system-backend that is pulled in transitively for any logging platform we add.

1.2. Underlying Logging Backend

Next we need to add a runtime dependency from given logging platforms. We will use its configuration files to customize log levels, appenders etc.

For example, if we are using Flogger with Log4j2 then we need to add flogger-log4j2-backend dependency. After importing this dependency, we can control the logging levels, appenders, layouts etc from log4j2.xml file.

<!-- The SLF4J backend -->

<dependency>
    <groupId>com.google.flogger</groupId>
    <artifactId>flogger-slf4j-backend</artifactId>
    <version>0.7.4</version>
</dependency>

<!-- The Log4j backend -->

<dependency>
    <groupId>com.google.flogger</groupId>
    <artifactId>flogger-log4j-backend</artifactId>
    <version>0.7.4</version>
</dependency>

<!-- The Log4j2 backend -->

<dependency>
    <groupId>com.google.flogger</groupId>
    <artifactId>flogger-log4j2-backend</artifactId>
    <version>0.7.4</version>
</dependency>

2. Flogger API

2.1. FluentLogger

Using the fluent API provided by Flogger in application classes starts with creating an instance of FluentLogger.

import com.google.common.flogger.FluentLogger;

public class Main {

	private static final FluentLogger logger = FluentLogger.forEnclosingClass();
}

Next, we can start using the FluentLogger instance for writing the log messages.

Note that we do not use curly brackets {} for data placeholders, we can use any of Java’s printf() format specifiers, such as %s, %d etc.

logger.atWarning().log("Warning message");

Article a = Article.builder(1L).title("Test Article").tag("Data").build();
logger.atInfo().log("Article found : %s", a);

This will print the log messages in configured appenders and layout.

2022-01-10 20:34:37.621 WARN [main] [com.howtodoinjava.demo.flogger.Main.main(Main.java:17)] - Warning message
2022-01-10 20:34:37.632 INFO [main] [com.howtodoinjava.demo.flogger.Main.main(Main.java:20)] - Article found : Article(id=1, title=Test Article, tags=[Data])

2.2. Log Levels

At the API level, flogger supports log levels provided by JDK Logging.

  • OFF: turn off logging.
  • SEVERE: (highest value): a serious failure.
  • WARNING: a potential problem.
  • INFO: informational messages.
  • CONFIG: log static configuration messages.
  • FINE: log tracing information.
  • FINER: log a fairly detailed tracing message.
  • FINEST: (lowest value): log a highly detailed tracing message.
  • ALL: enable logging of all messages.

We can use the following method calls to log messages at a specific level.

logger.atInfo().log("...");
logger.atWarning().log("...");
logger.atSevere().log("...");
logger.atFine().log("...");
logger.atFiner().log("...");
logger.atFinest().log("...");
logger.atConfig().log("...");

//Can be used for any log level
logger.at(Level.SEVERE).log("...");

The Log level can be set using the configuration files of the underlying logging platform (logging.properties, log4j.properties etc).

2.3. Logging Exceptions

Use the withStackTrace() method to log a Throwable instance.

logger.atInfo()
	.withStackTrace(StackSize.SMALL)
	.withCause(new NullPointerException())
	.log("NullPointerException Received");
2022-01-10 21:14:49 INFO  Main:26 - NullPointerException Received
java.lang.NullPointerException: null
at com.howtodoinjava.demo.flogger.Main.main(Main.java:26) [classes/:?]

StackSize enum has 3 constants:

  • StackSize.SMALL: Produces a small stack suitable for more fine grained debugging.
  • StackSize.MEDIUM: Produces a medium sized stack suitable for providing contextual information for most log statements.
  • StackSize.LARGE: Produces a large stack suitable for providing highly detailed contextual information.
  • StackSize.FULL: Provides the complete stack trace.
  • StackSize.NONE: Provides no stack trace. This is useful when the stack size is conditional.
logger.atWarning()
	.withStackTrace(showTrace ? StackSize.MEDIUM : StackSize.NONE)
	.log("message");

2.4. Rate Limiting

This is an interesting feature where we do not want to a log message for every occurrence of a statement. We want to log the message on every nth occurrence.

In the given example, we are logging the message at every 10th iteration.

IntStream.range(0, 50).forEach(value -> {
    logger.atInfo().every(10).log("The counter is => %d", value);
});
2022-01-10 21:13:23 INFO  Main:30 - The counter is => 0
2022-01-10 21:13:23 INFO  Main:30 - The counter is => 10
2022-01-10 21:13:23 INFO  Main:30 - The counter is => 20
2022-01-10 21:13:23 INFO  Main:30 - The counter is => 30
2022-01-10 21:13:23 INFO  Main:30 - The counter is => 40

3. Advantages of Flogger over Other Logging Frameworks

Now when we have a basic understanding of Flogger API, let us understand what makes it so useful that Google recommends it to use it internally in the organization.

3.1. Performance

According to Google, Flogger has been designed and implemented for high-performance logging by building a set of carefully constructed APIs, both frontend and backend.

Flogger APIs work on top of the logging platform to provide the best possible performance.

3.2. Cost of Disabled Log Messages

Most logging frameworks extensively use varargs in the methods like info(), debug() etc. These methods require a new Object[] to be allocated and filled before the called method can be invoked. Additionally, any fundamental types passed in must be auto-boxed.

For this reason, a simple log.info(String, Object...) approach to logging is concise at the source code level but can introduce surprising costs in bytecode. To make it even worse, this overhead bytecode will be executed even if the log statement is disabled.

From the analysis of logging behavior in large applications in Google, it seems that disabled log statements are hit many orders of magnitude more than enabled ones. This is something that should be avoided as a priority.

When using Flogger’s fluent API logger.atInfo().log("My message: %s", arg);, we can know whether or not logging is disabled at the point that the level selector was called with atInfo() method. So if logging is disabled we can choose to return a different implementation of the logging context which simply discards all its arguments for every subsequent method call (a “No-Op” instance).

Conveniently this instance can be naturally immutable and thread-safe, so we can return the same singleton instance every time, which avoids an allocation of any kind of memory when logging is disabled thus improving the performance.

4. Conclusion

Flogger seems very promising API with some great advantages discussed above. To make it even more familiar, we can use the existing Log4j2 or SLF4J configurations for easier migrations.

In this tutorial, we learned to import Flogger dependencies and use Flogger API to log various messages. You can read more about Flogger in its official Github page.

Happy Learning !!

Source Code Download

Comments

Subscribe
Notify of
guest
2 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode