Welcome to the Apache Log4j2 Example Tutorial. If you ask an expert developer about the most annoying thing about an application, the answer might be related to logging. If there is no suitable logging in an application, maintenance will be a nightmare. Most of the application go through Development testing, unit testing, integration testing. But when it comes to production, you will always face unique scenarios and exception. So the only way to figure out what happened in a specific case is to debug through the logs. Many frameworks provide some way of default logging, but it’s always best to go with the industry standard logging mechanism. Apache Log4j is one of the most widely used logging frameworks. Apache Log4j 2 is the next version, that is far better than Log4j.
In this Log4j2 Example Tutorial, you will learn how to get started with Apache Log4j2. We will also explore Log4j2 architecture, log4j2 configuration, log4j2 logging levels, appenders, filters and much more.
Using Logging API in application isn’t a luxury, it’s a must have. Log4j is an open source library that’s published and licensed under Apache Software. You can debug an application using Eclipse Debugging or some other tools, but that is not sufficient and feasible in a production environment. Logging mechanism will provide you several benefits that you will not find in normal debugging.
Category / Operation (Debugging, Logging) | Debugging | Logging |
---|---|---|
Human Intervention | There’s a need for human intervention | No need for human intervention |
Persistent Medium | Can’t be integrated with persistent storage | Can be integrated with persistent storage (Files, Database, NoSQL database, etc.) |
May used for Auditing | Can’t be used for achieving auditing | Can be used for achieving auditing if it’s used efficiently |
Sufficient for complicated structure and flow | Not sufficient; you may get lost with flow. | Sufficient |
Productivity | Less productive | More productive |
As you can see above, using of logging mechanism will be more efficient with less maintenance cost. Apache Log4j is the front runner tool for logging in Java applications, so you should use it.
Before we proceed for Log4j Example tutorial, it’s good to look into Log4j2 architecture. Below image shows the important classes in Log4j2 API. Here’s the detailed explanation for the architecture shown above:
Applications will ask LogManager
for a Logger
with a specific name.
LogManager
will locate the appropriate LoggerContext
and then obtain Logger
from it.
If the Logger isn’t created yet, it will be created and associated with LoggerConfig according to three choices below:
getLogger(App.class)
will be evaluated to be a String com.journaldev.App
. LoggerConfig name is identical to fully qualified class name (Software component).com.journaldev
in getLogger("com.journaldev")
LoggerConfig
objects are created from Logger declaration in the configuration file. LoggerConfig is also used to handle LogEvents
and delegate them for their defined Log4j2 Appenders.
Root logger is an exceptional case, in terms of its existence. It always exists and at the top of any logger hierarchy.
You may obtain the root logger by using the below statements:
Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
Logger logger = LogManager.getRootLogger();
The name of log4j2 loggers are case sensitive.
Except root logger, all loggers can be obtained through passing their name into LogManager.getLogger()
.
LoggerContext is a vocal point for Logging system as you may have multiple LoggerContexts inside your application. Per each LoggerContext an active configuration should be set.
Log4j2 configuration contains all Logging system assets; LoggerConfig(s), Appender(s), Filter(s) and many others.
Calling of LogManager.getLogger() by passing the same name will always return the reference for the exact same logger instance.
Configuration of Logging system is typically done with the application initialization. This can take different forms; programmatically or by reading a log4j2 configuration file.
Every logger is associated with a LoggerConfig object, set of LoggerConfig objects made up a Hierarchy of loggers. This concept is known as Logger Hierarchy. Logger Hierarchy is made up of set of LoggerConfig objects with a parent-child relationship. The topmost element in every Logger Hierarchy is the Root Logger. If Log4j2 doesn’t find the configuration file, only Root Logger will be used for logging with logging level as ERROR. Below image shows the warning message you will get in this case. Error StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console. The table below shows the parent-child relationship in the Logger Hierarchy.
LoggerConfig (Is A) | Root | com | com.journaldev | com.journaldev.logging |
---|---|---|---|---|
Root | X | Child | descendant | descendant |
com | Parent | X | Child | descendant |
com.journaldev | Ancestor | Parent | X | Child |
com.journaldev.logging | Ancestor | Ancestor | Parent | X |
To clarify Parent-Child relationship, table above would be read as follows:
An instance of LoggerConfig is said to be an ancestor of another LoggerConfig; if its name followed by a dot is a prefix for the descendant name. An instance of LoggerConfig is said to be a parent for another LoggerConfig; if there are no interleaving names between both of them.
There are many ways to use Log4j2 configuration in you application.
We will focus mainly on the configuration file. However, it’s good to know the programming approach too, in case you want to configure a specific logging strategy for some specific Logger. First of all, let’s consider the case where you didn’t provide a configuration file. Log4j2 implementation assumes that there is a System variable called log4j.configurationFile to point the location of log4j2 configuration file.
package com.journaldev;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class App
{
public static void main( String[] args ) {
Logger logger = LogManager.getRootLogger();
logger.trace("Configuration File Defined To Be :: "+System.getProperty("log4j.configurationFile"));
}
}
A simple log4j2 configuration file will look like below. configuration.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
And here is the detailed explanation for the code listed above:
getRootLogger
method.System.setProperties("log4j.configurationFile","FILE_PATH")
or by passing it as a JVM parameter like you see in the figure below. Notice also File protocol prefix.log4j2-test.properties
in the classpath.log4j2.properties
on the classpath%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
Using log4j2 configuration file makes the log4j2 configuration so simple, but let’s see how we can configure it programmatically. This is all about using ConfigurationFactory.
package com.journaldev;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
public class App
{
public static void main( String[] args ) throws FileNotFoundException, IOException {
// Get instance of configuration factory; your options are default ConfigurationFactory, XMLConfigurationFactory,
// YamlConfigurationFactory & JsonConfigurationFactory
ConfigurationFactory factory = XmlConfigurationFactory.getInstance();
// Locate the source of this configuration, this located file is dummy file contains just an empty configuration Tag
ConfigurationSource configurationSource = new ConfigurationSource(new FileInputStream(new File("C:/dummyConfiguration.xml")));
// Get a reference from configuration
Configuration configuration = factory.getConfiguration(configurationSource);
// Create default console appender
ConsoleAppender appender = ConsoleAppender.createDefaultAppenderForLayout(PatternLayout.createDefaultLayout());
// Add console appender into configuration
configuration.addAppender(appender);
// Create loggerConfig
LoggerConfig loggerConfig = new LoggerConfig("com",Level.FATAL,false);
// Add appender
loggerConfig.addAppender(appender,null,null);
// Add logger and associate it with loggerConfig instance
configuration.addLogger("com", loggerConfig);
// Get context instance
LoggerContext context = new LoggerContext("JournalDevLoggerContext");
// Start logging system
context.start(configuration);
// Get a reference for logger
Logger logger = context.getLogger("com");
// LogEvent of DEBUG message
logger.log(Level.FATAL, "Logger Name :: "+logger.getName()+" :: Passed Message ::");
// LogEvent of Error message for Logger configured as FATAL
logger.log(Level.ERROR, "Logger Name :: "+logger.getName()+" :: Not Passed Message ::");
// LogEvent of ERROR message that would be handled by Root
logger.getParent().log(Level.ERROR, "Root Logger :: Passed Message As Root Is Configured For ERROR Level messages");
}
}
XMLConfigurationFactory
to get an instance of ConfigurationFactory.The same configuration can be done through using of YAML, JSON or properties file. However log4j2 property file configuration is different from the log4j property file, so make sure you are not trying to use the log4j property file configuration with log4j2. It will throw below error;
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.
While processing the above code would give you the below output:
Logger Name :: com :: Passed Message ::
00:01:27.705 [main] ERROR - Root Logger:: Passed Message As Root Is Configured For ERROR Level messages
The First line of logs is from com logger and the second is from the Root Logger. com logger error message is not printed because its level is Fatal.
You can see in above code examples that every time we define a LoggerConfig, we also provide logging level. By default log4j2 logging is additive. It means that all the parent loggers will also be used when a specific logger is used. Below image clarifies this situation. And herein points of clarification for it:
com.journaldev
logger and initiate a logEvent for logging, the loggerConfig (net.journaldev) will log the message and the message will be propagated as well up in the hierarchy without any respect for parents logging levels. So log event will be propagated to com and Root loggers and they will also log the message respectively according to the levels defined.Now, let’s see the example that’s associated with the concept of additivity explained above:
import net.NetApp;
import net.journaldev.NetJournalDevApp;
import com.ComApp;
import com.journaldev.ComJournalDevApp;
public class Main {
public static void main(String [] args){
new ComApp();
new ComJournalDevApp();
new NetApp();
new NetJournalDevApp();
}
}
package com.journaldev;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ComJournalDevApp {
public ComJournalDevApp(){
Logger logger = LogManager.getLogger(ComJournalDevApp.class);
logger.trace("COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::");
}
}
package net;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class NetApp {
public NetApp(){
Logger logger = LogManager.getLogger(NetApp.class);
logger.error("NET :: LEVEL :: NetApp ERROR Message ::");
}
}
package net.journaldev;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class NetJournalDevApp {
public NetJournalDevApp(){
Logger logger = LogManager.getLogger(NetJournalDevApp.class);
logger.error("NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::");
}
}
Whereas the log4j2 configuration file looks like below:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
<logger name="com" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="net" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
<logger name="net.journaldev" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
</Loggers>
</Configuration>
If you get Main class executed you would find the below results:
10:34:47.168 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
10:34:47.168 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
10:34:47.170 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
10:34:47.170 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
10:34:47.170 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
10:34:47.171 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
10:34:47.171 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
10:34:47.171 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
10:34:47.171 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
10:34:47.171 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
Here’s below detailed explanation for the code listed above:
Logging Space takes into consideration Levels of log events and the loggerConfig’s level in addition to Logger Hierarchy.
So what if we changed the LoggerConfig for com to be INFO and left the whole program as is:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
<logger name="com" level="INFO">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="net" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
<logger name="net.journaldev" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
</Loggers>
</Configuration>
Then the result it would be like below:
11:08:10.305 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
11:08:10.305 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
11:08:10.305 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
11:08:10.307 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
11:08:10.307 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
11:08:10.308 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
11:08:10.308 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
11:08:10.308 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
To make sure Log events have been displayed, the LoggerConfig’s Level should be greater than or equal to Log event’s level.
Table below shows you the log4j2 Levels and the weight for each of them:
LEVEL | Weight |
---|---|
OFF | 0 |
FATAL | 100 |
ERROR | 200 |
WARN | 300 |
INFO | 400 |
DEBUG | 500 |
TRACE | 600 |
ALL | Integer.MAX_VALUE |
For sure Table above clarifies much more than words and it gives you the main cause for being the Log event TRACE isn’t displayed while the LoggerConfig’s level is INFO.
Notice that the propagation of log events up in the logger hierarchy is beyond this computation and it ignores the levels.
But what happens if we remove LoggerConfig of com.journaldev from the configuration and added a new one for com.journaldev.logging to make the configuration file looks like below:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
<logger name="com" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev.logging" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="net" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
<logger name="net.journaldev" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
</Loggers>
</Configuration>
You may find the figure below more convenient for you to understand what’s happened in the above log4j2 configuration. Here’s some clarification for the figure shown above and how it may affect the behavior of logging events:
As a result for the points mentioned, you would see the following outputs:
14:08:37.634 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
14:08:37.634 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
14:08:37.636 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
14:08:37.636 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
14:08:37.637 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:08:37.637 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:08:37.637 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:08:37.638 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
14:08:37.638 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
14:08:37.640 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
14:08:37.640 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
14:08:37.640 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
And you may notice the following:
In case you’ve defined a com.journaldev LoggerConfig instance with no Level specified, it will inherit Level of its parent.
But what if you have defined com.journaldev LoggerConfig at your configuration file and missed out specifying the LoggerConfig’s level. Fortunately, the concept of Logger Hierarchy will save you here and com.journaldev would inherit its level value from its parent. Below is a sample configuration file followed by the table for logging level of each logger config.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
<logger name="com" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev.logging" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="net" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
<logger name="net.journaldev" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
</Loggers>
</Configuration>
Logger Name | Assigned LoggerConfig | LoggerConfig Level | Logger Level |
---|---|---|---|
Root | Root | ERROR | ERROR |
com | com | TRACE | TRACE |
com.journaldev | com | TRACE | TRACE |
com.journaldev.logging | com.journaldev.logging | TRACE | TRACE |
Below is the result of execution while com.journaldev inherits com log level:
14:41:37.419 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
14:41:37.419 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
14:41:37.421 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
14:41:37.421 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
14:41:37.421 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
14:41:37.422 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:41:37.422 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:41:37.422 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:41:37.422 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:41:37.423 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
14:41:37.423 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
14:41:37.423 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
14:41:37.423 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
14:41:37.423 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
And below result would be if you remove declaring LoggerConfig for com package:
14:43:28.809 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:43:28.809 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:43:28.809 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:43:28.811 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
14:43:28.811 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
14:43:28.812 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
14:43:28.812 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
14:43:28.812 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
You may notice that no messages have logged for com and com.journaldev, below are the reasons.
Last but not least, following below Table shows you all possible logging scenarios that you may face when using Logging system:
X (N/A) | LoggerConfig Level | OFF(0) | FATAL(100) | ERROR(200) | WARN(300) | INFO(400) | DEBUG(500) | TRACE(600) | ALL(MAX) |
---|---|---|---|---|---|---|---|---|---|
Event Level | X | X | X | X | X | X | X | X | X |
OFF(0) | X | YES | NO | NO | NO | NO | NO | NO | NO |
FATAL(100) | X | NO | YES | YES | YES | YES | YES | YES | YES |
ERROR(200) | X | NO | NO | YES | YES | YES | YES | YES | YES |
WARN(300) | X | NO | NO | NO | YES | YES | YES | YES | YES |
INFO(400) | X | NO | NO | NO | NO | YES | YES | YES | YES |
DEBUG(500) | X | NO | NO | NO | NO | NO | YES | YES | YES |
TRACE(600) | X | NO | NO | NO | NO | NO | NO | YES | YES |
ALL(MAX) | X | NO | NO | NO | NO | NO | NO | NO | YES |
Handling equation says: If the LoggerConfig Level’s is greater than or equal to Log event’s level the event would be accepted for further processing.
The log event would be accepted for further processing - this is so important because you have the ability to prevent some event from being handled even if it’s accepted by using Log4j2 Filters. You can set additive property to false to avoid log event propagation to parent loggers. Following below the same example that you did see before but this time with an additivity attribute, so you may notice the difference.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
<logger name="com" level="TRACE" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev.logging" level="TRACE" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<logger name="net" level="ERROR" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<logger name="net.journaldev" level="ERROR" additivity="false">
<AppenderRef ref="Console"/>
</logger>
</Loggers>
</Configuration>
And the result of execution would be like below:
17:55:30.558 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
17:55:30.560 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
17:55:30.561 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
17:55:30.561 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
17:55:30.562 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
And you may notice that there’s no propagation of log events to its parent loggers.
Ideally, you may define lookups as a way in which you can pass values for your Logging configuration file. Log4j2 provides you a different set of Lookups that can be used independently for setting values from different contexts:
You may refer for Log4j2 documentation for further details on every type of lookups, but let’s look at some example here to cover the basics of the Log4j2 Lookup. Environment Lookup represents the way in which you can pass an environment value (either by Linux etc/profile, windows system environment or Startup scripts for the application. As most of us know, we have the ability to define a set of environmental values for Application to use. Let’s see the most famous ways to define your environmental variables.
Look now at the modified log4j2.xml file and notice the use of environment variables.
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} $${env:JournalDevVar} $${env:JournalDevSecondVar} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
And the result of execution would look like below:
23:57:02.511 JournalDev www.journaldev.com [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
23:57:02.517 JournalDev www.journaldev.com [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
23:57:02.520 JournalDev www.journaldev.com [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
23:57:02.523 JournalDev www.journaldev.com [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
23:57:02.527 JournalDev www.journaldev.com [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
But you may face a little issue here and especially when you’re defining an OS’s environment variables and it’s the Eclipse Cache. Ideally, Eclipse has cached all system variables when it gets running and you may find all of them under Run - Run Configuration - Environment Tab - Click Select button. So, you may be confusing when you have defined it but the Application doesn’t recognize it. Even if you restart your Eclipse, you won’t get the solution and to solve it you must execute eclipse.exe -clean upon your Eclipse installation. To make sure your environment variables are defined properly and your system is going to find them willingly, you may use the correspondence plugin type that’s provided by Log4j2 API. Make an instance of EnvironmentLookup and ask it for looking up certain variable and if it’s defined, so you would find them easily.
EnvironmentLookup lookup = new EnvironmentLookup();
LogManager.getRootLogger().error(lookup.lookup("JournalDevSecondVar"));
You did see previously how can use Lookups for injecting variables into your configuration file. Though, you may want to modify the medium that your messages went through. Instead of using console directly, you may want such a file or database repository to make sure your messages are retained permanently. Log4j2 has provided a lot of Appenders, and you may refer for log4j2 documentation to get further details for Appender. In a brief manner, below is the list of all Log4j2 Appenders.
The most famous mediums used for logging events are console, file, and Database. Since the file would save your messages, the database might be used for auditing them. For this purpose, this section would focus on how JDBCAppender can be used efficiently.
The main goal of JDBCAppender is to write Log events into a relational table through JDBC connections. We don’t consume much time explaining how you can optimize your connection pools as this tutorial isn’t intended for this purpose. But for sure you will get a full functional example that helps writing your log events into database. Before we may proceed in, let’s see all needed parameters and a description for each to get JDBCAppender configured properly.
Parameter Name | Type | Description |
---|---|---|
Name | String | Required, The name of the Appender |
ignoreExceptions | boolean | Default value is set to true, making exceptions thrown to be logged also and then ignored. False value means the exception will be propagated for the caller. |
filter | Filter | The filter that should be used to make a decision whether the log events are going to be handled by this Appender or not. |
bufferSize | int | Default value is zero, indicating there’s no buffering have been done upon log events. Value greater than 0 would lead the Appender to buffer log events and then flush them once the buffer reaches the limit specified. |
connectionSource | ConnectionSource | Required, the connections source from which the database connections should be retrieved. |
tableName | String | Required, the name of the Table on which your log events should be persisted. |
columnConfigs | ColumnConfig[] | Required, additional information may be set upon those used columns and how the data should be persisted on each of them. This can be handled with multiple <Column> elements. |
Parameter Name | Type | Description |
---|---|---|
jndiName | String | Required, full prefixed JNDI name that the javax.sql.Datasource is bound to. |
Parameter Name | Type | Description |
---|---|---|
class | String | Requird, The fully qualified name for a class containg a static factory method for obtaining JDBC connections. |
method | boolean | Required, The name of the static factory method for obtaining JDBC connections. |
Parameter Name | Type | Description |
---|---|---|
name | String | Required, the name of the database column |
pattern | String | Ability to specify any legal pattern that Log event would be formatted with |
literal | String | Ability to specify literal value in this column (i.e. SEQ.NEXTVAL) |
isEventTimestamp | boolean | Indicating whether the event would consider Timestamp |
isUnicode | boolean | For unicode purpose as you may refer for Log4j2 documentation for further details |
isClob | boolean | For storing character large object, you may refer for Log4j2 documentation for further details. |
Since you’re enforced to use JNDI, our example would configure a connection data source for Oracle database and Apache Tomcat 7.
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.2</version>
</dependency>
<Context path="/JournalDevWebLogging"
privileged="true" antiResourceLocking="false" antiJARLocking="false">
<Resource name="jdbc/JournalDevDB" auth="Container"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000"
username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/journaldev" />
</Context>
CREATE TABLE `logging` (
`EVENT_ID` int(11) NOT NULL AUTO_INCREMENT,
`EVENT_DATE` datetime DEFAULT NULL,
`LEVEL` varchar(45) DEFAULT NULL,
`LOGGER` varchar(45) DEFAULT NULL,
`MSG` varchar(45) DEFAULT NULL,
`THROWABLE` varchar(45) DEFAULT NULL,
PRIMARY KEY (`EVENT_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout
pattern="%d{HH:mm:ss.SSS} $${env:JournalDevVar} $${env:JournalDevSecondVar} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<JDBC name="databaseAppender" tableName="journaldev.logging">
<DataSource jndiName="java:/comp/env/jdbc/JournalDevDB" />
<Column name="EVENT_DATE" isEventTimestamp="true" />
<Column name="LEVEL" pattern="%level" />
<Column name="LOGGER" pattern="%logger" />
<Column name="MSG" pattern="%message" />
<Column name="THROWABLE" pattern="%ex{full}" />
</JDBC>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console" />
</Root>
<logger name="com" level="TRACE" additivity="false">
<AppenderRef ref="databaseAppender" />
</logger>
<logger name="com.journaldev" additivity="false">
<AppenderRef ref="databaseAppender" />
</logger>
</Loggers>
</Configuration>
package com.journaldev;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class JournalDevServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
Logger logger = LogManager.getLogger(JournalDevServlet.class);
logger.trace("JournalDev Database Logging Message !");
}
}
package com.journaldev;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.logging.log4j.LogManager;
public class JournalDevServletContextListener implements ServletContextListener{
private InitialContext context = null;
public void contextDestroyed(ServletContextEvent event) {
}
public void contextInitialized(ServletContextEvent event) {
try {
// Get initial context
context = new InitialContext();
// Get a reference for sub context env
Context envContext = (Context)context.lookup("java:comp/env");
// Get a reference for sub context jdbc and then locating the data source defined
LogManager.getRootLogger().error(((Context)envContext.lookup("jdbc")).lookup("JournalDevDB"));
} catch (NamingException e) {
LogManager.getRootLogger().error(e);
}
}
}
Mar 15, 2015 2:31:41 PM org.apache.catalina.core.AprLifecycleListener init
INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: C:\Program Files\Java\jdk1.6.0_26\bin;C:\Windows\Sun\Java\bin;C:\Windows\system32;C:\Windows;C:\Program Files\Java\jdk1.6.0_26\jre\bin;C:/Program Files/Java/jdk1.6.0_26/bin/../jre/bin/server;C:/Program Files/Java/jdk1.6.0_26/bin/../jre/bin;C:/Program Files/Java/jdk1.6.0_26/bin/../jre/lib/amd64;D:\OracleWebCenter\OracleWC\Oracle11g\app\oracle\product\11.2.0\server\bin;;C:\Program Files\Common Files\Microsoft Shared\Windows Live;C:\Program Files (x86)\Common Files\Microsoft Shared\Windows Live;D:\OracleDB\app\product\11.2.0\dbhome_1\bin;org.C:\Program Files (x86)\Common Files\NetSarang;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x86;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x64;D:\SpringRoo\spring-roo-1.2.5.RELEASE\bin;D:\Ant\apache-ant-1.9.2\bin;C:\Python27;C:\Program Files\Java\jdk1.6.0_26\bin;D:\Maven\apache-maven-3.2.1/bin;D:\bower-master\bin;C:\Program Files (x86)\Git\cmd;C:\Program Files\nodejs\;C:\Program Files\Microsoft Windows Performance Toolkit\;D:\Grails\grails-2.4.0\bin;D:\Gradle\gradle-2.0\bin;C:\Program Files (x86)\Windows Live\Shared;C:\Program Files\TortoiseSVN\bin;D:\Strawberry\perl\bin;D:\Strawberry\perl\site\bin;D:\Strawberry\c\bin;C:\Users\mohammad.amr\AppData\Roaming\npm;D:\JournalDev\eclipse;;.
Mar 15, 2015 2:31:41 PM org.apache.tomcat.util.digester.SetPropertiesRule begin
WARNING: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclipse.jst.j2ee.server:JournalDevWebLogging' did not find a matching property.
Mar 15, 2015 2:31:41 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-bio-8080"]
Mar 15, 2015 2:31:41 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["ajp-bio-8009"]
Mar 15, 2015 2:31:41 PM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 1020 ms
Mar 15, 2015 2:31:41 PM org.apache.catalina.core.StandardService startInternal
INFO: Starting service Catalina
Mar 15, 2015 2:31:41 PM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet Engine: Apache Tomcat/7.0.35
14:31:43.847 [localhost-startStop-1] ERROR - org.apache.tomcat.jdbc.pool.DataSource@10fd0a62{ConnectionPool[defaultAutoCommit=null; defaultReadOnly=null; defaultTransactionIsolation=-1; defaultCatalog=null; driverClassName=com.mysql.jdbc.Driver; maxActive=100; maxIdle=30; minIdle=10; initialSize=10; maxWait=10000; testOnBorrow=false; testOnReturn=false; timeBetweenEvictionRunsMillis=5000; numTestsPerEvictionRun=0; minEvictableIdleTimeMillis=60000; testWhileIdle=false; testOnConnect=false; password=root; url=jdbc:mysql://localhost:3306/journaldev; username=root; validationQuery=null; validatorClassName=null; validationInterval=30000; accessToUnderlyingConnectionAllowed=true; removeAbandoned=false; removeAbandonedTimeout=60; logAbandoned=false; connectionProperties=null; initSQL=null; jdbcInterceptors=null; jmxEnabled=true; fairQueue=true; useEquals=true; abandonWhenPercentageFull=0; maxAge=0; useLock=false; dataSource=null; dataSourceJNDI=null; suspectTimeout=0; alternateUsernameAllowed=false; commitOnReturn=false; rollbackOnReturn=false; useDisposableConnectionFacade=true; logValidationErrors=false; propagateInterruptState=false; }
Mar 15, 2015 2:31:43 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-8080"]
Mar 15, 2015 2:31:43 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["ajp-bio-8009"]
Mar 15, 2015 2:31:43 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 1909 ms
Even if there’s a LoggerConfig candidate for handling Log event, you may configure it to deny passing the Log events into back end Appenders. This can be done by log4j2 Filter. This section isn’t intended for providing you an invasive, massive and huge amount of tutorial about using filters in Log4j2, as they need a lot of articles covering every one of them. But here, you would see how to use the most simple filter to learn the concept. One of the most simple filters that you may use is BurstFilter that provides you with a mechanism to control the rate at which LogEvents are processed by silently discarding events after the maximum limit has been reached. For now, you may see below all details needed to use BurstFilter.
Parameter Name | Type | Description |
---|---|---|
level | String | Level of messages to be filtered |
rate | float | The average number of events per second to allow |
maxBurst | integer | The maximum number of events that can occur before events are filtered for exceeding the average rate. The default is 10 times the rate. |
onMatch | String | Action to take when filter matches. May be Accept, DENY or NEUTRAL. The default is NEUTRAL |
onMismatch | String | Action to tale when filter doesn’t match. May be Accept, DENY or NEUTRAL. The default is NEUTRAL |
Now, look at the location of BurstFilter inside your database Appender.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout
pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<JDBC name="databaseAppender" tableName="journaldev.logging">
<DataSource jndiName="java:/comp/env/jdbc/JournalDevDB" />
<BurstFilter level="TRACE" rate="20" maxBurst="2"/>
<Column name="EVENT_DATE" isEventTimestamp="true" />
<Column name="LEVEL" pattern="%level" />
<Column name="LOGGER" pattern="%logger" />
<Column name="MSG" pattern="%message" />
<Column name="THROWABLE" pattern="%ex{full}" />
</JDBC>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console" />
</Root>
<logger name="com" level="TRACE" additivity="false">
<AppenderRef ref="databaseAppender" />
</logger>
<logger name="com.journaldev" additivity="false">
<AppenderRef ref="databaseAppender" />
</logger>
</Loggers>
</Configuration>
package com.journaldev;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class JournalDevServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
Logger logger = LogManager.getLogger(JournalDevServlet.class);
for(int i = 0 ; i < 1000 ; i++){
logger.trace("Index :: "+i+" :: JournalDev Database Logging Message !");
LogManager.getRootLogger().error("Index :: "+i+" :: JournalDev Database Logging Message !");
}
}
}
Even that those LoggerConfigs are the candidate for handling Log Events thrown there, but the Filter has prevented some of them from being handled and then logged. You may add this for Logging Space concept to get the whole concept of logging.
Due to different Appenders that consume Log Events and nature of each appender, the layouts are made to form the LogEvent in the format that meets the needs of whoever will be consuming the log event. In Log4j 1.x and Logback APIs, the layout transformation of Log Events was into a String, while Log4j2 layouts have considered a different way of transformation; and that’s by transforming the LogEvent into the array of bytes. This new type of transformation would enforce you configuring the Charset to ensure that byte array contains the correct values. It’s highly recommended to return back into Apache Log4j2 official site and see more about Layout and different types that Log4j2 provides. In this section, we will consider the most famous Layout that is always used by most of our developers and for sure you may hear about it; it’s PatternLayout.
Pattern layout is a configurable, flexible String pattern aimed to format the LogEvent. This kind of formatting is dependent on the conversion pattern concept. This section will depict you the most important features pattern layout provides. Conversion pattern is related to the conversion pattern that printf in language C provides. Generally, the conversion pattern is composed of literal text and format control Expressions called conversion specifiers. Figure below depicts you what parts the conversion pattern composed from: This figure above is a trial to simplify the Conversion Pattern, but for sure it’s better for you to refer Apache Log4j2 documentation for further details about Layouts and Pattern Layout specifically. Also, you may refer above for log events and see at every time what’s Conversion Pattern it is used for format the messages.
The biggest question that you may ask yourself is when specific Log Event level should be used. In the development field, it’s normal to use DEBUG log event whereas in production we should INFO or WARN level. This table below should guide you on which log4j2 level should be used in which case.
Log Event Level | When It Should Be Used |
---|---|
OFF | When no events will be logged |
FATAL | When a severe error will prevent the application from continuing |
ERROR | When an error in the application, possibly recoverable |
WARN | When an event that might possible lead to an error |
INFO | When an event for informational purposes |
DEBUG | When a general debugging event required |
TRACE | When a fine grained debug message, typically capturing the flow through the application |
ALL | When all events should be logged |
Log4j2 is revamped version of Apache Logging framework. Log4j2 has provided a set of new features and performance enhancements from Log4j1.x. This log4j2 tutorial is aimed to help you get it all in one location. Since some of these concepts aren’t so easy to cover them all at once, we’re decided to enclose our efforts in explaining the concept and using some samples for more clarification. Appenders, Filters, layouts, and lookups are subject to this rule. Some important points To make sure you’re able to get the below application running and to avoid any obstacles, verify the below:
Download Apache Log4j 2 Example Project
That’s all for the log4j2 tutorial, I hope most of the important points are covered to get you started using Log4j2 in your application.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Hi, I am programmatically loading log4j config file using logger context and creating logger instance from logger context. I am doing this my main class. How can i create and use logger instance in subsequent classes? . for example if i call a method in another class how should i create a logger instance in that class using the same logger context. Please advice
- Vignesh
How to change the name and location of log4j2.xml file? where we need to configure for the changed xml/property name and different location of the file configuration?
- Nageswarareddy Chaduvula
Nice article on log4j2 for beginners. I think the Logger Config section is bit lengthy and can be refactored.
- Rupesh
You’re missing the Http appender
- Kevin
Declarative configuration of logging is only “so simple” when it works. When it doesn’t, good luck trying to debug it.
- Sridhar Sarnobat
As of current version factory.getConfiguration(configurationSource) is undefined, an additional LoggerContext parameter is required.
- Luke
Very good over view of log4j2 logging framework. This was very helpful. Thanks for your effort.
- Ravi
very good and detailed explanation of log4j2 concepts and demo examples. Was really helpful in understanding them log4j2 version. The pattern layout could have been in more details like how the pattern works along with the diagram
- pkkumar
System.setProperties(“log4j.configurationFile”,“FILE_PATH”) should be System.setProperty(“log4j.configurationFile”,“FILE_PATH”)
- Himadri
Great ideas , Incidentally , people are looking for a CA BOE-58-AH , my colleagues filled out a fillable version here
https://goo.gl/dhsh23
- tillie arend