Java University – Logging in Java (2/2)

This article is the follow up of Logging in Java (1/2). The first part explained what loggers are and attempt to compare some of the major loggers in Java. This second part will show you how to use them. Firstly with easy configuration, then with some more advanced one.

If you are interesting in a specific logger, you can use this quick access menu:

  1. API’s :
    1. Apache Commons Logging (JCL)
    2. Simple Logging Facade For Java (SLF4J)
    3. Log4j2
  2. Implementations :
    1. Log4j 1.2
    2. Logback
    3. Log4j2

Using loggers API’s

Apache Commons Logging (JCL)

As for part 1, we will begin with JCL. In order to use it, the following maven dependency needs to be added :

<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

And now let us see how we can use it:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

// Declaration 
private static final Log LOG = LogFactory.getLog(TestCommonsLogging.class);

// Use
LOG.info("A simple info message");
LOG.debug("A finer message");
if (LOG.isInfoEnabled()) {
    LOG.info("Total price is: " + totalPrice + " " + currency);
}

try {
    Integer myNumber = Integer.parseInt("NaN");
} catch (NumberFormatException e) {
    LOG.warn("Cannot parseInt!", e);
}

Commons Logging has the following log level: TRACE, DEBUG, INFO, WARN, ERROR, FATAL. There is a method for each level. The method is overloaded to take whether a message alone or a message and a Throwable. Notice that the concatenation is not part of the API. If we want to avoid concatenation overhead, we are forced to check the log level with LOG.isInfoEnabled() for instance.
If I run the example, the output is:

mars 17, 2016 11:37:56 AM io.resourcepool.jcl.TestCommonsLogging main
Infos: A simple info message
mars 17, 2016 11:37:56 AM io.resourcepool.jcl.TestCommonsLogging main
Infos: Total price is: 25 €
mars 17, 2016 11:43:46 AM io.resourcepool.jcl.TestCommonsLogging main
Warning: Cannot parseInt!
java.lang.NumberFormatException: For input string: "NaN"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at io.resourcepool.jcl.TestCommonsLogging.main(TestCommonsLogging.java:22)

By default, messages, from INFO level, are logged to the system error console. There is no way to change this without including a proper implementation.

That was quick, but that is all there is to know about the Commons Logging API. We will see later how to add an implementation and properly configure our logs.

Simple Logging Facade for Java (SLF4J)

Let us continue with a second logging API, the follower of JCL is SLF4J. I am not going to compare them, and explain why you should be using SLF4J rather than JCL, because I have already covered this in part 1. Let us rush to how to use it. Maven side:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.19</version>
</dependency>

Java side:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// Declaration
private static final Logger LOGGER = LoggerFactory.getLogger(TestSlf4j.class);

// Use
LOGGER.info("A simple info message");
LOGGER.debug("A debug message");
LOGGER.info("Total price is: {} {}", price, currency);

try {
    Integer myNumber = Integer.parseInt("NaN");
} catch (NumberFormatException e) {
    LOGGER.warn("Cannot parseInt!", e);
}

We find again the same log level as Commons Logging, except for Fatal which was dropped. Besides the method used to log a message or a message and a Throwable, there are methods which allow to avoid concatenation. There is no use to call every time isXXXEnabled() method. Even through these methods still exist. Now let us see the execution output:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

That seems weird, this is not what we expected. Actually, SLF4J will only work as an API. If it is not able to find an implementation at runtime, it will fail with this error and no log will be outputted. So, if you see this message when starting your application, you just need to add implementation. Now we will add logback for instance:

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.6</version>
    <scope>runtime</scope>
</dependency>

Using this new dependency and without adding any configuration file, this is the new output:

12:59:49.380 [main] INFO io.resourcepool.slf4j.TestSlf4j - A simple info message
12:59:49.382 [main] DEBUG io.resourcepool.slf4j.TestSlf4j - A debug message
12:59:49.383 [main] INFO io.resourcepool.slf4j.TestSlf4j - Total price is: 20 € 
12:59:49.386 [main] WARN io.resourcepool.slf4j.TestSlf4j - Cannot parseInt!
java.lang.NumberFormatException: For input string: "NaN"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at io.resourcepool.slf4j.TestSlf4j.main(TestSlf4j.java:18)

Ah, this is better, everything is displayed from DEBUG level (TRACE is omitted) and this is outputted on system out, and not system error.

Now we can have the opposite problem, this is, having more than one implementation available in the classpath. Let us add this:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.19</version>
    <scope>runtime</scope>
</dependency>

I start the application:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/home/ydemartino/.m2/repository/ch/qos/logback/logback-classic/1.1.6/logback-classic-1.1.6.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/ydemartino/.m2/repository/org/slf4j/slf4j-simple/1.7.19/slf4j-simple-1.7.19.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
13:03:04.836 [main] INFO io.resourcepool.slf4j.TestSlf4j - A simple info message
13:03:04.839 [main] DEBUG io.resourcepool.slf4j.TestSlf4j - A debug message
13:03:04.839 [main] INFO io.resourcepool.slf4j.TestSlf4j - Total price is: 20 € 
13:03:04.842 [main] WARN io.resourcepool.slf4j.TestSlf4j - Cannot parseInt!
java.lang.NumberFormatException: For input string: "NaN"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at io.resourcepool.slf4j.TestSlf4j.main(TestSlf4j.java:19)

As you can see, this still works, but this is something that needs to be fixed. SLF4J is nice and tells us from which jars the bindings come from (lines 2 and 3). In a real project, when this happens, you need to check the dependency hierarchy in eclipse or use the following maven command to detect the dependency that brought the unnecessary binding, then exclude it.

mvn dependency:tree -Dverbose -Dincludes=slf4j-simple

Another interesting point about SLF4J is that it provides bridges to and from other logging librairies. Imagine you were using JCL and you want to move to SLF4J without rewriting your whole application. No problem at all: you just need to add this dependency:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.19</version>
</dependency>

This jar contains class from Commons Logging that redirects logs to SLF4J. Thus, it is absolutely needed to exclude commons-logging from your dependency. This warning applies for all bridges.
Another bridge available is to redirect log4j 1.x to SLF4J:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.19</version>
</dependency>

Then again, it is mandatory that you exclude the log4j dependency from your build as bridges come as a replacement.

This is not the full list. There also is a bridge from JUL to SLF4J. I am not going to discuss it here: JUL being part of the JDK, you cannot exclude the jar. Using this bridge will add a significant overhead.
Additionally, there are bridges to go the other way. This means, redirecting SLF4J to log4j or JCL. Then again, I think the cases are unlikely and will not discuss these.

Log4j2 API

Since July 2014, log4j2 has been added to the logging API list. Maven side:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.5</version>
</dependency>

Java side:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

// Declaration
private static final Logger LOGGER = LogManager.getLogger(TestLog4j2.class);

// Use
LOGGER.info("A simple info message");
LOGGER.debug("A debug message");
LOGGER.info("Total price is: {} {}", price, currency);

try {
    Integer myNumber = Integer.parseInt("NaN");
} catch (NumberFormatException e) {
    LOGGER.warn("Cannot parseInt!", e);
}

The output:

ERROR StatusLogger Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console...

You know the drill, only the API will not work, you also need an implementation:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.5</version>
    <scope>runtime</scope>
</dependency>
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.

Well, log4j2 is not as permissive as SLF4J, without configuration, it will not log anything! We will see later how to configure Log4j2.

16:08:01.100 [main] INFO io.resourcepool.log4j2.TestLog4j2 - A simple info message
16:08:01.101 [main] DEBUG io.resourcepool.log4j2.TestLog4j2 - A debug message
16:08:01.102 [main] INFO io.resourcepool.log4j2.TestLog4j2 - Total price is: 200 €
16:08:01.102 [main] WARN io.resourcepool.log4j2.TestLog4j2 - Cannot parseInt!
java.lang.NumberFormatException: For input string: "NaN"
	at java.lang.NumberFormatException.forInputString(Unknown Source) ~[?:1.8.0_73]
	at java.lang.Integer.parseInt(Unknown Source) ~[?:1.8.0_73]
	at java.lang.Integer.parseInt(Unknown Source) ~[?:1.8.0_73]
	at io.resourcepool.log4j2.TestLog4j2.main(TestLog4j2.java:22) [classes/:?]

As you can see, it really looks like SLF4J. I could see two differences. The first one, it is possible to instantiate a different logger to format parameters:

private static final Logger FORMATTER_LOGGER = LogManager.getFormatterLogger(TestLog4j2.class);

LOGGER.debug("Starting at %1$tm %1$te,%1$tY", new Date());
15:58:55.952 [main] DEBUG io.resourcepool.log4j2.TestLog4j2 - Starting at 03 17,2016

Or the same thing with the initial logger with a syntax not that sexy, that reminds us of JUL

private static final Logger LOGGER = LogManager.getLogger(TestLog4j2.class);
LOGGER.printf(Level.DEBUG, "Starting at %1$tm %1$te,%1$tY", new Date());

Second difference: the use of lambda. Let us say you want to log a debug message.

LOGGER.debug("Message: {}", expensiveOperation());

Using the braces syntax will prevent the concatenation from happening if you are not in debug, but the expensiveOperation method will be called regardless of the log level.

private static String expensiveOperation() {
    try {
        long timeSlept = 5_000L;
        Thread.sleep(timeSlept);
        return "Slept " + timeSlept + "ms";
    } catch (InterruptedException e) {
        return e.getMessage();
    }
}

Moving the level to INFO, we will still waste 5 seconds and in the end the message will not even be logged! Log4j2 brings a solution for this with the use of lambda:

LOGGER.debug("Message: {}", () -> expensiveOperation());

We are passing a function as parameter and it will only be executed if the log level is DEBUG. Note that lambda expression are only available in java 8.

Using loggers implementations

Now for each implementation, we are going to see how to log to the console. Then we will see how to log to the console and to a file. And finally how to modify the log level of a specific package.

java.util.logging JUL

Let us start with JUL, the logger shipped with the JDK. As a reminder, the use of this logger is discouraged. Obviously, there is no maven import to do.

import java.util.logging.Level;
import java.util.logging.Logger;

// LOGGER declaration
private static final Logger LOGGER = Logger.getLogger(TestLogger.class.getName());

// LOGGER use
LOGGER.info("A simple info message");
LOGGER.finer("A finer message");
LOGGER.log(Level.INFO, "Total price is: {0} {1}", new Object[]{totalPrice, currency});

As you can see, we need to pass parameters to the logger. The syntax is a little strange. By default, JUL will only display messages from INFO level using the following template:

févr. 06, 2016 2:47:16 PM io.resourcepool.TestLogger main
INFOS: Total price is: 200 €

In order to log to a different level or to log to a file instead of the console we can use Handlers. Each logger has a list of Handlers. We can add a Handler this way:

import java.util.logging.*;

Handler consoleHandler = new ConsoleHandler();
LOGGER.addHandler(consoleHandler);
Handler fileErrorHandler = new FileHandler("./err.log");
fileErrorHandler.setLevel(Level.WARNING);
LOGGER.addHandler(fileErrorHandler);

By default, a logger has the ConsoleHandler added. The ConsoleHandler writes logs to System.err. In this example, I added another Handler on the same logger. This FileHandler will write logs to the “err.log” file. I also set that I only want to log messages from WARNING level.
Here is the content of err.log after execution:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2016-02-06T15:03:03</date>
  <millis>1454767383925</millis>
  <sequence>3</sequence>
  <logger>io.resourcepool.TestLogger</logger>
  <level>WARNING</level>
  <class>io.resourcepool.TestLogger</class>
  <method>main</method>
  <thread>1</thread>
  <message>Warning unable to find username</message>
</record>
</log>

I only logged one message and it is not really clear what it means! Fortunately, there are Formatters which help to format the logs the way we want.

import java.util.logging.*;
Handler fileErrorHandler = new FileHandler("./err.log");
fileErrorHandler.setLevel(Level.WARNING);
Formatter simpleFormatter = new SimpleFormatter();
fileErrorHandler.setFormatter(simpleFormatter);
LOGGER.addHandler(fileErrorHandler);

SimpleFormatter is the default formatter for the ConsoleHandler. This means, now my file will have the same content as my console. You also can create custom formatters by extending the java.util.logging.Formatter class. This class manipulate LogRecord containing all the informations (log level, message, date, thread, source, etc.).
It is also possible to configure JUL using a file:

handlers=java.util.logging.ConsoleHandler
.level=INFO
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.append=true
myLogger.level=ALL
  • handlers: defines default handlers
  • .level: defines default log level
  • java.util.logging.ConsoleHandler.level: defines default log level for all ConsoleHandlers
  • java.util.logging.FileHandler.formatter: defines default formatter for all FileHandlers
  • java.util.logging.FileHandler.append: defines the default behavior for append. By default FileHandler has the property set to false. Thus, the log file is recreated after each execution
  • myLogger.level: if there is a logger named “myLogger”, its log level will be set with this value

There is no default file name for the logger to discover it. You need to programmaticaly load the file:

import java.util.logging.*;
LogManager.getLogManager().readConfiguration(new FileInputStream("logger.properties"));

That is all for java.util.logging. This is not the complete guide to it, but I do not find we need to know more about this logger.

Log4j version 1.2

In order to use log4j, you need to add the following maven dependency, but you should know that the project is no longer supported:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

Let us look at how to use it:

import org.apache.log4j.Logger;

// LOGGER declaration
private static final Logger LOGGER = Logger.getLogger(TestLog4j.class);

// LOGGER use
LOGGER.info("A simple info message");
LOGGER.debug("A debug message");
if (LOGGER.isInfoEnabled()) {
    LOGGER.info("Total price is: " + price + " " + currency);
}

Log4j is an implementation. In this example, I show you have to use it without an API. This is not recommended. You should use it through an API like Commons Logging, SLF4J or Log4j2 (even if this does not make sense for the latter). You should add log4j with the runtime scope, so you cannot use directly the log4j classes.

Let us see the output:

log4j:WARN No appenders could be found for logger (io.resourcepool.log4j.TestLog4j).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

It looks like something we saw earlier! We had the same kind of message with SLF4J.
This message tells us that log4j has been detected but could not find any configuration file. To make it work, we need to add into the classpath a file named log4j.properties or log4j.xml. log4j.properties example:

log4j.rootLogger=INFO, Console

log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d{dd/MM/yyyy HH:mm:ss,SSS} [%p] %c{1} - %m%n

This configuration tells log4j to log to the console with the following format for every messages from level INFO:

17/03/2016 14:36:18,140 [INFO] TestLog4j - A simple info message
17/03/2016 14:36:18,141 [INFO] TestLog4j - Total price is: 200 €
17/03/2016 14:36:18,141 [WARN] TestLog4j - Cannot parseInt!
java.lang.NumberFormatException: For input string: "NaN"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at io.resourcepool.log4j.TestLog4j.main(TestLog4j.java:19)

Console is actually the name of my logger, I could have chosen anything. For the complete list of patterns, I redirect you to the log4j official website instead of copy/pasting everything.
As a first example I chose to use properties over XML as this is less verbose. XML allows to use more complex configuration and properties act as legacy configuration. This also means that if both files are present in the classpath, log4j.xml will be chosen over log4j.properties.

Now for a more complete example, I added the possibility to log to the console and to a file, and to set the log level of the io.resourcepool package to DEBUG:

log4j.rootLogger=INFO, Console, File

log4j.appender.File=org.apache.log4j.FileAppender
log4j.appender.File.File=C:\logs\logs.log
log4j.appender.File.append=false
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.conversionPattern=%d{dd/MMM/yyyy HH:mm:ss,SSS} [%p] %c{1} - %m%n

log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d{dd/MM/yyyy HH:mm:ss,SSS} [%p] %c{1} - %m%n

log4j.logger.io.resourcepool=DEBUG

The same thing with log4j.xml would look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<appender name="Console" class="org.apache.log4j.ConsoleAppender">
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%d{dd/MM/yyyy HH:mm:ss,SSS} [%p] %c{1} - %m%n" />
		</layout>
	</appender>

	<appender name="File" class="org.apache.log4j.FileAppender">
		<param name="file" value="C:\logs\logs.log" />
		<param name="append" value="false" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%d{dd/MM/yyyy HH:mm:ss,SSS} [%p] %c{1} - %m%n" />
		</layout>
	</appender>

	<logger name="io.resourcepool">
		<level value="DEBUG" />
	</logger>

	<root>
		<level value="INFO" />
		<appender-ref ref="Console" />
		<appender-ref ref="File" />
	</root>

</log4j:configuration>

If you need to use a different file name, you can add the following JVM argument: -Dlog4j.configuration=my_custom_file.xml

Logback

As we saw earlier with SLF4J, adding the logback dependency without configuration will allow us to log message to the console from DEBUG level. To add logback, you need the following maven dependency:

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.6</version>
    <scope>runtime</scope>
</dependency>

To have the same output with a configuration file, you would need to add a logback.xml file to the classpath:

<configuration scan="true" scanPeriod="30 seconds" > 
  <appender name="Console" class="ch.qos.logback.core.ConsoleAppender"> 
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="Console" />
  </root>
</configuration>

This is almost the same thing. I aligned level name on 5 characters. Moreover take a look at the XML options from first line: scan and scanPeriod. These options are optional. Adding the scan option will make logback read the logback.xml file every minute. Here I set the scanPeriod to 30 secondes. This is really a big improvement compared to log4j. We do not have to restart our application just to change the log level! The overhead is pretty small as logback will not check the file for every logging operation but only from time to time. Indeed, logback will not start a dedicated thread to monitor the file for changes.

<configuration scan="true"> 
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> 
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>
  
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>resourcepool.log</file>
	<append>false</append>
    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
    </encoder>
  </appender>
  
  <logger name="io.resourcepool" level="DEBUG" />

  <root level="INFO">
    <appender-ref ref="CONSOLE" />
    <appender-ref ref="FILE" />
  </root>
</configuration>

This file configures logback so it can log to Console and to a resourcepool.log file. It also configures the package io.resourcepool to log in DEBUG. The FILE appender by default append logs to the file. It will not erase what already exists. With line 10, I showed how to overwrite it. If this is not what you need, you can remove the line or set it to true.

Just like log4j, I will not list the whole patterns, but I will redirect you to the logback official site.

In order to use a different file than logback.xml, you need to add the following JVM parameter: -Dlogback.configurationFile=my_custom_logback.xml

Log4j2

The last one we are going to see is log4j2. In order to use it, we need to add the following dependency:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.5</version>
    <scope>runtime</scope>
</dependency>

Just like log4j 1.2, Log4j2 needs a configuration file to work. If none is provided, it will log a message saying it was not configured and will only log errors to System.err. Here is a basic log4j2.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Appenders>
    <Console name="CONSOLE" target="SYSTEM_OUT">
      <PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="DEBUG">
      <AppenderRef ref="CONSOLE"/>
    </Root>
  </Loggers>
</Configuration>

It will output logs to console from DEBUG level. This is more clear than log4j 1.2! Now here is how you can log to a file:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorInterval="30">
  <Appenders>
    <Console name="CONSOLE" target="SYSTEM_OUT">
      <PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
    </Console>
    <File name="FILE" fileName="resourcepool.log" append="false">
      <PatternLayout pattern="%t %-5p %c{2} - %m%n"/>
    </File>
  </Appenders>
  <Loggers>
    <Logger name="io.resourcepool" level="DEBUG"/>
    <Root level="INFO">
      <AppenderRef ref="CONSOLE"/>
      <AppenderRef ref="FILE"/>
    </Root>
  </Loggers>
</Configuration>

Here is the complete example. We can see how to log to the console, how to log the a file and how to set the level of the io.resourcepool package to DEBUG. Just like logback the default FILE logger will append data. I show you how to change this behavior.
I also put the monitorInterval option which is equivalent to the scan option of logback. Here it will

As log4j2 async logger are one of the fastest logger currently, I will show you a configuration example. If you want to make every loggers async, you just need to add the following JVM parameter: -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector. It is also possible to change the log4j2.xml file to make them async but with a little loss in performance:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Appenders>
    <Console name="CONSOLE" target="SYSTEM_OUT">
      <PatternLayout pattern="%d %-5p [%t] %c{1.} - %m%n"/>
    </Console>
    <File name="FILE" fileName="resourcepool.log" append="false">
      <PatternLayout pattern="%t %-5p %c{2.} - %m%n"/>
    </File>
  </Appenders>
  <Loggers>
    <asyncLogger name="io.resourcepool" level="DEBUG"/>
    <asyncRoot level="INFO">
      <AppenderRef ref="CONSOLE"/>
      <AppenderRef ref="FILE"/>
    </asyncRoot>
  </Loggers>
</Configuration>

That is it, you just need to add “async” before Root and Logger. Note that I also changed the pattern. Indeed, async loggers do not know the location and they will output some “?” if you use such patterns. If you really need to have the location, you can tell the logger that it should be included:

<asyncRoot level="INFO" includeLocation="true">

This will make everything work as in a synchronous logger, but again we are losing some performances.

Whatever way you chose to enable async loggers, you will need to add the LMAX Disruptor library in the classpath:

<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.3.4</version>
    <scope>runtime</scope>
</dependency>

This lib can manage high performance non-blocking queue by using a little more memory.

Lastly, the link to the log4j2 patterns and if you want to use a file with a different name use: -Dlog4j.configurationFile=my_log4j2.xml

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.