blob: 2ba1b0584448075bcd01f4ec9b7f418efe0144e0 [file] [log] [blame]
////
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
////
= Log4j explained in 5 minutes
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]
== What is logging?
Logging is the action 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:
[source, java]
----
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 the `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 could be used to provide more context information.
`%s` will be replaced with the value of `tableName`, and `%n` will be replaced with a new line.
[source, java]
----
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`.
If the developer decides the truncate the table "fruits", the output of this code will look like this:
[source]
----
[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]
== 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 will:
* Enhance the message with additional information (timestamp, class & method name, line number, host, severity, etc.)
* Write the message differently, using a different **layout** (CSV, JSON, etc.)
* Write the message to a different medium, using a different **appender** (file, socket, database, queue, etc.)
* Write only some of the messages, using a **filter** (e.g. filter by severity, content, etc.)
[#install]
== How do to install Log4j?
Add the necessary `log4j-api` dependencies to your application. We will need
a **BOM** (Bill of Materials) to manage the versions of the dependencies.
In addition, we will need the `log4j-api` dependency itself.
[tabs]
====
Maven::
+
[source,xml,subs="+attributes"]
----
<project>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>{log4j-core-version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
</dependency>
</project>
----
Gradle::
+
[source,groovy,subs="+attributes"]
----
dependencies {
implementation platform('org.apache.logging.log4j:log4j-bom:{log4j-core-version}')
implementation 'org.apache.logging.log4j:log4j-api'
}
----
====
[#logging]
== How do I write logs using Log4j?
To write logs, you need a `Logger` instance which you will retrieve from the `LogManager`.
The `Logger` instance is thread-safe and reusable.
Second, you can use the `Logger` instance to write logs by using methods like `info`, `warn`, `error`, etc.
We will come to them in a moment.
The string can also contain placeholders written as `{}` that will be replaced by the arguments passed to the method.
[source,java]
----
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> This is a thread-safe, reusable `Logger` instance.
<2> The placeholder `{}` in the message will be replaced with the value of `tableName`
The generated **log event** will be enriched with the **log level** (i.e., `WARN`),
but also timestamp, class & method name, line number, and several other information.
Log levels are used to categorize log events by severity and control the verbosity of the logs.
Log4j knows various levels, but the most common are `DEBUG`, `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 - we can simply pass the exception as the last argument to the log method.
[source,java]
----
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.
[#pitfalls]
=== Common pitfalls
There are several widespread bad practices.
Let's try to walk through the most common ones.
[#pitfal-toString]
==== Don't use `toString()`
* [ ] Don't use `Object#toString()` in arguments, it is redundant!
+
[source,java]
----
/* BAD! */ LOGGER.info("userId: {}", userId.toString());
----
* [x] Underlying message type and layout will deal with arguments:
+
[source,java]
----
/* GOOD */ LOGGER.info("userId: {}", userId);
----
[#pitfall-exception]
==== Pass exception as the last extra argument
* [ ] Don't call `Throwable#printStackTrace()`!
This not only circumvents the logging but can also leak sensitive information!
+
[source,java]
----
/* BAD! */ exception.printStackTrace();
----
* [ ] Don't use `Throwable#getMessage()`!
This prevents the log event from getting enriched with the exception.
+
[source,java]
----
/* BAD! */ LOGGER.info("failed", exception.getMessage());
/* BAD! */ LOGGER.info("failed for user ID `{}`: {}", userId, exception.getMessage());
----
* [ ] Don't provide both `Throwable#getMessage()` and `Throwable` itself!
This bloats the log message with a duplicate exception message.
+
[source,java]
----
/* BAD! */ LOGGER.info("failed for user ID `{}`: {}", userId, exception.getMessage(), exception);
----
* [x] Pass exception as the last extra argument:
+
[source,java]
----
/* GOOD */ LOGGER.error("failed", exception);
/* GOOD */ LOGGER.error("failed for user ID `{}`", userId, exception);
----
[#pitfal-concat]
==== 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!**
Imagine `userId` being provided by the user with the following content:
`placeholders for non-existing args to trigger failure: {} {} \{dangerousLookup}`
+
[source,java]
----
/* BAD! */ LOGGER.info("failed for user ID: " + userId);
----
* [x] Use message parameters
+
[source,java]
----
/* GOOD */ LOGGER.info("failed for user ID `{}`", userId);
----
[#integrating-log4j]
== Integrating Log4j
Log4j is composed of two main parts, the API and the Core.
With this distinction, you can log through the API and route the log events through the Core.
If you prefer, you can also route the log events through other logging frameworks like SLF4J.
[#log4j-api]
Log4j API::
The logging API your code (programmatically) logs through.
This needs to be available at compile-time and no configuration is needed.
Log4j Core::
The logging implementation is responsible for filtering, routing, encoding, and appending log events.
This needs to be available at runtime and configured by the user.
So your dependencies and their dependencies too.
While deploying your application, you need to provide a **logging implementation** along with its configuration to consume all generated log events.
[#config-app]
== How do I configure Log4j to run my **application**?
The following section describes, how an application can be configured to use Log4j.
It will add a configuration and some other artifacts to your application.
The configuration shown here enhances the security and usability of your application.
[IMPORTANT]
====
Are you implementing not an **application**, but a **library**?
Please skip to the xref:#config-lib[] instead.
====
As mentioned, Log4j is using a logging API.
First of all, add the `log4j-core` **runtime** dependency to our 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 preferred over the default `PatternLayout`.
[tabs]
====
Maven::
+
[source,xml,subs="+attributes"]
----
<project>
<!-- Assuming `log4j-bom` is already 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>
----
Gradle::
+
[source,groovy,subs="+attributes"]
----
dependencies {
// Assuming `log4j-bom` is already 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> Note that the logging implementation and bridges are only needed at runtime.
Now it is time to configure Log4j and instruct how the log events should be routed.
Save the following XML document to `src/**main**/resources/log4j2.xml`.
The xref:manual/json-template-layout.adoc[JSON Template Layout] is used to encode log events in JSON.
Once encoded xref:manual/appenders.adoc[Appenders] are responsible for writing log events to the console, file, socket, database, etc.
The `<logger>` defines, that log events generated by classes in the `com.mycompany` package (incl. its sub-packages) and that are of level `INFO` and higher (i.e., `WARN`, `ERROR`, `FATAL`) will be consumed.
Finally, the `<root>` logger defines that log events of level `WARN` and higher will be consumed unless specified otherwise. It serves as a default configuration.
.An example `src/**main**/resources/log4j2.xml`
[source,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> xref:manual/appenders.adoc[Appenders] are responsible for writing log events to their target
<2> xref:manual/appenders.adoc#ConsoleAppender[Console Appender] writes logs to the console.
<3> xref:manual/json-template-layout.adoc[JSON Template Layout] encodes log events in JSON.
<4> Log events generated by classes in the `com.mycompany` package (incl. its sub-packages) that are of level `INFO` and higher will be consumed.
<5> Unless specified otherwise, log events of level `WARN` and higher will be consumed.
<6> Unless specified otherwise, log events will be forwarded to the `console` appender defined earlier.
If you want to configure Log4j for tests, you are strongly advised to use a different Log4j configuration.
Continue to xref:#config-test[]
In many cases, you might have a library that logs through SLF4J.
Due to the separation of Log4js API and Core, you can add a bridge to forward SLF4J calls to the Log4j API.
This way, SLF4J calls will be processed by Log4j Core too.
It is similarly easy: just add the new dependency `log4j-slf4j2-impl to your application.
[tabs]
====
Maven::
+
[source,xml,subs="+attributes"]
----
<project>
<!-- Other dependencies -->
<dependency>
<!-- SLF4J-to-Log4j bridge --><!--2-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<scope>runtime</scope><!--1-->
</dependency>
</dependency>
</project>
----
Gradle::
+
[source,groovy,subs="+attributes"]
----
dependencies {
// Other dependencies
// SLF4J-to-Log4j bridge // <2>
runtimeOnly 'org.apache.logging.log4j:log4j-slf4j2-impl' // <1>
}
----
====
<1> Again, we only need a runtime dependency.
<2> This dependency will forward SLF4J calls to the Log4j API.
`log4j-slf4j2-impl` forwards SLF4J calls to Log4j API, which effectively gets processed by Log4j Core too.
[#config-lib]
== How do I configure Log4j for my **library**?
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.
[IMPORTANT]
====
Are you implementing not a **library**, but an **application**?
Please skip to the xref:#config-app[] instead.
====
Add the `log4j-core` dependency in **test** scope to your library.
Very similar to the previous section, in most cases it is useful to also add the `log4j-slf4j2-impl` dependency.
SLF4J is a widely used logging API and this way, SLF4J calls will be processed by Log4j Core too.
[tabs]
====
Maven::
+
[source,xml,subs="+attributes"]
----
<project>
<!-- Assuming `log4j-bom` is already added -->
<dependency>
<!-- The logging implementation (i.e., Log4j Core) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>test</scope><!--1-->
</dependency>
<!-- SLF4J-to-Log4j bridge --><!--2-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<scope>test</scope><!--1-->
</dependency>
</dependency>
</project>
----
Gradle::
+
[source,groovy,subs="+attributes"]
----
dependencies {
// Assuming `log4j-bom` is already added
// The logging implementation (i.e., Log4j Core)
testRuntimeOnly 'org.apache.logging.log4j:log4j-core' // <1>
// SLF4J-to-Log4j bridge // <2>
testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j2-impl' // <1>
}
----
====
<1> Note that the logging implementation and bridges are only needed for tests!
<2> SLF4J is another widely used logging API.
`log4j-slf4j2-impl` forwards SLF4J calls to Log4j API, which effectively gets processed by Log4j Core too.
Next, you need a `src/**test**/resources/log4j2-test.xml`.
See xref:#config-test[]
[#config-test]
== How do I configure Log4j for tests?
For tests, prefer a human-readable layout with increased verbosity.
While it is not recommended to use the `PatternLayout` in production for security reasons, it is a good choice for tests.
Save the following XML document to `src/**test**/resources/log4j2-test.xml`.
The xref:manual/layouts.adoc#PatternLayout[Pattern Layout] is used for formatting strings in a specific way.
In the below case, it will include the timestamp, thread name, log level, class name, and the message and
print it to the Console.
Very similar to the earlier configuration, the `<logger>` defines what should be logged on
which level and the `<root>` logger serves as a default configuration.
.An example `src/**test**/resources/log4j2-test.xml`
[source,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> xref:manual/layouts.adoc#PatternLayout[Pattern Layout] is used for a human-readable layout.
<2> Increased logging verbosity for the `com.mycompany` package.
[#next]
== What is next?
More details::
If you are looking for a more detailed read, please see {logging-services-url}/what-is-logging.html[What is logging?].
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 xref:manual/installation.adoc[] for the complete installation guide.
Configuration::
Log4j can be configured in several ways in various file formats (XML, JSON, Properties, and YAML).
See the xref:manual/configuration.adoc[] page for details.
Appenders & Layouts::
Log4j contains several xref:manual/appenders.adoc[appenders] and xref:manual/layouts.adoc[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 xref:manual/performance.adoc[] page.
Architecture::
Want to learn more about loggers, contexts, and how these are all wired together?
See the xref:manual/architecture.adoc[] page.
Support::
Confused?
Having a problem while setting up Log4j?
See the {logging-services-url}/support.html[Support] page.