| //// |
| 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] |
| == Prerequisites |
| |
| 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. |
| 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. |
| |
| [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. |
| |
| As already mentioned 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`, `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 - 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); |
| ---- |
| |
| [#basic-log4j-architecture] |
| == Basic Log4j Architecture |
| |
| In a nutshell, Log4j operates with two main parts: the API and the Core. |
| With this structure, Log4j allows you to log events using the API and route them through the Core. |
| |
| Optionally you can also route the log events through other logging frameworks |
| like SLF4J or JUL (Java Util Logging) by adding a bridge. |
| |
| [#log4j-api] |
| Log4j API:: |
| This is the interface that you use in your application to log events. |
| It needs to be available at compile-time and no configuration is needed. |
| By using it, you ensure that your application can write logs but is not tied to a specific logging implementation. |
| |
| [#log4j-core] |
| Log4j Core:: |
| The Log4j Core is a logging implementation that processes log events. |
| It is responsible for filtering, routing, encoding, and appending log events. |
| This needs to be available at runtime and requires configuration by the user. |
| When you deploy your application, you must also deploy the Log4j Core or any |
| other logging implementation along with its configuration to consume all generated log events |
| |
| The following sections show you examples of how you can get started quickly with Log4j. |
| |
| [#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`, at least for production deployments. |
| |
| [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. |
| |
| 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. |
| |
| Save the following XML document to `src/**main**/resources/log4j2.xml`. |
| |
| .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 from `com.mycompany` and its sub-packages, at `INFO` level or higher, are 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[] |
| |
| [#integrating-log4j] |
| == Integrating Log4j with SLF4J |
| |
| 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> `log4j-slf4j2-impl` forwards SLF4J calls to the Log4j API. |
| |
| 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. |
| |
| 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. |
| |
| Save the following XML document to `src/**test**/resources/log4j2-test.xml`. |
| |
| .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. |