| //// |
| 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. |
| //// |
| |
| = Learn Log4j in 5 minutes! |
| |
| Do you need a crash course on Log4j? |
| You have come to the right place! |
| If you are looking for a more detailed read, please see {logging-services-url}/what-is-logging.html[What is logging?]. |
| |
| [#what] |
| == What is logging and Log4j? |
| |
| Logging is the action of publishing diagnostics information at certain points of a program execution: |
| |
| [source,java] |
| ---- |
| private void truncateTable(String tableName) { |
| System.out.format("[WARN] Truncating table `%s`!%n", tableName); |
| db.truncate(tableName); |
| } |
| ---- |
| |
| This provides observability into an application's runtime. |
| |
| But we can do way better than a `printf()` statement! |
| |
| * 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.) |
| |
| Log4j is a versatile, industrial-grade Java logging framework delivering all these and more in one product. |
| It is essentially composed of a **logging API** and its **implementation**: |
| |
| 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. |
| |
| [#logging] |
| == How do I write logs using Log4j? |
| |
| Add the `log4j-api` dependency to your application: |
| |
| [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' |
| } |
| ---- |
| ==== |
| |
| And start logging: |
| |
| [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. |
| The associated class will be captured at initialization – no need for a `getLogger(DbTableService.class)`. |
| <2> The parameter placeholders `{}` in the message will be automatically replaced with the value of `tableName` and the generated **log event** will be enriched with **level** (i.e., `WARN`), timestamp, class & method name, line number, and several other information. |
| |
| Make sure to log exceptions that have diagnostics value: |
| |
| [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> Notice the `error()` method? |
| Yup, the level is set to `ERROR`. |
| + |
| What about the `exception` in the last argument? |
| Wait a second! |
| There is one placeholder in the format (i.e., `{}`), but there are two parameters passed in arguments: `tableName` and `exception`! |
| What the heck? |
| Yep, you guessed it right! |
| Log4j API 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); |
| ---- |
| |
| [#config-app] |
| == How do I configure Log4j to run my **application**? |
| |
| Your code logs through a logging API. |
| 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. |
| |
| [IMPORTANT] |
| ==== |
| Are you implementing not an **application**, but a **library**? |
| Please skip to the xref:#config-lib[] instead. |
| ==== |
| |
| Add the `log4j-core` **runtime** dependency to your application: |
| |
| [tabs] |
| ==== |
| Maven:: |
| + |
| [source,xml,subs="+attributes"] |
| ---- |
| <project> |
| |
| <!-- Assuming you already have the `dependencyManagement > dependencies > dependency` entry for `log4j-bom` --> |
| |
| <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> |
| |
| <!-- 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 { |
| |
| // Assuming you already have the `implementation platform(...)` entry for `log4j-bom` |
| |
| // 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> |
| |
| // SLF4J-to-Log4j bridge // <2> |
| runtimeOnly 'org.apache.logging.log4j:log4j-slf4j2-impl' // <1> |
| |
| } |
| ---- |
| ==== |
| <1> Note that the logging implementation and bridges are only needed at runtime! |
| <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. |
| |
| 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`: |
| |
| .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 the console, file, socket, database, etc. |
| <2> xref:manual/appenders.adoc#ConsoleAppender[Console Appender] is used to write logs to the console. |
| <3> xref:manual/json-template-layout.adoc[JSON Template Layout] is used to encode 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` and higher (i.e., `WARN`, `ERROR`, `FATAL`) 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. |
| |
| You are strongly advised to use a different Log4j configuration for tests. |
| Continue to xref:#config-test[] |
| |
| [#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` **test** dependency to your library: |
| |
| [tabs] |
| ==== |
| Maven:: |
| + |
| [source,xml,subs="+attributes"] |
| ---- |
| <project> |
| |
| <!-- Assuming you already have the `dependencyManagement > dependencies > dependency` entry for `log4j-bom` --> |
| |
| <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 you already have the `implementation platform(...)` entry for `log4j-bom` |
| |
| // 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. |
| 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? |
| |
| 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. |