In: Computer Science
Suppose, we need to debug somebody else’s program. We suspect that there is a problem with the method wizbang() in class Widget or with how that method is called. We cannot modify class Widget, nor can we modify the client code that contains the calls to Widget.wizbang(), since we don’t have those sources. However, we can modify the code where Widget objects are created and we can create new classes.
In order to better understand what this method does, we would like to print the values of the parameters and the return value into a log file every time Widget.wizbang() is called. Explain, how you would produce this log file given the constraints that neither class Widget nor the client can be modified.
We can use JAVA framework for this.
If you ever had to analyze an issue in production, I’m sure you know how important it is to have good logging. Good logging requires three things:
While you still need to decide yourself which log messages you should write for each use case, you don’t need to worry about requirement 2 and 3. Various logging frameworks already solved these technical requirements. You only need to choose one of them and use it to write your log messages.
To make it even better, SLF4J provides a standardized API that in one way or the other is implemented by most of these frameworks. That enables you to change your logging framework without changing your code. You only need to change the dependency to a different framework that implements the SLF4J interfaces.
Writing Log Messages with SLF4J
Writing log messages with SLF4J is very easy. You first need to call the getLogger method on the LoggerFactory to instantiate a new Logger object. You can then call one of the debug, info, warning, error or fatal methods on the Logger to write a log message with the corresponding log level. Here you can see a typical example:
public class MyClass { Logger log = LoggerFactory.getLogger(this.getClass().getName()); public void myMethod() { log.info("This is an info message"); // ... } }
So if these frameworks are easily interchangeable, which one should you choose?
The answer to this question is not as easy as you might expect. There are several frameworks available that are broadly used in the Java world. I want to introduce you to Log4j and its two successors Logback and Log4j2.
Apache Log4j
Apache Log4j is a very old logging framework and was the most popular one for several years. It introduced basic concepts, like hierarchical log levels and loggers, that are still used by modern logging frameworks.
Required dependencies
If you want to use Log4j in your application, you need to add the log4j.jar file to your classpath. You can see the required Maven dependency in the following code snippet.
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
Log4j doesn’t support SLF4J natively. You also need to add the following dependency to be able to use Log4j via the standardized interfaces.
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <scope>test</scope> </dependency>
Configuring Log4j
In addition to the log4j.jar file, you need to define your appender and logger with their log levels in the log4j.properties file. The appender writes the log messages to a destination such as a file or database. The logger and level define the granularity of log messages that are written to the log file.
The following code snippet shows a typical Log4j configuration for a development system of an application that uses Hibernate as an object-relational mapper. It writes all log message to the file app.log and sets the general log level to INFO. These are 2 of Hibernate’s loggers that write the executed SQL statements and their bind parameter values to the configured file appender.
log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.File=app.log log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n log4j.rootLogger=info, file # basic log level for all messages log4j.logger.org.hibernate=info # SQL statements and parameters log4j.logger.org.hibernate.SQL=debug log4j.logger.org.hibernate.type.descriptor.sql=trace
Based on this configuration, you can write your log messages using the SLF4J API. That’s all about Log4j for now. If you want to learn more about it, please take a look at Matt Watson’s Ultimate Log4j Tutorial.
Required dependencies
You only need to define a dependency on logback-classic. It transitively includes the dependencies to logback-core and the SLF4J API.
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
Configuring Logback
Logback doesn’t require any configuration. By default, it writes all log messages in DEBUG level or higher to standard out. You can change that with a custom configuration file in XML or Groovy format.
Logback uses the same concepts as Log4j. So it’s no surprise that even if they are using different file formats, their configurations are very similar. The following code snippet shows the same configuration as I used with Log4j.
<configuration> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>app.log</file> <encoder> <pattern>%d{HH:mm:ss,SSS} %-5p [%c] - %m%n</pattern> </encoder> </appender> <logger name="org.hibernate.SQL" level="DEBUG" /> <logger name="org.hibernate.type.descriptor.sql" level="TRACE" /> <root level="info"> <appender-ref ref="FILE" /> </root> </configuration>
After you’ve added the required dependency and configured Logback, you can use it to write log messages via the SLF4J API. So if you want to benefit from the improvements provided by Logback, you don’t need to change any code to replace Log4j with Logback.
Apache Log4j2
Apache Log4j2 is the youngest of these three frameworks, and its goal is to improve on both of them by providing its own improvements on Log4j, including some of the improvements included in Logback and avoiding problems of Log4j and Logback.
So like Logback, Log4j2 provides support for SLF4J, automatically reloads your logging configuration, and supports advanced filtering options. In addition to these features, it also allows lazy evaluation of log statements based on lambda expressions, offers asynchronous loggers for low-latency systems, and provides a garbage-free mode to avoid any latency caused by garbage collector operations.
All these features make Log4j2 the most advanced and the fastest of these three logging frameworks.
Required Dependencies
Log4j2 packages its API and implementation in two separate jar files. You can implement and build your application using the log4j-api.jar, and you need to provide the additional log4j-core.jar at runtime. If you want to use the SLF4JAPI, you also need the log4j-slf4j-impl.jar file, which contains a bridge between the two APIs.
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.11.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.11.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.11.1</version> </dependency>
Configuring Log4j2
The configuration of Log4j2 follows the same principles as the configuration of the two previous logging frameworks and, therefore, looks pretty similar.
<Configuration status="info"> <Appenders> <File name="FILE" fileName="app.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </File> </Appenders> <Loggers> <Logger name="org.hibernate.SQL" level="DEBUG"> <AppenderRef ref="FILE"/> </Logger> <Logger name="org.hibernate.type.descriptor.sql" level="TRACE"> <AppenderRef ref="FILE"/> </Logger> <Root level="info"> <AppenderRef ref="FILE"/> </Root> </Loggers> </Configuration>