Getting started
This document aims to guide you through the most important aspects of logging with Log4j. It is not a comprehensive guide, but it should give you a good starting point.
What is logging?
Logging is the act of publishing diagnostics information at certain points of a program execution. It means you can write messages to a log file or console to help you understand what your application is doing.
The simplest way to log in Java is to use System.out.println()
, like this:
private void truncateTable(String tableName) {
System.out.println("truncating table"); (1)
db.truncate(tableName);
}
1 | The information that a table is being truncated is written to the console. |
This is already useful, but the reader of this message does not know what table is being truncated.
Usually, we would like to include the table name in the message, which quickly leads developers to use System.out.format()
or similar methods.
Log4j helps with formatting strings as we will see later, but for now, let’s see how to work without it.
The following code shows how this method can be improved to provide more context about its action.
private void truncateTable(String tableName) {
System.out.format("[WARN] truncating table `%s`%n", tableName); (1)
db.truncate(tableName);
}
1 | format() writes the message to the console, replacing %s with the value of tableName , and %n with a new line. |
If the developer decides the truncate the table fruits
, the output of this code will look like this:
[WARN] Truncating table `fruits`
This provides observability into an application’s runtime, and we can follow the execution flow.
However, there are several drawbacks with the above approach and this is where Log4j comes in. Log4j will help you to write logs in a more structured way, with more information, and with more flexibility.
Why should I use Log4j?
Log4j is a versatile, industrial-grade Java logging framework, maintained by many contributors. It can help us with common logging tasks and lets us focus on the application logic.
It helps with:
-
Enhancing the message with additional information (timestamp, file, class, and method name, line number, host, severity, etc.)
-
Formatting the message according to a given layout (CSV, JSON, etc.)
-
Writing the message to various targets using an appender (console, file, socket, database, queue, etc.)
-
Filtering messages to be written (e.g. filter by severity, content, etc.)
What is Log4j composed of?
Log4j is essentially composed of a logging API called Log4j API, and its reference implementation called Log4j Core. Log4j also bundles several logging bridges to enable Log4j Core consume from foreign logging APIs. Let’s briefly explain these concepts:
- Logging API
-
A logging API is an interface your code or your dependencies directly logs against. It is required at compile-time. It is implementation agnostic to ensure that your application can write logs, but is not tied to a specific logging implementation. Log4j API, SLF4J, JUL (Java Logging), JCL (Apache Commons Logging), JPL (Java Platform Logging) and JBoss Logging are major logging APIs.
- Logging implementation
-
A logging implementation is only required at runtime and can be changed without the need to recompile your software. Log4j Core, JUL (Java Logging), Logback are the most well-known logging implementations.
- Logging bridge
-
Logging implementations accept input from a single logging API of their preference; Log4j Core from Log4j API, Logback from SLF4J, etc. A logging bridge is a simple logging implementation of a logging API that forwards all messages to a foreign logging API. Logging bridges allow a logging implementation to accept input from other logging APIs that are not their primary logging API. For instance,
log4j-slf4j2-impl
bridges SLF4J calls to Log4 API and effectively enables Log4j Core to accept input from SLF4J.
What are the installation prerequisites?
We will need a BOM (Bill of Materials) to manage the versions of the dependencies. This way we won’t need to provide the version for each Log4j module explicitly.
-
Maven
-
Gradle
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>2.24.3</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
dependencies {
implementation platform('org.apache.logging.log4j:log4j-bom:2.24.3')
}
How do I log using Log4j API?
To log, you need a Logger
instance which you will retrieve from the LogManager
.
These are all part of the log4j-api
module, which you can install as follows:
-
Maven
-
Gradle
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j-api.version}</version>
</dependency>
implementation 'org.apache.logging.log4j:log4j-api:${log4j-api.version}'
You can use the Logger
instance to log by using methods like info()
, warn()
, error()
, etc.
These methods are named after the log levels they represent, a way to categorize log events by severity.
The log message can also contain placeholders written as {}
that will be replaced by the arguments passed to the method.
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class DbTableService {
private static final Logger LOGGER = LogManager.getLogger(); (1)
public void truncateTable(String tableName) throws IOException {
LOGGER.warn("truncating table `{}`", tableName); (2)
db.truncate(tableName);
}
}
1 | The returned Logger instance is thread-safe and reusable.
Unless explicitly provided as an argument, getLogger() associates the returned Logger with the enclosing class, that is, DbTableService in this example. |
2 | The placeholder {} in the message will be replaced with the value of tableName |
The generated log event, which contain the user-provided log message and log level (i.e., WARN
), will be enriched with several other implicitly derived contextual information: timestamp, class & method name, line number, etc.
What happens to the generated log event will vary significantly depending on the configuration used. It can be pretty-printed to the console, written to a file, or get totally ignored due to insufficient severity or some other filtering.
Log levels are used to categorize log events by severity and control the verbosity of the logs.
Log4j contains various predefined levels, but the most common are DEBUG
, INFO
, WARN
, and ERROR
.
With them, you can filter out less important logs and focus on the most critical ones.
Previously we used Logger#warn()
to log a warning message, which could mean that something is not right, but the application can continue.
Log levels have a priority, and WARN
is less severe than ERROR
.
Exceptions are often also errors.
In this case, we might use the ERROR
log level.
Make sure to log exceptions that have diagnostics value.
This is simply done by passing the exception as the last argument to the log method:
LOGGER.warn("truncating table `{}`", tableName);
try {
db.truncate(tableName);
} catch (IOException exception) {
LOGGER.error("failed truncating table `{}`", tableName, exception); (1)
throw new IOException("failed truncating table: " + tableName, exception);
}
1 | By using error() instead of warn() , we signal that the operation failed. |
While there is only one placeholder in the message, we pass two arguments: tableName
and exception
.
Log4j will attach the last extra argument of type Throwable
in a separate field to the generated log event.
Best practices
There are several widespread bad practices while using Log4j API. Below we will walk through the most common ones and see how to fix them. For a complete list, refer to the Log4j API best practices page.
Don’t use toString()
-
Don’t use
Object#toString()
in arguments, it is redundant!/* BAD! */ LOGGER.info("userId: {}", userId.toString());
-
Underlying message type and layout will deal with arguments:
/* GOOD */ LOGGER.info("userId: {}", userId);
Pass exception as the last extra argument
-
Don’t call
Throwable#printStackTrace()
! This not only circumvents the logging but can also leak sensitive information!/* BAD! */ exception.printStackTrace();
-
Don’t use
Throwable#getMessage()
! This prevents the log event from getting enriched with the exception./* BAD! */ LOGGER.info("failed", exception.getMessage()); /* BAD! */ LOGGER.info("failed for user ID `{}`: {}", userId, exception.getMessage());
-
Don’t provide both
Throwable#getMessage()
andThrowable
itself! This bloats the log message with a duplicate exception message./* BAD! */ LOGGER.info("failed for user ID `{}`: {}", userId, exception.getMessage(), exception);
-
Pass exception as the last extra argument:
/* GOOD */ LOGGER.error("failed", exception); /* GOOD */ LOGGER.error("failed for user ID `{}`", userId, exception);
Don’t use string concatenation
If you are using String
concatenation while logging, you are doing something very wrong and dangerous!
-
Don’t use
String
concatenation to format arguments! This circumvents the handling of arguments by message type and layout. More importantly, this approach is prone to attacks! ImagineuserId
being provided by the user with the following content:placeholders for non-existing args to trigger failure: {} {} {dangerousLookup}
/* BAD! */ LOGGER.info("failed for user ID: " + userId);
-
Use message parameters
/* GOOD */ LOGGER.info("failed for user ID `{}`", userId);
How do I install Log4j Core to run my application?
This section explains how to install Log4j Core to run an application.
Are you implementing not an application, but a library? Please refer to the How do I install Log4j Core for my library? instead. |
First, add the log4j-core
runtime dependency to your application.
Second, it is highly recommended to add the log4j-layout-template-json
runtime dependency to encode log events in JSON.
This is the most secure way to format log events and should be preferred over the default PatternLayout
, at least for production deployments.
-
Maven
-
Gradle
<project>
<!-- Assuming `log4j-bom` is added -->
<dependency>
<!-- Logging implementation (Log4j Core) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>runtime</scope>(1)
</dependency>
<!-- Log4j JSON-encoding support -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-layout-template-json</artifactId>
<scope>runtime</scope>(1)
</dependency>
</dependency>
</project>
dependencies {
// Assuming `log4j-bom` is added
// The logging implementation (i.e., Log4j Core)
runtimeOnly 'org.apache.logging.log4j:log4j-core' (1)
// Log4j JSON-encoding support
runtimeOnly 'org.apache.logging.log4j:log4j-layout-template-json' (1)
}
1 | For applications, the logging implementation need to be runtime dependencies. |
If your application has (direct or transitive!) dependencies that use another logging API, you need to bridge that to Log4j.
This way the foreign logging API calls will effectively be consumed by Log4j too.
SLF4J is another logging API used pretty common in the wild.
(Installation covers all supported foreign APIs.)
Let’s see how you can use the log4j-slf4j2-impl
bridge to support SLF4J:
-
Maven
-
Gradle
<project>
<!-- Assuming `log4j-bom` is added -->
<dependency>
<!-- Assuming `log4j-core` and `log4j-layout-template-json` is added -->
<!-- SLF4J-to-Log4j bridge -->(2)
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<scope>runtime</scope>(1)
</dependency>
</dependency>
</project>
dependencies {
// Assuming `log4j-bom`, `log4j-core`, and `log4j-layout-template-json` is added
// SLF4J-to-Log4j bridge (2)
runtimeOnly 'org.apache.logging.log4j:log4j-slf4j2-impl' (1)
}
1 | For applications, bridges need to be runtime dependencies. |
2 | Log4j module bridging SLF4J to Log4j |
To complete the installation, Log4j needs to be configured. Please continue with How do I configure Log4j Core to run my application?
How do I configure Log4j Core to run my application?
This section explains configuring Log4j on how log events should be processed.
Log4j supports several configuration inputs and file formats.
Let’s start with a basic and robust configuration where the logs are encoded in JSON and written to the console.
Save the following XML-formatted Log4j configuration file to src/main/resources/log4j2.xml
in your application.
src/main/resources/log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://logging.apache.org/xml/ns"
xsi:schemaLocation="
https://logging.apache.org/xml/ns
https://logging.apache.org/xml/ns/log4j-config-2.xsd">
<Appenders>(1)
<Console name="CONSOLE">(2)
<JsonTemplateLayout/>(3)
</Console>
</Appenders>
<Loggers>
<Logger name="com.mycompany" level="INFO"/>(4)
<Root level="WARN">(5)
<AppenderRef ref="CONSOLE"/>(6)
</Root>
</Loggers>
</Configuration>
1 | Appenders are responsible for writing log events to a particular target; console, file, socket, database, queue, etc. |
2 | Console Appender writes logs to the console. |
3 | Layouts are responsible for encoding log events before appenders writing them. JSON Template Layout encodes log events in JSON. |
4 | Log events generated by classes in the com.mycompany package (incl. its sub-packages) and that are of level INFO or higher (i.e., WARN , ERROR , FATAL ) will be accepted. |
5 | Unless specified otherwise, log events of level WARN and higher will be accepted.
It serves as the default <logger> configuration. |
6 | Unless specified otherwise, accepted log events will be forwarded to the console appender defined earlier. |
Next, you need to configure Log4j for the tests of your application. Please proceed to How do I configure Log4j Core for tests?
How do I install Log4j Core for my library?
This section explains how to install Log4j Core for libraries.
Are you implementing not a library, but an application? Please refer to How do I install Log4j Core to run my application? instead. |
Unlike applications, libraries should be logging implementation agnostic. That is, libraries should log through a logging API, but leave the decision of the logging implementation to the application. That said, libraries need a logging implementation while running their tests.
Let’s see how you can install Log4j Core for your tests.
Start with adding the log4j-core
dependency in test scope to your library:
-
Maven
-
Gradle
<project>
<!-- Assuming `log4j-bom` is added -->
<dependency>
<!-- Logging implementation (Log4j Core) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>test</scope>(1)
</dependency>
</dependency>
</project>
dependencies {
// Assuming `log4j-bom` is already added
// The logging implementation (i.e., Log4j Core)
testRuntimeOnly 'org.apache.logging.log4j:log4j-core' (1)
}
1 | For tests of libraries, the logging implementation is only needed in test scope. |
If your library has (direct or transitive!) dependencies that use another logging API, you need to bridge that to Log4j.
This way the foreign logging API calls will effectively be consumed by Log4j too.
SLF4J is another logging API used pretty common in the wild.
(Installation covers all supported foreign APIs.)
Let’s see how you can use the log4j-slf4j2-impl
bridge to support SLF4J:
-
Maven
-
Gradle
<project>
<!-- Assuming `log4j-bom` is added -->
<dependency>
<!-- Assuming `log4j-core` and `log4j-layout-template-json` is added -->
<!-- SLF4J-to-Log4j bridge -->(2)
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<scope>test</scope>(1)
</dependency>
</dependency>
</project>
dependencies {
// Assuming `log4j-bom`, `log4j-core`, and `log4j-layout-template-json` is added
// SLF4J-to-Log4j bridge (2)
runtimeOnly 'org.apache.logging.log4j:log4j-slf4j2-impl' (1)
}
1 | For tests of libraries, logging bridges are only needed in test scope. |
2 | Log4j module bridging SLF4J to Log4j |
Next, you need to you need to configure Log4j. Please proceed to How do I configure Log4j Core for tests?
How do I configure Log4j Core for tests?
This section explains configuring Log4j on how log events should be processed for tests.
Log4j supports several configuration inputs and file formats. Let’s start with a basic and developer-friendly configuration where the logs are pretty-printed in a human-readable way and written to the console.
Contrast to an application’s more conservative Log4j setup, for tests, we will go with a more developer-friendly Log4j configuration where
-
the logs are pretty-printed to the console, and
-
logging verbosity is increased.
While it is not recommended to use Pattern Layout in production for security reasons, it is a good choice for tests to encode log events. We will use it to pretty-print the log event to the console with extra fields: timestamp, thread name, log level, class name, etc. The rest of the configuration should look familiar from earlier sections.
Save the following XML-formatted Log4j configuration file to src/test/resources/log4j2-test.xml
.
src/test/resources/log4j2-test.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://logging.apache.org/xml/ns"
xsi:schemaLocation="
https://logging.apache.org/xml/ns
https://logging.apache.org/xml/ns/log4j-config-2.xsd">
<Appenders>
<Console name="CONSOLE">
<PatternLayout pattern="%d [%t] %5p %c{1.} - %m%n"/>(1)
</Console>
</Appenders>
<Loggers>
<Logger name="com.mycompany" level="DEBUG"/>(2)
<Root level="WARN">
<AppenderRef ref="CONSOLE"/>
</Root>
</Loggers>
</Configuration>
1 | Pattern Layout is used for encoding the log event in a human-readable way. |
2 | Increased logging verbosity for the com.mycompany package. |
What is next?
At this stage, you know
-
How to install Log4j API and log using it
-
How to install and configure Log4j Core in your application/library
You can use following pointers to further customize your Log4j setup.
- Installation
-
While shared dependency management snippets should get you going, your case might necessitate a more intricate setup. Are you dealing with a Spring Boot application? Is it running in a Java EE container? Do you need to take into account other logging APIs such as JUL, JPL, JCL, etc.? See Installation for the complete installation guide.
- Configuration
-
Log4j can be configured in several ways in various file formats (XML, JSON, Properties, and YAML). See the Configuration file page for details.
- Appenders & Layouts
-
Log4j contains several appenders and layouts to compose a configuration that best suit your needs.
- Performance
-
Do you want to get the best performance out of your logging system? Make sure to check out the Performance page.
- Architecture
-
Want to learn more about loggers, contexts, and how these are all wired together? See the Architecture page.
- Support
-
Confused? Having a problem while setting up Log4j? See the Support page.