From 48aa669d04c2a3fff7bcf1bc4a9d154f7ce9fcc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Wed, 22 May 2024 12:10:06 +0200 Subject: [PATCH 01/11] Improve `api.adoc` et al. --- src/site/antora/modules/ROOT/nav.adoc | 20 +- src/site/antora/modules/ROOT/pages/5min.adoc | 140 +------ .../ROOT/pages/manual/api-separation.adoc | 233 ----------- .../antora/modules/ROOT/pages/manual/api.adoc | 370 ++++++++++++------ .../ROOT/pages/manual/customloglevels.adoc | 2 +- .../modules/ROOT/pages/manual/logbuilder.adoc | 2 +- .../modules/ROOT/pages/manual/usage.adoc | 227 ----------- ...-best-practice-dont-use-string-concat.adoc | 36 ++ .../api-best-practice-dont-use-toString.adoc | 30 ++ ...t-practice-exception-as-last-argument.adoc | 30 ++ .../api-best-practice-use-supplier.adoc | 47 +++ .../ROOT/partials/manual/api-intro.adoc | 72 ++++ src/site/resources/.htaccess | 2 + 13 files changed, 492 insertions(+), 719 deletions(-) delete mode 100644 src/site/antora/modules/ROOT/pages/manual/api-separation.adoc delete mode 100644 src/site/antora/modules/ROOT/pages/manual/usage.adoc create mode 100644 src/site/antora/modules/ROOT/partials/manual/api-best-practice-dont-use-string-concat.adoc create mode 100644 src/site/antora/modules/ROOT/partials/manual/api-best-practice-dont-use-toString.adoc create mode 100644 src/site/antora/modules/ROOT/partials/manual/api-best-practice-exception-as-last-argument.adoc create mode 100644 src/site/antora/modules/ROOT/partials/manual/api-best-practice-use-supplier.adoc create mode 100644 src/site/antora/modules/ROOT/partials/manual/api-intro.adoc diff --git a/src/site/antora/modules/ROOT/nav.adoc b/src/site/antora/modules/ROOT/nav.adoc index 15e7856c7db..6fde30e7cde 100644 --- a/src/site/antora/modules/ROOT/nav.adoc +++ b/src/site/antora/modules/ROOT/nav.adoc @@ -31,17 +31,18 @@ * xref:manual/installation.adoc[] * xref:manual/architecture.adoc[] * xref:manual/migration.adoc[] -* xref:manual/api.adoc[] +* xref:manual/api.adoc[API] ** xref:manual/logbuilder.adoc[] -** xref:manual/flowtracing.adoc[] ** xref:manual/markers.adoc[] -** xref:manual/eventlogging.adoc[] -** xref:manual/messages.adoc[] -** xref:manual/thread-context.adoc[] ** xref:manual/scoped-context.adoc[] +** xref:manual/thread-context.adoc[] +** xref:manual/messages.adoc[] +** xref:manual/flowtracing.adoc[] +** xref:manual/eventlogging.adoc[] ** xref:manual/resource-logger.adoc[] * Configuration ** xref:manual/configuration.adoc[Configuration file] +** xref:manual/systemproperties.adoc[] ** xref:manual/appenders.adoc[] ** xref:manual/layouts.adoc[] *** xref:manual/json-template-layout.adoc[] @@ -49,21 +50,12 @@ ** xref:manual/customloglevels.adoc[] ** xref:manual/filters.adoc[] ** xref:manual/scripts.adoc[] -** xref:manual/systemproperties.adoc[] -* xref:manual/usage.adoc[] * xref:manual/cloud.adoc[] -* xref:manual/lookups.adoc[] -* xref:manual/appenders.adoc[] -* xref:manual/layouts.adoc[] -** xref:manual/json-template-layout.adoc[] -* xref:manual/filters.adoc[] * xref:manual/extending.adoc[] ** xref:manual/customconfig.adoc[] * xref:manual/plugins.adoc[] * xref:manual/customconfig.adoc[] -* xref:manual/customloglevels.adoc[] * xref:manual/jmx.adoc[] -* xref:manual/logsep.adoc[] * xref:manual/performance.adoc[] ** xref:manual/async.adoc[] ** xref:manual/garbagefree.adoc[] diff --git a/src/site/antora/modules/ROOT/pages/5min.adoc b/src/site/antora/modules/ROOT/pages/5min.adoc index cc5246fb651..ec24c35776c 100644 --- a/src/site/antora/modules/ROOT/pages/5min.adoc +++ b/src/site/antora/modules/ROOT/pages/5min.adoc @@ -129,144 +129,32 @@ dependencies { [#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. +include::partial$manual/api-intro.adoc[] -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. +[#best-practice] +=== Best practices -[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. +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 xref:manual/api.adoc#best-practice[the Log4j API best practices page]. -[#pitfal-toString] +[#best-practice-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); ----- +include::partial$manual/api-best-practice-dont-use-toString.adoc[] -[#pitfall-exception] +[#best-practice-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); ----- +include::partial$manual/api-best-practice-exception-as-last-argument.adoc[] -[#pitfal-concat] +[#best-practice-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); ----- +include::partial$manual/api-best-practice-dont-use-string-concat.adoc[] -[#basic-log4j-architecture] -== Basic Log4j Architecture +[#architecture] +== 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. diff --git a/src/site/antora/modules/ROOT/pages/manual/api-separation.adoc b/src/site/antora/modules/ROOT/pages/manual/api-separation.adoc deleted file mode 100644 index 51233ef907e..00000000000 --- a/src/site/antora/modules/ROOT/pages/manual/api-separation.adoc +++ /dev/null @@ -1,233 +0,0 @@ -//// -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. -//// -= API Separation - -When selecting a logging library, some care must be taken in order to ensure that multiple different logging libraries are properly accounted for. -For example, library code that you depend on may use slf4j, while other libraries may simply use java.util.logging. -All of these can be routed to the log4j core in order to be logged. - -If however you want to use a different logging implementation (such as logback), it is possible to route messages from the Log4j API to logback, ensuring that your application is not tied to a specific logging framework. - -A typical class using the Log4j2 API looks like the following: - -[,java] ----- -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class Log4j2Test { - private static final Logger logger = LogManager.getLogger(); - - public Log4j2Test(){ - logger.info( "Hello World!" ); - } -} ----- - -In order to use the API portion of Log4j2, we only need to provide a single dependency, log4j-api. -Using Maven, you would add the following to your dependencies: - -[,xml] ----- - - org.apache.logging.log4j - log4j-api - 2.17.0 - ----- - -== Using Log4j2 API and Core - -Using the Log4j2 API and the implementation (Core) together means that log messages will be routed through the Log4j2 Core. -The Log4j2 core implementation is responsible for the following (note: this is not an exhaustive list): - -* Configuration of the system (via an XML file for example) -* Routing messages to Appenders -* Opening files and other resources for logging (e.g. -network sockets) - -The xref:manual/configuration.adoc[configuration] page in the manual describes the configuration format supported by the Log4j2 core implementation. - -To use both the API and the core implementation, you would add the following to your dependencies (assuming that you are using Maven): - -[,xml] ----- - - org.apache.logging.log4j - log4j-api - 2.17.0 - - - org.apache.logging.log4j - log4j-core - 2.17.0 - ----- - -Note that having two different versions of log4j-api and log4j-core on your classpath is not guaranteed to work correctly (e.g., log4j-api version 2.15 and log4j-core version 2.17 are not guaranteed to work correctly together). - -== Using Log4j2 API with Logback - -Since the Log4j2 API is generic, we can use it to send messages via SLF4J and then have Logback do the actual logging of the messages. -This means that you can write your code tied to the Log4j2 API, but users of your code do not need to use the Log4j2 core if they are already using Logback. - -To switch to using Logback as your logging backend, you will need to add the following to your dependencies (assuming that you are using Maven): - -[,xml] ----- - - org.apache.logging.log4j - log4j-api - 2.17.0 - - - org.apache.logging.log4j - log4j-to-slf4j - 2.17.0 - - - ch.qos.logback - logback-classic - 1.2.10 - ----- - -== Using Log4j2 as an SLF4J Implementation - -If you don't want to depend on the Log4j2 API and instead want to use SLF4J, that is possible as well. -Assuming that our code looks like the following: - -[,java] ----- -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Log4j2Test { - - private static final Logger logger = LoggerFactory.getLogger(Log4j2Test.class); - - public Log4j2Test(){ - logger.info( "Hello World!" ); - } -} ----- - -We can then route the messages to Log4j2 using the log4j-slf4j-impl like the following: - -[,xml] ----- - - org.slf4j - slf4j-api - 1.7.32 - - - org.apache.logging.log4j - log4j-slf4j-impl - 2.17.0 - - - org.apache.logging.log4j - log4j-core - 2.17.0 - ----- - -Note that if we were using SLF4J 1.8 instead of 1.7, that requires us to use log4j-slf4j18-impl instead of log4j-slf4j-impl. - -== Using Log4j2 with JUL - -It is also possible to route messages that are logged using java.util.logging to Log4j2. -Assuming that the code looks like the following: - -[,java] ----- -import java.util.logging.Logger; - -public class Log4j2Test { - - private static final Logger logger = Logger.getLogger(Log4j2Test.class.getName()); - - public Log4j2Test() { - logger.info("Hello World!"); - } -} ----- - -We can then also route these messages to the Log4j2 core by adding in the JUL bridge, and setting `-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager` on the JVM (see the documentation on xref:../log4j-jul.adoc[the JUL adapter] for more information as to how this works). - -In order to route these messages to Log4j2, your dependencies would look like the following: - -[,xml] ----- - - org.apache.logging.log4j - log4j-jul - 2.17.0 - - - org.apache.logging.log4j - log4j-core - 2.17.0 - ----- - -== Using Log4j2 as a backend for Log4j1 - -Some software may still depend on Log4j1, and in some cases it may be infeasible to modify this software to migrate it to Log4j2. - -However, it may be possible to start using Log4j2 without modifying the application. - -Assuming that our code looks like the following: - -[,java] ----- -import org.apache.log4j.Logger; - -public class Log4j2Test { - - private static final Logger logger = Logger.getLogger(Log4j2Test.class); - - public Log4j2Test(){ - logger.info( "Hello World!" ); - } -} ----- - -we can then quickly and easily configure these messages to use Log4j2 as the logging implementation by depending on the `log4j-1.2-api` bridge, like so: - -[,xml] ----- - - org.apache.logging.log4j - log4j-1.2-api - 2.17.0 - - - org.apache.logging.log4j - log4j-core - 2.17.0 - ----- - -There are some limitations to this, but it is expected to work for the majority of common cases. -See the xref:manual/migration.adoc[manual page on migration] for more information on this feature. - -== Conclusion - -With the API separation that Log4j2 provides, it is possible to use multiple logging APIs and implementations in the same project. -This allows for greater flexibility, ensuring that you are not tied to a single API or implementation. diff --git a/src/site/antora/modules/ROOT/pages/manual/api.adoc b/src/site/antora/modules/ROOT/pages/manual/api.adoc index 7c5b8d0154c..ce3360844f9 100644 --- a/src/site/antora/modules/ROOT/pages/manual/api.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/api.adoc @@ -14,187 +14,323 @@ See the License for the specific language governing permissions and limitations under the License. //// + +:jboss-logging-link: https://github.com/jboss-logging/jboss-logging[JBoss Logging] +:jcl-link: https://commons.apache.org/proper/commons-logging/[JCL (Apache Commons Logging)] +:jpl-link: https://openjdk.org/jeps/264[JPL (Java Platform Logging)] +:jul-link: https://docs.oracle.com/en/java/javase/{java-target-version}/core/java-logging-overview.html[JUL (Java Logging)] +:logback-link: https://logback.qos.ch/[Logback] +:slf4j-link: https://www.slf4j.org/[SLF4J] + = Log4j API -== Overview +Log4j is essentially composed of a logging API called *Log4j API*, and its reference implementation called *Log4j Core*. -The Log4j 2 API provides the interface that applications should code to -and provides the adapter components required for implementers to create -a logging implementation. Although Log4j 2 is broken up between an API -and an implementation, the primary purpose of doing so was not to allow -multiple implementations, although that is certainly possible, but to -clearly define what classes and methods are safe to use in "normal" -application code. +.What is a logging API and a logging implementation? +[%collapsible] +==== +Logging APIs:: +A logging API is an interface your code or your dependencies directly logs against. +It is implementation agnostic. +Log4j API, {slf4j-link}, {jul-link}, {jcl-link}, {jpl-link} and {jboss-logging-link} are major logging APIs. -=== Hello World! +Logging implementation:: +A logging implementation is only required at runtime and can be changed without the need to recompile your software. +Log4j Core, {jul-link}, {logback-link} are the most well-known logging implementations. +==== -No introduction would be complete without the customary Hello, World -example. Here is ours. First, a Logger with the name "HelloWorld" is -obtained from the -link:../javadoc/log4j-api/org/apache/logging/log4j/LogManager.html[`LogManager`]. -Next, the logger is used to write the "Hello, World!" message, however -the message will be written only if the Logger is configured to allow -informational messages. +[TIP] +==== +Are you looking for a crash course on how to use Log4j in your application or library? +See xref:5min.adoc[]. +You can also check out xref:manual/installation.adoc[] for the complete installation instructions. +==== -[source,java] ----- -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +Log4j API provides -public class HelloWorld { - private static final Logger logger = LogManager.getLogger("HelloWorld"); - public static void main(String[] args) { - logger.info("Hello, World!"); - } -} ----- +* A logging API that libraries and applications can code to +* Adapter components to create a logging implementation -The output from the call to `logger.info()` will vary significantly -depending on the configuration used. See the -xref:manual/configuration.adoc[Configuration] section for more details. +This page tries to cover the most prominent Log4j API features that libraries and applications can code to. -=== Substituting Parameters +== Introduction -Frequently the purpose of logging is to provide information about what -is happening in the system, which requires including information about -the objects being manipulated. In Log4j 1.x this could be accomplished -by doing: +include::partial$manual/api-intro.adoc[leveloffset=+1] -[source,java] ----- -if (logger.isDebugEnabled()) { - logger.debug("Logging in user " + user.getName() + " with birthday " + user.getBirthdayCalendar()); -} ----- +[#best-practice] +== Best practices + +There are several widespread bad practices while using Log4j API. +Let's try to walk through the most common ones and see how to fix them. + +[#best-practice-toString] +=== Don't use `toString()` + +include::partial$manual/api-best-practice-dont-use-toString.adoc[] + +[#best-practice-exception] +=== Pass exception as the last extra argument + +include::partial$manual/api-best-practice-exception-as-last-argument.adoc[] + +[#best-practice-concat] +=== Don't use string concatenation + +include::partial$manual/api-best-practice-dont-use-string-concat.adoc[] + +[#best-practice-supplier] +=== Use ``Supplier``s to pass computationally expensive arguments + +include::partial$manual/api-best-practice-use-supplier.adoc[] + +[#loggers] +== Loggers -Doing this repeatedly has the effect of making the code feel like it is -more about logging than the actual task at hand. In addition, it results -in the logging level being checked twice; once on the call to -isDebugEnabled and once on the debug method. A better alternative would -be: +link:../javadoc/log4j-api/org/apache/logging/log4j/Logger.html[`Logger`]s obtained using link:../javadoc/log4j-api/org/apache/logging/log4j/LogManager.html[`LogManager`] is the primary entry point for logging. +In this section we will introduce you to further details about ``Logger``s. + +[#logger-names] +=== Logger names + +Most logging implementations use a hierarchical scheme for matching logger names with logging configuration. +In this scheme, the logger name hierarchy is represented by `.` (dot) characters in the logger name, in a fashion very similar to the hierarchy used for Java package names. +For example, `org.apache.logging.appender` and `org.apache.logging.filter` both have `org.apache.logging` as their parent. + +In most cases, applications name their loggers by passing the current class's name to `LogManager.getLogger(...)`. +Because this usage is so common, Log4j provides that as the default when the logger name parameter is either omitted or is null. +For example, all `Logger`-typed variables below will have a name of `com.mycompany.LoggerNameTest`: [source,java] ---- -logger.debug("Logging in user {} with birthday {}", user.getName(), user.getBirthdayCalendar()); +package com.mycompany; + +public class LoggerNameTest { + + Logger logger1 = LogManager.getLogger(LoggerNameTest.class); + + Logger logger2 = LogManager.getLogger(LoggerNameTest.class.getName()); + + Logger logger3 = LogManager.getLogger(); + +} ---- -With the code above the logging level will only be checked once and the -String construction will only occur when debug logging is enabled. +**We suggest you to use `LogManager.getLogger()` without any arguments** since it delivers the same functionality with less characters and is not prone to copy-paste errors. -=== Formatting Parameters +[#formatter-logger] +=== Formatter logger -Formatter Loggers leave formatting up to you if `toString()` is not what -you want. To facilitate formatting, you can use the same format strings -as Java's -http://docs.oracle.com/javase/6/docs/api/java/util/Formatter.html#syntax[`Formatter`]. -For example: +The `Logger` instance returned by default replaces the occurrences of `{}` placeholders with the `toString()` output of the associated parameter. +If you need more control over how the parameters are formatted, you can also use the http://docs.oracle.com/javase/6/docs/api/java/util/Formatter.html#syntax[`java.util.Formatter`] format strings by obtaining your `Logger` using link:../javadoc/log4j-api/org/apache/logging/log4j/LogManager.html#getFormatterLogger(java.lang.Class)[`LogManager#getFormatterLogger()`]: [source,java] ---- -public static Logger logger = LogManager.getFormatterLogger("Foo"); - +Logger logger = LogManager.getFormatterLogger(); logger.debug("Logging in user %s with birthday %s", user.getName(), user.getBirthdayCalendar()); logger.debug("Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar()); logger.debug("Integer.MAX_VALUE = %,d", Integer.MAX_VALUE); logger.debug("Long.MAX_VALUE = %,d", Long.MAX_VALUE); ---- -To use a formatter Logger, you must call one of the `LogManager` -link:../javadoc/log4j-api/org/apache/logging/log4j/LogManager.html#getFormatterLogger(java.lang.Class)[`getFormatterLogger`] -methods. The output for this example shows that `Calendar::toString` is -verbose compared to custom formatting: +Loggers returned by `getFormatterLogger()` are referred as *formatter loggers*. + +[#printf] +==== `printf()` method + +Formatter loggers give fine-grained control over the output format, but have the drawback that the correct type must be specified. +For example, passing anything other than a decimal integer for a `%d` format parameter gives an exception. +If your main usage is to use `{}`-style parameters, but occasionally you need fine-grained control over the output format, you can use the `Logger#printf()` method: [source,java] ---- -2012-12-12 11:56:19,633 [main] DEBUG: User John Smith with birthday java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=?,YEAR=1995,MONTH=4,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=23,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?] -2012-12-12 11:56:19,643 [main] DEBUG: User John Smith with birthday 05 23, 1995 -2012-12-12 11:56:19,643 [main] DEBUG: Integer.MAX_VALUE = 2,147,483,647 -2012-12-12 11:56:19,643 [main] DEBUG: Long.MAX_VALUE = 9,223,372,036,854,775,807 +Logger logger = LogManager.getLogger("Foo"); +logger.debug("Opening connection to {}...", someDataSource); +logger.printf(Level.INFO, "Hello, %s!", userName); ---- -=== Mixing Loggers with Formatter Loggers +[#formatter-perf] +==== Formatter performance + +Keep in mind that, contrary to the formatter logger, the default Log4j logger (i.e., `{}`-style parameters) is heavily optimized for several use cases and can operate xref:manual/garbagefree.adoc[garbage-free] when configured correctly. +You might reconsider your formatter logger usages for latency sensitive applications. + +[#resource-logger] +=== Resource logger + +Resource loggers, introduced in Log4j API `2.24.0`, is a special kind of `Logger` that: + +* is a regular class member variable that will be garbage collected along with the class instance +* enriches generated log events with data associated with the resource (i.e., the class instance) + +xref:manual/resource-logger.adoc[Read more on resource loggers...] + +[#event-logger] +=== Event logger + +link:../javadoc/log4j-api/org/apache/logging/log4j/LogManager.html[`EventLogger`] provides a simple way to log structured events conforming with the `STRCUTURED-DATA` format defined in https://tools.ietf.org/html/rfc5424[RFC 5424 (The Syslog Protocol)]. -Formatter loggers give fine-grained control over the output format, but -have the drawback that the correct type must be specified (for example, -passing anything other than a decimal integer for a %d format parameter -gives an exception). +xref:manual/eventlogging.adoc[Read more on event loggers...] -If your main usage is to use \{}-style parameters, but occasionally you -need fine-grained control over the output format, you can use the -`printf` method: +[#feature-fluent-api] +== Fluent API + +The fluent API allows you to log using a fluent interface: [source,java] ---- -public static Logger logger = LogManager.getLogger("Foo"); - -logger.debug("Opening connection to {}...", someDataSource); -logger.printf(Level.INFO, "Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar()); +logger.atInfo() + .withMarker(marker) + .withLocation() + .withThrowable(exception) + .log("Login for user `{}` failed", userId); ---- -[#LambdaSupport] -=== Java 8 lambda support for lazy logging +xref:manual/logbuilder.adoc[Read more on the Fluent API...] + +[#fish-tagging] +== Fish tagging + +Just as a fish can be tagged and have its movement tracked (aka. _fish tagging_), stamping log events with a common tag or set of data +elements allows the complete flow of a transaction or a request to be tracked. +You can use them for several purposes, such as: + +* Provide extra information while serializing the log event +* Allow filtering of information so that it does not overwhelm the system or the individuals who need to make use of it + +Log4j provides fish tagging in several flavors: + +[#levels] +=== Levels + +Log levels are used to categorize log events by severity. +Log4j contains predefined levels, of which the most common are `DEBUG`, `INFO`, `WARN`, and `ERROR`. +Log4j also allows you to introduce your own custom levels too. + +xref:manual/customloglevels.adoc[Read more on custom levels...] -In release 2.4, the `Logger` interface added support for lambda -expressions. This allows client code to lazily log messages without -explicitly checking if the requested log level is enabled. For example, -previously you would write: +[#markers] +=== Markers + +Markers are programmatic labels developers can associate to log statements: [source,java] ---- -// pre-Java 8 style optimization: explicitly check the log level -// to make sure the expensiveOperation() method is only called if necessary -if (logger.isTraceEnabled()) { - logger.trace("Some long-running operation returned {}", expensiveOperation()); +public class MyApp { + + private static final Logger LOGGER = LogManager.getLogger(); + + private static final Marker ACCOUNT_MARKER = MarkerManager.getMarker("ACCOUNT"); + + public void removeUser(String userId) { + logger.debug(ACCOUNT_MARKER, "Removing user with ID `{}`", userId); + // ... + } + } ---- -With Java 8 you can achieve the same effect with a lambda expression. -You no longer need to explicitly check the log level: +xref:manual/markers.adoc[Read more on markers...] + +[#scoped-context] +=== Scoped Context + +Just like a https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/ScopedValue.html[Java's `ScopedValue`], the visibility of tags are associated with the block they were introduced: [source,java] ---- -// Java-8 style optimization: no need to explicitly check the log level: -// the lambda expression is not evaluated if the TRACE level is not enabled -logger.trace("Some long-running operation returned {}", () -> expensiveOperation()); + +private class Worker implements Runnable { + + private static final Logger LOGGER = LogManager.getLogger(); + + public void authUser(Request request, Session session) { + // ... // <3> + ScopedContext + .where("ipAddress", request.getRemoteAddr()) // <1> + .where("hostName", request.getServerName()) // <1> + .where("loginId", session.getAttribute("loginId")) // <1> + .run(() -> { // <2> + LOGGER.debug("Authenticating user"); + // ... + }); + // ... // <3> + } + +} ---- +<1> Associating properties such that they will only be visible to Log4j **within the scope of the `run()` method**. +These properties can later on be used to, for instance, filter the log event, provide extra information in the layout, etc. +<2> The block determining the visibility scope of the provided properties. +<3> Outside the scope of the `run()` method provided properties will not be visible. + +xref:manual/scoped-context.adoc[Read more on Scoped Context...] -=== Logger Names +[#thread-context] +=== Thread Context -Most logging implementations use a hierarchical scheme for matching -logger names with logging configuration. In this scheme, the logger name -hierarchy is represented by `'.'` characters in the logger name, in a -fashion very similar to the hierarchy used for Java package names. For -example, `org.apache.logging.appender` and `org.apache.logging.filter` -both have `org.apache.logging` as their parent. In most cases, -applications name their loggers by passing the current class's name to -`LogManager.getLogger(...)`. Because this usage is so common, Log4j 2 -provides that as the default when the logger name parameter is either -omitted or is null. For example, in all examples below the Logger will -have a name of `"org.apache.test.MyTest"`. +Just like https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html[Java's `ThreadLocal`], the visibility of tags are associated with the thread they were introduced: [source,java] ---- -package org.apache.test; +ThreadContext.put("ipAddress", request.getRemoteAddr()); // <1> +ThreadContext.put("hostName", request.getServerName()); // <1> +ThreadContext.put("loginId", session.getAttribute("loginId")); // <1> -public class MyTest { - private static final Logger logger = LogManager.getLogger(MyTest.class); -} +LOGGER.debug("Performing work"); ---- +<1> Associating properties **with the thread**. +These properties can later on be used to, for instance, filter the log event, provide extra information in the layout, etc. + +[CAUTION] +==== +**Thread Context is mostly superseded by Scoped Context**, which, unlike Thread Context, + +* is safe to use in servlet applications +* can store any `Object`-typed value +==== + +xref:manual/thread-context.adoc[Read more on Thread Context]... + +[#messages] +== Messages + +Whereas almost every other logging API and implementation accepts only `String`-typed input as message, Log4j generalizes this concept with a `Message` contract. +Customizability of the message type enables users to **have complete control over how a message is encoded** by Log4j. +This liberal approach allows applications to choose the message type best fitting to their logging needs; they can log plain ``String``s, or custom `PurchaseOrder` objects. + +Log4j provides several predefined message types to cater for common use cases: +* Simple `String`-typed messages: ++ [source,java] ---- -package org.apache.test; - -public class MyTest { - private static final Logger logger = LogManager.getLogger(MyTest.class.getName()); -} +LOGGER.info("foo"); +LOGGER.info(new SimpleMessage("foo")); ---- +* `String`-typed parameterized messages: ++ [source,java] ---- -package org.apache.test; +LOGGER.info("foo {} {}", "bar", "baz"); +LOGGER.info(new ParameterizedMessage("foo {} {}", new Object[]{"bar", "baz"})); +---- -public class MyTest { - private static final Logger logger = LogManager.getLogger(); -} +* `Map`-typed messages: ++ +[source,java] ---- +LOGGER.info(new StringMapMessage() + .with("key1", "val1") + .with("key2", "val2")); +---- + +xref:manual/messages.adoc[Read more on messages...] + +[#flow-tracing] +== Flow tracing + +The `Logger` class provides `traceEntry()`, `traceExit()`, `catching()`, `throwing()` methods that are quite useful for following the execution path of applications. +These methods generate log events that can be filtered separately from other debug logging. + +xref:manual/flowtracing.adoc[Read more on flow tracing...] diff --git a/src/site/antora/modules/ROOT/pages/manual/customloglevels.adoc b/src/site/antora/modules/ROOT/pages/manual/customloglevels.adoc index 3110c717055..8f15912e375 100644 --- a/src/site/antora/modules/ROOT/pages/manual/customloglevels.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/customloglevels.adoc @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. //// -= Custom Log Levels += Levels [[top]] diff --git a/src/site/antora/modules/ROOT/pages/manual/logbuilder.adoc b/src/site/antora/modules/ROOT/pages/manual/logbuilder.adoc index f6b93f158a8..3cc8c40591a 100644 --- a/src/site/antora/modules/ROOT/pages/manual/logbuilder.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/logbuilder.adoc @@ -15,7 +15,7 @@ limitations under the License. //// -= Log Builder += Fluent API Log4j has traditionally been used with logging statements like [source,java] diff --git a/src/site/antora/modules/ROOT/pages/manual/usage.adoc b/src/site/antora/modules/ROOT/pages/manual/usage.adoc deleted file mode 100644 index 3c158bf7047..00000000000 --- a/src/site/antora/modules/ROOT/pages/manual/usage.adoc +++ /dev/null @@ -1,227 +0,0 @@ -//// - 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. -//// -= Usage - -[#static-vs-non-static] -== Static vs Non-Static Loggers -As with any variable in Java, Loggers may be declared as static variables or class member variables. However, -there are a few factors to consider when choosing to declare a logger as static vs non-static. Generally, it -is better to declare Loggers as static. - -1. Instantiation of a new Logger is a fairly expensive operation when using the default ContextSelector, -link:../javadoc/log4j-core/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.html[ClassLoaderContextSelector]. -When the Logger is created the `ClassLoaderContextSelector` will locate the ClassLoader for the Class the Logger -is associated with and add the Logger to the LoggerContext associated with that ClassLoader. -2. Once a Logger is created it will not be deleted until the `LoggerContext` it is associated with -is deleted. Typically, this will only happen when the application is shut down or un-deployed. Each call -to getLogger with the same logger name will return the same Logger instance. Thus, there is very little -difference between a static or non-static Logger. -3. There is no behavioral difference between a static and non-static Logger. Both will have the Logger name -assigned when they are created, which usually will be the name of the class they are associated with. See -the discussion below on logger names vs class names and the example for more information. - -[#logger-name-vs-class-name] -== Logging the Logger name vs the Class name -The logger name of a Logger is specified when the Logger is created. When a log method is called the -class name value in the log event will reflect the name of the class the log method was called from, which is -not necessarily the same as the class that created the Logger. The following example illustrations this. - -The base class creates a static Logger and a logger variable that is initialized as that same Logger. - It has two methods that perform logging, once that uses the static logger and one that uses a Logger that - can be overridden. - - -[source] ----- - package org.apache.logging; - - import org.apache.logging.log4j.LogManager; - import org.apache.logging.log4j.Logger; - import org.apache.logging.log4j.Marker; - - /** - * - */ - public abstract class Parent { - - // The name of this Logger will be "org.apache.logging.Parent" - protected static final Logger parentLogger = LogManager.getLogger(); - - private Logger logger = parentLogger; - - protected Logger getLogger() { - return logger; - } - - protected void setLogger(Logger logger) { - this.logger = logger; - } - - - public void log(Marker marker) { - logger.debug(marker,"Parent log message"); - } - } ----- - -This class extends the base class. It provides its own logger and has three methods, one that uses the -logger in this class,one that uses the static logger from the base class, and one that where the logger -may be set to either the parent or the child. - -[source] ----- - package org.apache.logging; - - import org.apache.logging.log4j.LogManager; - import org.apache.logging.log4j.Logger; - import org.apache.logging.log4j.Marker; - - /** - * - */ - public class Child extends Parent { - - // The name of this Logge will be "org.apache.logging.Child" - public Logger childLogger = LogManager.getLogger(); - - public void childLog(Marker marker) { - childLogger.debug(marker,"Child logger message"); - } - - public void logFromChild(Marker marker) { - getLogger().debug(marker,"Log message from Child"); - } - - public void parentLog(Marker marker) { - parentLogger.debug(marker,"Parent logger, message from Child"); - } - } ----- - -The application exercises all the logging methods four times. The first two times the Logger in the base -class is set to the static Logger. The second two times the Logger in the base class is set to use the -Logger in the subclass. In the first and third invocation of each method a null Marker is passed. In the -second and fourth a Marker named "CLASS" is passed. - -[source] ----- - package org.apache.logging; - - import org.apache.logging.log4j.Marker; - import org.apache.logging.log4j.MarkerManager; - - public class App { - - public static void main( String[] args ) { - Marker marker = MarkerManager.getMarker("CLASS"); - Child child = new Child(); - - System.out.println("------- Parent Logger ----------"); - child.log(null); - child.log(marker); - child.logFromChild(null); - child.logFromChild(marker); - child.parentLog(null); - child.parentLog(marker); - - child.setLogger(child.childLogger); - - System.out.println("------- Parent Logger set to Child Logger ----------"); - child.log(null); - child.log(marker); - child.logFromChild(null); - child.logFromChild(marker); - } - } ----- - -The configuration takes advantage of Log4j's ability to select a pattern based upon attributes of the log event. -In this case %C, the class name pattern, is used when the CLASS Marker is present, and %c, the logger name -is used when the CLASS marker is not present. - -[source,xml] ----- - - - - - - - - - - - - - - - - - ----- - -The output below illustrates the difference between using the Logger name and the Class name in the pattern. All -the odd numbered items print the name of the logger (%c) while all the even numbered items print the -name of the class that called the logging method (%C). The numbers in the description of the outcomes in the -following list match the corresponding numbers shown in the output. - -1. Logging is performed in the parent class using the static logger with the Logger name pattern. The -logger name matches the name of the parent class. -2. Logging is performed in the parent class using the static logger with the Class name pattern. Although -the method was called against the Child instance it is implemented in Parent so that is what appears. -3. Logging is performed in Child using the logger in the parent, so the name of the parent is printed as the logger -name. -4. Logging is performed in Child using the logger in the parent. Since the method calling the logging -method is in Child that is the class name that appears. -5. Logging is performed in Child using the static logger in the parent, so the name of the parent is printed as the -logger name. -6. Logging is performed in Child using the static logger in the parent. Since the method calling the logging -method is in Child that is the class name that appears. -7. Logging is performed in the parent class using the logger of Child. The logger name matches the name of the child -and so it is printed. -8. Logging is performed in the parent class using the logger of the Child. Although the method was called against -the Child instance it is implemented in Parent so that is what appears as the class name. -9. Logging is performed in Child using the logger in the parent which is set to the child logger, so the name of the -child is printed as the logger name. -10. Logging is performed in Child using the logger in the parent, which is set to the child logger. Since -the method calling the logging method is in Child that is the class name that appears. - -[source] ----- - ------- Parent Logger ---------- - 1. Parent log message: Logger=org.apache.logging.Parent - 2. Parent log message: Class=org.apache.logging.Parent - 3. Log message from Child: Logger=org.apache.logging.Parent - 4. Log message from Child: Class=org.apache.logging.Child - 5. Parent logger, message from Child: Logger=org.apache.logging.Parent - 6. Parent logger, message from Child: Class=org.apache.logging.Child - ------- Parent Logger set to Child Logger ---------- - 7. Parent log message: Logger=org.apache.logging.Child - 8. Parent log message: Class=org.apache.logging.Parent - 9. Log message from Child: Logger=org.apache.logging.Child - 10. Log message from Child: Class=org.apache.logging.Child ----- - -In the example above there are two Loggers declared. One is static and one is non-static. When looking at -the results it is clear that the outcomes would be exactly the same regardless of whether how the loggers -are declared. The name of the logger will always originate from the class in which it is created and the -Class name in each log event will always reflect the Class from which the logging method was called. - -It should be noted that there is a substantial performance penalty for printing the location information -(class name, method name, and line number). If the method name and line number are not important it is -usually better to make sure that each class has its own Logger so the logger name accurately reflects the -class performing the logging. diff --git a/src/site/antora/modules/ROOT/partials/manual/api-best-practice-dont-use-string-concat.adoc b/src/site/antora/modules/ROOT/partials/manual/api-best-practice-dont-use-string-concat.adoc new file mode 100644 index 00000000000..5759d12dc94 --- /dev/null +++ b/src/site/antora/modules/ROOT/partials/manual/api-best-practice-dont-use-string-concat.adoc @@ -0,0 +1,36 @@ +//// + 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. +//// + +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); +---- diff --git a/src/site/antora/modules/ROOT/partials/manual/api-best-practice-dont-use-toString.adoc b/src/site/antora/modules/ROOT/partials/manual/api-best-practice-dont-use-toString.adoc new file mode 100644 index 00000000000..eada95008ca --- /dev/null +++ b/src/site/antora/modules/ROOT/partials/manual/api-best-practice-dont-use-toString.adoc @@ -0,0 +1,30 @@ +//// + 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. +//// + +* [ ] 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); +---- diff --git a/src/site/antora/modules/ROOT/partials/manual/api-best-practice-exception-as-last-argument.adoc b/src/site/antora/modules/ROOT/partials/manual/api-best-practice-exception-as-last-argument.adoc new file mode 100644 index 00000000000..eada95008ca --- /dev/null +++ b/src/site/antora/modules/ROOT/partials/manual/api-best-practice-exception-as-last-argument.adoc @@ -0,0 +1,30 @@ +//// + 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. +//// + +* [ ] 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); +---- diff --git a/src/site/antora/modules/ROOT/partials/manual/api-best-practice-use-supplier.adoc b/src/site/antora/modules/ROOT/partials/manual/api-best-practice-use-supplier.adoc new file mode 100644 index 00000000000..5156e248542 --- /dev/null +++ b/src/site/antora/modules/ROOT/partials/manual/api-best-practice-use-supplier.adoc @@ -0,0 +1,47 @@ +//// + 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. +//// + +If one or more arguments of the log statement are computationally expensive, it is not wise to evaluate them knowing that their results can be discarded. +Consider the following example: + +[source,java] +---- +/* BAD! */ LOGGER.info("failed for user ID `{}` and role `{}`", userId, db.findUserRoleById(userId)); +---- + +The database query (i.e., `db.findUserNameById(userId)`) can be a significant bottleneck if the created the log event will be discarded anyway – maybe the `INFO` level is not accepted for this logger, or due to some other filtering. + +* [ ] The old-school way of solving this problem is to level-guard the log statement: ++ +[source,java] +---- +/* OKAY */ if (LOGGER.isInfoEnabled()) { LOGGER.info(...); } +---- ++ +While this would work for cases where the message can be dropped due to insufficient level, this approach is still prone to other filtering cases; e.g., maybe the associated xref:manual/markers.adoc[marker] is not accepted. +* [x] Use ``Supplier``s to pass arguments containing computationally expensive items: ++ +[source,java] +---- +/* GOOD */ LOGGER.info("failed for user ID `{}` and role `{}`", () -> userId, () -> db.findUserRoleById(userId)); +---- +* [x] Use a `Supplier` to pass the message and its arguments containing computationally expensive items: ++ +[source,java] +---- +/* GOOD */ LOGGER.info(() -> new ParameterizedMessage("failed for user ID `{}` and role `{}`", userId, db.findUserRoleById(userId))); +---- diff --git a/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc b/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc new file mode 100644 index 00000000000..ba0921f305a --- /dev/null +++ b/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc @@ -0,0 +1,72 @@ +//// + 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. +//// + +To write logs, you need a `Logger` instance which you will retrieve from the `LogManager`. +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> 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** will be enriched with the log level (i.e., `WARN`), but also timestamp, class & method name, line number, and several other information. + +**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 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. +This is simply done by passing 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. diff --git a/src/site/resources/.htaccess b/src/site/resources/.htaccess index 62f3e811403..ee5a68abe50 100644 --- a/src/site/resources/.htaccess +++ b/src/site/resources/.htaccess @@ -18,5 +18,7 @@ # RewriteEngine On RewriteRule "^(/log4j/[23].x)/log4j-(core|api)/apidocs(.*)" "$1/javadoc/$2$3" [R=permanent] +RewriteRule "^(/log4j/[23].x)/manual/api-separation.html" "$1/manual/api.html" [R=permanent] RewriteRule "^(/log4j/[23].x)/manual/scala-api.html" "/log4j/scala" [R=permanent] +RewriteRule "^(/log4j/[23].x)/manual/usage.html" "$1/manual/api.html" [R=permanent] RewriteRule "^(/log4j/[23].x)/release-notes/index.html" "$1/release-notes.html" [R=permanent] From 5eb000713fc958fb395a47d54a04606d6e778a14 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 22 May 2024 15:56:09 +0200 Subject: [PATCH 02/11] Update `log4j-docgen` configuration --- pom.xml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index ac79f1bd05d..b090cd9168c 100644 --- a/pom.xml +++ b/pom.xml @@ -825,14 +825,18 @@ pre-site ${project.basedir}/src/docgen - - index.adoc.ftl - ${project.build.directory}/generated-site/antora/modules/ROOT/pages/plugin-reference.adoc - - - type.adoc.ftl - ${project.build.directory}/generated-site/antora/modules/ROOT/pages/_plugin-reference/%g-%a-%c.adoc - + + + index.adoc.ftl + ${project.build.directory}/generated-site/antora/modules/ROOT/pages/plugin-reference.adoc + + + + + type.adoc.ftl + ${project.build.directory}/generated-site/antora/modules/ROOT/pages/_plugin-reference/%g-%a-%c.adoc + + From 7a23eaae8a6b3313974eb1439645fe774a8d37b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Wed, 22 May 2024 16:53:37 +0200 Subject: [PATCH 03/11] Fix `api-best-practice-exception-as-last-argument.adoc` --- ...t-practice-exception-as-last-argument.adoc | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/site/antora/modules/ROOT/partials/manual/api-best-practice-exception-as-last-argument.adoc b/src/site/antora/modules/ROOT/partials/manual/api-best-practice-exception-as-last-argument.adoc index eada95008ca..bdb5f19709e 100644 --- a/src/site/antora/modules/ROOT/partials/manual/api-best-practice-exception-as-last-argument.adoc +++ b/src/site/antora/modules/ROOT/partials/manual/api-best-practice-exception-as-last-argument.adoc @@ -15,16 +15,35 @@ limitations under the License. //// -* [ ] Don't use `Object#toString()` in arguments, it is redundant! +* [ ] Don't call `Throwable#printStackTrace()`! +This not only circumvents the logging but can also leak sensitive information! + [source,java] ---- -/* BAD! */ LOGGER.info("userId: {}", userId.toString()); +/* BAD! */ exception.printStackTrace(); ---- -* [x] Underlying message type and layout will deal with arguments: +* [ ] Don't use `Throwable#getMessage()`! +This prevents the log event from getting enriched with the exception. + [source,java] ---- -/* GOOD */ LOGGER.info("userId: {}", userId); +/* 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); ---- From 88f2f5a32d9f494aefb2b2936c6ac243b63d29d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Wed, 22 May 2024 16:59:14 +0200 Subject: [PATCH 04/11] Provide Javadoc links in `api-intro.adoc` --- src/site/antora/modules/ROOT/partials/manual/api-intro.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc b/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc index ba0921f305a..278df9af557 100644 --- a/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc +++ b/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc @@ -15,7 +15,7 @@ limitations under the License. //// -To write logs, you need a `Logger` instance which you will retrieve from the `LogManager`. +To write logs, you need a link:../javadoc/log4j-api/org/apache/logging/log4j/Logger.html[`Logger`] instance which you will retrieve from the link:../javadoc/log4j-api/org/apache/logging/log4j/LogManager.html[`LogManager`]. 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. From f3191909cf4b418bbde8fd0637f03e93f2db6e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Thu, 23 May 2024 10:23:37 +0200 Subject: [PATCH 05/11] Create `Logger message factories` section --- .../antora/modules/ROOT/pages/manual/api.adoc | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/site/antora/modules/ROOT/pages/manual/api.adoc b/src/site/antora/modules/ROOT/pages/manual/api.adoc index ce3360844f9..50de5359ace 100644 --- a/src/site/antora/modules/ROOT/pages/manual/api.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/api.adoc @@ -115,7 +115,50 @@ public class LoggerNameTest { } ---- +[TIP] +==== **We suggest you to use `LogManager.getLogger()` without any arguments** since it delivers the same functionality with less characters and is not prone to copy-paste errors. +==== + +[#logger-message-factories] +=== Logger message factories + +Loggers translate + +[source,java] +---- +LOGGER.info("Hello, {}!", name) +---- + +calls to the appropriate canonical logging method: + +[source,java] +---- +LOGGER.log(Level.INFO, messageFactory.createMessage("Hello, {}!", new Object[]{name})); +---- + +Note that how `Hello, {}!` should be encoded given the `\{name}` array as argument completely depends on the link:../javadoc/log4j-api/org/apache/logging/log4j/message/MessageFactory.html[`MessageFactory`] employed. +Log4j allows users to customize this behaviour in several `getLogger()` methods of link:../javadoc/log4j-api/org/apache/logging/log4j/LogManager.html[`LogManager`]: + +[source,java] +---- +LogManager + .getLogger() // <1> + .info("Hello, {}!", name); // <2> + +LogManager + .getLogger(StringFormatterMessageFactory.INSTANCE) // <3> + .info("Hello, %s!", name); // <4> +---- +<1> Create a logger using the default message factory +<2> Use default parameter placeholders, that is, `{}` style +<3> Explicitly provide the message factory, that is, link:../javadoc/log4j-api/org/apache/logging/log4j/message/StringFormatterMessageFactory.html[`StringFormatterMessageFactory`]. +Note that there are several other `getLogger()` methods accepting a `MessageFactory`. +<4> Note the placeholder change from `{}` to `%s`! +Passed `Hello, %s!` and `name` arguments will be implicitly translated to a `String.format("Hello, %s!", name)` call due to the employed `StringFormatterMessageFactory`. + +Log4j bundles several xref:manual/messages.adoc[predefined message factories]. +Some common ones are accessible through convenient factory methods, which we will cover below. [#formatter-logger] === Formatter logger From fde6c0dfc00e8c087901f96223fe89f4ae3f3b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Thu, 23 May 2024 10:41:00 +0200 Subject: [PATCH 06/11] Highlight difference between log event and log message --- src/site/antora/modules/ROOT/partials/manual/api-intro.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc b/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc index 278df9af557..17e77a40ac6 100644 --- a/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc +++ b/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc @@ -40,7 +40,7 @@ public class DbTableService { 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** will be enriched with the log level (i.e., `WARN`), but also timestamp, class & method name, line number, and several other information. +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. From 1bb91cc860c4bdcdc6ea1d6f23847ce333b85787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Thu, 23 May 2024 13:02:02 +0200 Subject: [PATCH 07/11] Remove external references on Log4j's popularity, they tend to get invalidated in time --- src/site/antora/modules/ROOT/pages/index.adoc | 3 +-- src/site/antora/modules/ROOT/pages/manual/index.adoc | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/site/antora/modules/ROOT/pages/index.adoc b/src/site/antora/modules/ROOT/pages/index.adoc index e06af884540..a1d72e37184 100644 --- a/src/site/antora/modules/ROOT/pages/index.adoc +++ b/src/site/antora/modules/ROOT/pages/index.adoc @@ -18,8 +18,7 @@ = Apache Log4j Apache Log4j is a versatile, industrial-grade Java logging framework composed of an API, its implementation, and components to assist the deployment for various use cases. -Log4j is https://security.googleblog.com/2021/12/apache-log4j-vulnerability.html[used by 8% of the Maven ecosystem] and listed as one of https://docs.google.com/spreadsheets/d/1ONZ4qeMq8xmeCHX03lIgIYE4MEXVfVL6oj05lbuXTDM/edit#gid=1024997528[the top 100 critical open source software projects]. -The project is actively maintained by a {logging-services-url}/team-list.html[team] of several volunteers and {logging-services-url}/support[support]ed by a big community. +The project is actively maintained by a {logging-services-url}/team-list.html[team] of volunteers and {logging-services-url}/support[support]ed by a big community. [#shortcuts] == Shortcuts diff --git a/src/site/antora/modules/ROOT/pages/manual/index.adoc b/src/site/antora/modules/ROOT/pages/manual/index.adoc index 590bc6e5f86..01a80cec8b0 100644 --- a/src/site/antora/modules/ROOT/pages/manual/index.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/index.adoc @@ -19,8 +19,7 @@ == Welcome to Apache Log4j! Apache Log4j is a versatile, industrial-grade Java logging framework composed of an API, its implementation, and components to assist the deployment for various use cases. -Log4j is https://security.googleblog.com/2021/12/apache-log4j-vulnerability.html[used by 8% of the Maven ecosystem] and listed as one of https://docs.google.com/spreadsheets/d/1ONZ4qeMq8xmeCHX03lIgIYE4MEXVfVL6oj05lbuXTDM/edit#gid=1024997528[the top 100 critical open source software projects]. -The project is actively maintained by a {logging-services-url}/team-list.html[team] of several volunteers and {logging-services-url}/support[support]ed by a big community. +The project is actively maintained by a {logging-services-url}/team-list.html[team] of volunteers and {logging-services-url}/support[support]ed by a big community. Logging is an essential part of the software development process. It provides a way to track the flow of execution in a program, allowing developers From ebf0f04678bfdb97f1bf6537ac02628556cf3d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Thu, 23 May 2024 13:46:30 +0200 Subject: [PATCH 08/11] Improve `lo4j-features.adoc` as requested here[1] [1] https://github.com/apache/logging-log4j2/pull/2581#discussion_r1609800039 --- .../modules/ROOT/partials/log4j-features.adoc | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/site/antora/modules/ROOT/partials/log4j-features.adoc b/src/site/antora/modules/ROOT/partials/log4j-features.adoc index f7289e93fe4..bd1bedced07 100644 --- a/src/site/antora/modules/ROOT/partials/log4j-features.adoc +++ b/src/site/antora/modules/ROOT/partials/log4j-features.adoc @@ -22,26 +22,25 @@ Log4j bundles a rich set of components to assist various use cases. * xref:manual/filters.adoc[Filters] based on log event rates, regular expressions, scripts, time, etc. * xref:manual/lookups.adoc[Lookups] for accessing system properties, environment variables, log event fields, etc. -API separation:: -The API for Log4j (i.e., `log4j-api`) is separate from the implementation (i.e., `log4j-core`), making it straightforward for application developers which classes and methods to use while ensuring forward compatibility. -(See xref:manual/api-separation.adoc[API Separation] for details.) -The Log4j API also provides the most feature-rich logging facade in the market; support for various `Message` types (such as `Object` or `Map`) besides plain `String`, lambda expressions, parametrized logging, markers, levels, diagnostic contexts (aka. MDC/NDC), etc. -Check out the xref:manual/api.adoc[Java API], {logging-services-url}/log4j/kotlin[Kotlin API], and {logging-services-url}/log4j/scala[Scala API] pages for further information. - -No vendor lock-in:: -Even though Log4j implements the Log4j API at its fullest, users can use another logging backend. -This can be achieved by either using another backend implementing the Log4j API or forwarding Log4j API calls to another logging facade (e.g., SLF4J) and using a backend for that particular facade. - Reliability:: Log4j is built with solid reliability in mind. It can automatically reload its configuration upon modification and will do so without losing log events while reconfiguration occurs. Performance:: When configured correctly, Log4j can deliver excelling performance without almost any burden on the Java garbage collector, and it will do so without sacrificing reliability. -This is made possible via an asynchronous logger founded on the https://lmax-exchange.github.io/disruptor/[LMAX Disruptor] technology (having its roots in the demanding industry of financial trading) and the garbage-free features baked at hot paths. Check out the xref:manual/performance.adoc[Performance] page for details. Extensibility:: Log4j contains a fully-fledged xref:manual/plugins.adoc[plugin support] that users can leverage to extend functionality. -You can easily add your components (layouts, appenders, filters, etc.) or customize existing ones (e.g., adding new directives to the xref:manual/layouts.adoc#PatternLayout[Pattern] or xref:manual/json-template-layout.adoc#extending[JSON Template Layout]). +You can easily add your components (layouts, appenders, filters, etc.) or customize existing ones (e.g., adding new directives to xref:manual/extending.adoc#PatternConverters[Pattern Layout] or xref:manual/json-template-layout.adoc#extending[JSON Template Layout]). Check out the xref:manual/extending.adoc[Extending Log4j] page. + +Powerful API:: +Log4j is a logging system where the API (called Log4j API) and its implementation (called Log4j Core) is distinctly separate from each other. +xref:manual/api.adoc[Log4j API] provides the most feature-rich logging facade in the market; support for various `Message` types (such as `Object` or `Map`) besides plain `String`, lambda expressions, parameterized logging, markers, levels, diagnostic contexts (aka. MDC/NDC), etc. +Log4j team takes backward compatibility very seriously and makes sure people relying on Log4j API gets a logging facade that is straightforward to use in a correct and future-proof way. +Check out the xref:manual/api.adoc[Java API], {logging-services-url}/log4j/kotlin[Kotlin API], and {logging-services-url}/log4j/scala[Scala API] pages for further information. + +No vendor lock-in:: +Log4j API is a generic logging facade for various logging frameworks. +While Log4j Core implements it at its fullest, you can easily switch to other implementations such as Logback or JUL (`java.util.logging`). From cfc76c8c8566c9945ab7859cf5493b71c57a18c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Thu, 23 May 2024 13:56:41 +0200 Subject: [PATCH 09/11] Mention about MDC & NDC --- src/site/antora/modules/ROOT/pages/manual/api.adoc | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/site/antora/modules/ROOT/pages/manual/api.adoc b/src/site/antora/modules/ROOT/pages/manual/api.adoc index 50de5359ace..cec0fc80a66 100644 --- a/src/site/antora/modules/ROOT/pages/manual/api.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/api.adoc @@ -277,7 +277,7 @@ xref:manual/markers.adoc[Read more on markers...] [#scoped-context] === Scoped Context -Just like a https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/ScopedValue.html[Java's `ScopedValue`], the visibility of tags are associated with the block they were introduced: +Just like a https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/ScopedValue.html[Java's `ScopedValue`], in Scoped Context, the visibility of tags are associated with the block they were introduced: [source,java] ---- @@ -311,7 +311,8 @@ xref:manual/scoped-context.adoc[Read more on Scoped Context...] [#thread-context] === Thread Context -Just like https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html[Java's `ThreadLocal`], the visibility of tags are associated with the thread they were introduced: +Just like https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html[Java's `ThreadLocal`], in Thread Context, the visibility of tags are associated with the thread they were introduced. +Thread Context offers both a map-structured, called _Mapped Diagnostic Context (MDC)_, and a stack-structured, called _Nested Diagnostic Context (NDC)_, storage: [source,java] ---- @@ -319,10 +320,13 @@ ThreadContext.put("ipAddress", request.getRemoteAddr()); // <1> ThreadContext.put("hostName", request.getServerName()); // <1> ThreadContext.put("loginId", session.getAttribute("loginId")); // <1> -LOGGER.debug("Performing work"); +ThreadContext.push("performWork()"); // <2> + +LOGGER.debug("Performing work"); // <3> ---- -<1> Associating properties **with the thread**. -These properties can later on be used to, for instance, filter the log event, provide extra information in the layout, etc. +<1> Associating properties with the logging context map of the thread +<2> Pushing properties to the logging context stack of the thread +<3> Added properties can later on be used to, for instance, filter the log event, provide extra information in the layout, etc. [CAUTION] ==== From d945a12ef16151d21cb11fe72df6e400d7831100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Thu, 23 May 2024 16:19:12 +0200 Subject: [PATCH 10/11] Improve level-related docs --- antora-playbook.yaml | 3 + src/site/antora/modules/ROOT/nav.adoc | 6 +- src/site/antora/modules/ROOT/pages/5min.adoc | 8 +- .../ROOT/pages/manual/customloglevels.adoc | 352 +++++------------- .../modules/ROOT/pages/manual/migration.adoc | 2 +- .../ROOT/partials/manual/api-intro.adoc | 2 +- 6 files changed, 110 insertions(+), 263 deletions(-) diff --git a/antora-playbook.yaml b/antora-playbook.yaml index 52f1d254bc1..00cf205153a 100644 --- a/antora-playbook.yaml +++ b/antora-playbook.yaml @@ -90,6 +90,9 @@ ui: .doc .colist>table code, .doc p code, .doc thead code { font-size: 0.8em; } .doc pre { font-size: 0.7rem; } .doc .colist { font-size: 0.75rem; } + /* Make links more visible: */ + .doc a { text-decoration: underline; } + .doc a code { text-decoration: underline; color: #1565c0; } /* Tab header fonts aren't rendered good, adjusting the font weight: */ .tablist > ul li { font-weight: 500; } /* `page-toclevels` greater than 4 are not supported by Antora UI, patching it: */ diff --git a/src/site/antora/modules/ROOT/nav.adoc b/src/site/antora/modules/ROOT/nav.adoc index 6fde30e7cde..ab8ad43af7d 100644 --- a/src/site/antora/modules/ROOT/nav.adoc +++ b/src/site/antora/modules/ROOT/nav.adoc @@ -25,13 +25,15 @@ .Resources * xref:faq.adoc[F.A.Q.] * xref:5min.adoc[] +* xref:manual/migration.adoc[] +* xref:manual/cloud.adoc[] * xref:development.adoc[] .xref:manual/index.adoc[] * xref:manual/installation.adoc[] * xref:manual/architecture.adoc[] -* xref:manual/migration.adoc[] * xref:manual/api.adoc[API] +** xref:manual/customloglevels.adoc[] ** xref:manual/logbuilder.adoc[] ** xref:manual/markers.adoc[] ** xref:manual/scoped-context.adoc[] @@ -47,10 +49,8 @@ ** xref:manual/layouts.adoc[] *** xref:manual/json-template-layout.adoc[] ** xref:manual/lookups.adoc[] -** xref:manual/customloglevels.adoc[] ** xref:manual/filters.adoc[] ** xref:manual/scripts.adoc[] -* xref:manual/cloud.adoc[] * xref:manual/extending.adoc[] ** xref:manual/customconfig.adoc[] * xref:manual/plugins.adoc[] diff --git a/src/site/antora/modules/ROOT/pages/5min.adoc b/src/site/antora/modules/ROOT/pages/5min.adoc index ec24c35776c..9271a8c43e5 100644 --- a/src/site/antora/modules/ROOT/pages/5min.adoc +++ b/src/site/antora/modules/ROOT/pages/5min.adoc @@ -264,7 +264,7 @@ Save the following XML document to `src/**main**/resources/log4j2.xml`. https://logging.apache.org/xml/ns/log4j-config-2.xsd"> - + @@ -272,7 +272,7 @@ Save the following XML document to `src/**main**/resources/log4j2.xml`. - + @@ -432,7 +432,7 @@ Save the following XML document to `src/**test**/resources/log4j2-test.xml`. https://logging.apache.org/xml/ns/log4j-config-2.xsd"> - + @@ -440,7 +440,7 @@ Save the following XML document to `src/**test**/resources/log4j2-test.xml`. - + diff --git a/src/site/antora/modules/ROOT/pages/manual/customloglevels.adoc b/src/site/antora/modules/ROOT/pages/manual/customloglevels.adoc index 8f15912e375..b19fbbef144 100644 --- a/src/site/antora/modules/ROOT/pages/manual/customloglevels.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/customloglevels.adoc @@ -16,47 +16,15 @@ //// = Levels -[[top]] - -[#DefiningLevelsInCode] -== Defining Custom Log Levels in Code - -Log4j 2 supports custom log levels. Custom log levels can be defined in -code or in configuration. To define a custom log level in code, use the -`Level.forName()` method. This method creates a new level for the -specified name. After a log level is defined you can log messages at -this level by calling the `Logger.log()` method and passing the custom log -level: - -[source,java] ----- -// This creates the "VERBOSE" level if it does not exist yet. -final Level VERBOSE = Level.forName("VERBOSE", 550); - -final Logger logger = LogManager.getLogger(); -logger.log(VERBOSE, "a verbose message"); // use the custom VERBOSE level - -// Create and use a new custom level "DIAG". -logger.log(Level.forName("DIAG", 350), "a diagnostic message"); +Log levels are used to categorize log events by severity and control the verbosity of the logs. +They are one of many xref:manual/api.adoc#fish-tagging[_fish tagging_ capabilities provided by Log4j API]. +Using levels, you can filter out less important logs and focus on the most critical ones. -// Use (don't create) the "DIAG" custom level. -// Only do this *after* the custom level is created! -logger.log(Level.getLevel("DIAG"), "another diagnostic message"); +Log4j contains following predefined levels: -// Using an undefined level results in an error: Level.getLevel() returns null, -// and logger.log(null, "message") throws an exception. -logger.log(Level.getLevel("FORGOT_TO_DEFINE"), "some message"); // throws exception! ----- - -When defining a custom log level, the `intLevel` parameter (550 and 350 -in the example above) determines where the custom level exists in -relation to the standard levels built-in to Log4j 2. For reference, the -table below shows the `intLevel` of the built-in log levels. - -.Standard log levels built-in to Log4j -[cols=",>",options="header"] -|======================== -|Standard Level |intLevel +[%header,cols="1m,1"] +|=== +|Name |Priority |OFF |0 |FATAL |100 |ERROR |200 @@ -65,258 +33,134 @@ table below shows the `intLevel` of the built-in log levels. |DEBUG |500 |TRACE |600 |ALL |`Integer.MAX_VALUE` -|======================== - -[#DefiningLevelsInConfiguration] -== Defining Custom Log Levels in Configuration +|=== -Custom log levels can also be defined in configuration. This is -convenient for using a custom level in a logger filter or an appender -filter. Similar to defining log levels in code, a custom level must be -defined first, before it can be used. If a logger or appender is -configured with an undefined level, that logger or appender will be -invalid and will not process any log events. +A level is composed of a case-sensitive name and a *priority* (of type `int`), which is used to define an order while comparing two. +Priority can be used in several contexts to express a filtering capability, for instance: -The *CustomLevel* configuration element creates a custom level. -Internally it calls the same `Level.forName()` method discussed above. +* `WARN` is _less severe_ than `ERROR` +* `WARN` is _more specific_ than `ERROR` -.CustomLevel Parameters -[cols="m,,4",options="header"] -|=== -|Parameter Name |Type |Description +The entry point to log levels are through link:../javadoc/log4j-api/org/apache/logging/log4j/Level.html[`Level`]. +Predefined levels are available for Log4j API integrators through link:../javadoc/log4j-api/org/apache/logging/log4j/spi/StandardLevel.html[`StandardLevel`]. -|name -|String -|The name of the custom level. Note that level names are -case sensitive. The convention is to use all upper-case names. +[#usage] +== [[StandardLoggerInterface]] Usage -|intLevel -|integer -|Determines where the custom level exists in relation -to the standard levels built-in to Log4j 2 (see the table above). -|=== +You can specify the log level while using `Logger` in several ways: -The following example shows a configuration that defines some custom log -levels and uses a custom log level to filter log events sent to the -console. +[source,java] +---- +LOGGER.info("Hello, {}!", userName); // <1> +LOGGER.log(Level.INFO, "Hello, {}!", userName); // <2> +---- +<1> Using `Logger#info()` to log a message at `INFO` level +<2> Using `Logger#log()` to log a message and specifying the `INFO` level explicitly +There are several ways levels can be employed in the Log4j configuration file for filtering purposes. +Filtering on levels for loggers is a pretty common example: [source,xml] ---- - - - - - - - - - - - + + + + + - - - - - - - - - - - - ----- + -[#StandardLoggerInterface] -== Convenience Methods for the Built-in Log Levels + + + + + + -The built-in log levels have a set of convenience methods on the Logger -interface that makes them easier to use. For example, the Logger -interface has 24 `debug()` methods that support the DEBUG level: - -[source,java] ----- -// convenience methods for the built-in DEBUG level -debug(Marker, Message) -debug(Marker, Message, Throwable) -debug(Marker, Object) -debug(Marker, Object, Throwable) -debug(Marker, String) -debug(Marker, String, Object...) -debug(Marker, String, Throwable) -debug(Message) -debug(Message, Throwable) -debug(Object) -debug(Object, Throwable) -debug(String) -debug(String, Object...) -debug(String, Throwable) -// lambda support methods added in 2.4 -debug(Marker, MessageSupplier) -debug(Marker, MessageSupplier, Throwable) -debug(Marker, String, Supplier...) -debug(Marker, Supplier) -debug(Marker, Supplier, Throwable) -debug(MessageSupplier) -debug(MessageSupplier, Throwable) -debug(String, Supplier...) -debug(Supplier) -debug(Supplier, Throwable) + ---- +<1> Logs of level `INFO` or higher severity (i.e., `WARN`, `ERROR`, `FATAL`) are allowed for `com.mycompany` package +<2> Logs of level `ERROR` or higher severity (i.e., `FATAL`) are allowed for the rest -Similar methods exist for the other built-in levels. Custom levels, in -contrast, need to pass in the log level as an extra parameter. +[[top]] -[source,java] ----- -// need to pass the custom level as a parameter -logger.log(VERBOSE, "a verbose message"); -logger.log(Level.forName("DIAG", 350), "another message"); ----- +[#DefiningLevelsInCode] +== Defining custom log levels programmatically -It would be nice to have the same ease of use with custom levels, so -that after declaring the custom VERBOSE/DIAG levels, we could use code -like this: +Users can programmatically define custom levels using link:../javadoc/log4j-api/org/apache/logging/log4j/Level.html#forName(java.lang.String,int)[the `Level.forName()` method]: [source,java] ---- -// nice to have: descriptive methods and no need to pass the level as a parameter -logger.verbose("a verbose message"); -logger.diag("another message"); -logger.diag("java 8 lambda expression: {}", () -> someMethod()); ----- - -The standard Logger interface cannot provide convenience methods for -custom levels, but the next few sections introduce a code generation -tool to create loggers that aim to make custom levels as easy to use as -built-in levels. - -[#AddingOrReplacingLevels] -== Adding or Replacing Log Levels - -We assume that most users want to _add_ custom level methods to the -Logger interface, in addition to the existing `trace()`, `debug()`, `info()`, -... methods for the built-in log levels. - -There is another use case, Domain Specific Language loggers, where we -want to _replace_ the existing `trace()`, `debug()`, `info()`, ... methods -with all-custom methods. - -For example, for medical devices we could have only `critical()`, -`warning()`, and `advisory()` methods. Another example could be a game -that has only `defcon1()`, `defcon2()`, and `defcon3()` levels. - -If it were possible to hide existing log levels, users could customize -the Logger interface to match their requirements. Some people may not -want to have a FATAL or a TRACE level, for example. They would like to -be able to create a custom Logger that only has `debug()`, `info()`, `warn()` -and `error()` methods. - -[#CustomLoggers] -== Generating Source Code for a Custom Logger Wrapper +public final class CustomLogLevel { -Common Log4j usage is to get an instance of the `Logger` interface from -the `LogManager` and call the methods on this interface. However, the -custom log Levels are not known in advance, so Log4j cannot provide an -interface with convenience methods for these custom log Levels. + public static final Level VERBOSE = Level.forName("VERBOSE", 550); // <1> -To solve this, Log4j ships with a tool that generates source code for a -Logger wrapper. The generated wrapper class has convenience methods for -each custom log level, making custom levels just as easy to use as the -built-in levels. - -There are two flavors of wrappers: ones that _*extend*_ the Logger API -(adding methods to the built-in levels) and ones that _*customize*_ the -Logger API (replacing the built-in methods). - -When generating the source code for a wrapper class, you need to -specify: - -* the fully qualified name of the class to generate -* the list of custom levels to support and their `intLevel` relative -strength -* whether to extend `Logger` (and keep the existing built-in methods) or -have only methods for the custom log levels - -You would then include the generated source code in the project where -you want to use custom log levels. - -[#ExampleUsage] -== Example Usage of a Generated Logger Wrapper +} +---- +<1> Creating a custom level with name `VERBOSE` and priority 550 -Here is an example of how one would use a generated logger wrapper with -custom levels DIAG, NOTICE and VERBOSE: +Once defined, you can log messages at this level by calling the `Logger#log()` method and passing the custom log level: [source,java] ---- -// ExtLogger is a generated logger wrapper -import com.mycompany.myproject.ExtLogger; +public class PurchaseOrder { -public class MyService { - // instead of Logger logger = LogManager.getLogger(MyService.class): - private static final ExtLogger logger = ExtLogger.create(MyService.class); + private static final Logger LOGGER = LogManager.getLogger(); - public void demoExtendedLogger() { - // ... - logger.trace("the built-in TRACE level"); - logger.verbose("a custom level: a VERBOSE message"); - logger.debug("the built-in DEBUG level"); - logger.notice("a custom level: a NOTICE message"); - logger.info("the built-in INFO level"); - logger.diag("a custom level: a DIAG message"); - logger.warn("the built-in WARN level"); - logger.error("the built-in ERROR level"); - logger.fatal("the built-in FATAL level"); - logger.notice("java 8 lambda expression only executed if NOTICE is enabled: {}", () -> someMethod()); + public PurchaseOrder(String id) { + LOGGER.log(CustomLogLevel.VERBOSE, "Creating purchase order with ID `{}`", id); // <1> // ... } - ... -} ----- -[#CodeGen] -== Generating Extended Loggers + // ... -Use the following command to generate a logger wrapper that adds methods -to the built-in ones: - -[source,sh,subs="attributes"] ----- -java -cp log4j-core-{log4j-core-version}.jar org.apache.logging.log4j.core.tools.ExtendedLoggerGenerator \ - com.mycomp.ExtLogger DIAG=350 NOTICE=450 VERBOSE=550 > com/mycomp/ExtLogger.java +} ---- +<1> Logging with the created custom level -This will generate source code for a logger wrapper that has the -convenience methods for the built-in levels _as well as_ the specified -custom levels. The tool prints the generated source code to the console. -By appending " > _filename_" the output can be redirected to a file. - -NOTE: Prior to log4j-2.9, this tool was an inner class -`Generate$ExtendedLogger`. + -Under the bash shell on Unix/Mac/Linux the dollar character $ needs to -be escaped, so the class name should be between single quotes -'org.apache.logging.log4j.core.tools.Generate$ExtendedLogger’. +[#DefiningLevelsInConfiguration] +== Defining custom log levels in configuration -== Generating Custom Loggers +Similar to defining log levels programmatically, a custom level must be defined first, before it can be used in a configuration file. +To facilitate this, the `CustomLevel` configuration element is used to define a custom level. +Internally it calls the same `Level.forName()` method discussed in xref:#DefiningLevelsInCode[]. -Use the following command to generate a logger wrapper that hides the -built-in levels and has only custom levels: +The following example shows a configuration that defines the `VERBOSE` custom log level and uses it to filter log events sent to the console. -[source,sh,subs="attributes"] ----- -java -cp log4j-core-{log4j-core-version}.jar org.apache.logging.log4j.core.tools.CustomLoggerGenerator \ - com.mycomp.MyLogger DEFCON1=350 DEFCON2=450 DEFCON3=550 > com/mycomp/MyLogger.java +[source,xml] ---- + + + + + + + + + + + + + + + -This will generate source code for a logger wrapper that _only_ has -convenience methods for the specified custom levels, _not_ for the -built-in levels. The tool prints the generated source code to the -console. By appending " > _filename_" the output can be redirected to a -file. + + + + + + -NOTE: Prior to log4j-2.9, this tool was an inner class `Generate$ExtendedLogger`. -Under the bash shell on Unix/Mac/Linux the dollar character $ needs to -be escaped, so the class name should be between single quotes -'org.apache.logging.log4j.core.tools.Generate$CustomLogger’. + +---- +<1> Defining the `VERBOSE` custom log level +<2> Only events of `VERBOSE` level or higher severity are sent to the console diff --git a/src/site/antora/modules/ROOT/pages/manual/migration.adoc b/src/site/antora/modules/ROOT/pages/manual/migration.adoc index 6bb50d23060..82ebbad046a 100644 --- a/src/site/antora/modules/ROOT/pages/manual/migration.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/migration.adoc @@ -15,7 +15,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more limitations under the License. //// -= Migrating from Log4j 1.x to 2.x += Migrating from Log4j 1 http://logging.apache.org/log4j/1.2/[Log4j 1.x] has https://blogs.apache.org/foundation/entry/apache_logging_services_project_announces[reached End of Life] in 2015 and is no longer supported. diff --git a/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc b/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc index 17e77a40ac6..20aa521c549 100644 --- a/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc +++ b/src/site/antora/modules/ROOT/partials/manual/api-intro.adoc @@ -46,7 +46,7 @@ The _generated_ **log event**, which contain the _user-provided_ **log message** 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 knows various levels, but the most common are `DEBUG`, `INFO`, `WARN`, and `ERROR`. +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`. From b9236e557c3b4338b9b2bf48999ed55f7623a163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Fri, 24 May 2024 09:58:25 +0200 Subject: [PATCH 11/11] Minor fixes --- .../manual/markers/MarkerExample.java | 8 +-- .../manual/messages/MessagesExample.java | 25 ++++---- .../StringBuilderFormattableExample.java | 4 +- src/site/antora/modules/ROOT/nav.adoc | 9 ++- .../ROOT/pages/manual/eventlogging.adoc | 2 +- .../modules/ROOT/pages/manual/messages.adoc | 60 +++++++++---------- .../ROOT/pages/manual/resource-logger.adoc | 2 +- 7 files changed, 54 insertions(+), 56 deletions(-) diff --git a/src/site/antora/modules/ROOT/examples/manual/markers/MarkerExample.java b/src/site/antora/modules/ROOT/examples/manual/markers/MarkerExample.java index ef3f30c173d..e08cd9b09b9 100644 --- a/src/site/antora/modules/ROOT/examples/manual/markers/MarkerExample.java +++ b/src/site/antora/modules/ROOT/examples/manual/markers/MarkerExample.java @@ -23,7 +23,7 @@ public final class MarkerExample { - private static final Logger logger = LogManager.getLogger("example.MarkerExample"); + private static final Logger LOGGER = LogManager.getLogger("example.MarkerExample"); // tag::create-marker[] private static final Marker SQL_MARKER = MarkerManager.getMarker("SQL"); // end::create-marker[] @@ -43,21 +43,21 @@ public static void main(final String[] args) { public static void doQuery(String table) { // Do business logic here // tag::use-marker[] - logger.debug(SQL_MARKER, "SELECT * FROM {}", table); + LOGGER.debug(SQL_MARKER, "SELECT * FROM {}", table); // end::use-marker[] } public static void doQueryParent(String table) { // Do business logic here // tag::use-marker-parent[] - logger.debug(QUERY_MARKER, "SELECT * FROM {}", table); + LOGGER.debug(QUERY_MARKER, "SELECT * FROM {}", table); // end::use-marker-parent[] } public static void doUpdate(String table, String column, String value) { // Do business logic here // tag::use-marker-parent[] - logger.debug(UPDATE_MARKER, "UPDATE {} SET {} = {}", table, column, value); + LOGGER.debug(UPDATE_MARKER, "UPDATE {} SET {} = {}", table, column, value); // end::use-marker-parent[] } } diff --git a/src/site/antora/modules/ROOT/examples/manual/messages/MessagesExample.java b/src/site/antora/modules/ROOT/examples/manual/messages/MessagesExample.java index a25bb0791cc..ee0230f8a76 100644 --- a/src/site/antora/modules/ROOT/examples/manual/messages/MessagesExample.java +++ b/src/site/antora/modules/ROOT/examples/manual/messages/MessagesExample.java @@ -24,42 +24,43 @@ import org.apache.logging.log4j.message.SimpleMessage; public class MessagesExample { - private static final Logger logger = LogManager.getLogger(); + + private static final Logger LOGGER = LogManager.getLogger(); public static void main(String[] args) { - Throwable e = new RuntimeException(); - doLogSimple(e); - doLogParameterized("foo", "bar", e); + Throwable exception = new RuntimeException(); + doLogSimple(exception); + doLogParameterized("foo", "bar", exception); InetSocketAddress socketAddress = InetSocketAddress.createUnresolved("192.0.2.17", 1234); LoginFailureEvent event = new LoginFailureEvent("root", socketAddress); failedLogin(event); } - private static void doLogSimple(Throwable e) { + private static void doLogSimple(Throwable exception) { // tag::simple[] - logger.error("Houston, we have a problem.", e); - logger.error(new SimpleMessage("Houston, we have a problem."), e); + LOGGER.error("Houston, we have a problem.", exception); + LOGGER.error(new SimpleMessage("Houston, we have a problem."), exception); // end::simple[] } - private static void doLogParameterized(String action, String cause, Throwable e) { + private static void doLogParameterized(String userId, Throwable exception) { // tag::parameterized[] - logger.error("Unable to {}, because {} occurred", action, cause, e); - logger.error(new ParameterizedMessage("Unable to {}, because {} occurred", action, cause), e); + LOGGER.error("Unable process user with ID `{}`", userId, exception); + LOGGER.error(new ParameterizedMessage("Unable process user with ID `{}`", userId), exception); // end::parameterized[] } private static void failedLogin(LoginFailureEvent event) { // tag::complex[] - logger.info( + LOGGER.info( "Connection closed by authenticating user {} {} port {} [preauth]", event.userName(), event.remoteAddress().getHostName(), event.remoteAddress().getPort()); // end::complex[] // tag::complex-message[] - logger.info(event); + LOGGER.info(event); // end::complex-message[] } diff --git a/src/site/antora/modules/ROOT/examples/manual/messages/StringBuilderFormattableExample.java b/src/site/antora/modules/ROOT/examples/manual/messages/StringBuilderFormattableExample.java index 20d522ee385..da83ada7f26 100644 --- a/src/site/antora/modules/ROOT/examples/manual/messages/StringBuilderFormattableExample.java +++ b/src/site/antora/modules/ROOT/examples/manual/messages/StringBuilderFormattableExample.java @@ -23,7 +23,7 @@ import org.apache.logging.log4j.util.StringBuilderFormattable; public class StringBuilderFormattableExample { - private static final Logger logger = LogManager.getLogger(); + private static final Logger LOGGER = LogManager.getLogger(); public static void main(String[] args) { InetSocketAddress socketAddress = InetSocketAddress.createUnresolved("192.0.2.17", 1234); @@ -32,7 +32,7 @@ public static void main(String[] args) { } private static void failedLogin(LoginFailureEvent event) { - logger.info(event); + LOGGER.info(event); } // tag::loginFailure[] diff --git a/src/site/antora/modules/ROOT/nav.adoc b/src/site/antora/modules/ROOT/nav.adoc index ab8ad43af7d..e82c69c6c70 100644 --- a/src/site/antora/modules/ROOT/nav.adoc +++ b/src/site/antora/modules/ROOT/nav.adoc @@ -45,17 +45,16 @@ * Configuration ** xref:manual/configuration.adoc[Configuration file] ** xref:manual/systemproperties.adoc[] +** xref:manual/customconfig.adoc[] ** xref:manual/appenders.adoc[] ** xref:manual/layouts.adoc[] *** xref:manual/json-template-layout.adoc[] ** xref:manual/lookups.adoc[] ** xref:manual/filters.adoc[] ** xref:manual/scripts.adoc[] -* xref:manual/extending.adoc[] -** xref:manual/customconfig.adoc[] -* xref:manual/plugins.adoc[] -* xref:manual/customconfig.adoc[] -* xref:manual/jmx.adoc[] +** xref:manual/jmx.adoc[] +* xref:manual/extending.adoc[Extending] +** xref:manual/plugins.adoc[] * xref:manual/performance.adoc[] ** xref:manual/async.adoc[] ** xref:manual/garbagefree.adoc[] diff --git a/src/site/antora/modules/ROOT/pages/manual/eventlogging.adoc b/src/site/antora/modules/ROOT/pages/manual/eventlogging.adoc index ede1bc39017..707effd69e2 100644 --- a/src/site/antora/modules/ROOT/pages/manual/eventlogging.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/eventlogging.adoc @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. //// -= Event Logging with Log4j += Event Logging The `EventLogger` class provides a mechanism for logging significant events in an application using structured data. This approach is beneficial for diff --git a/src/site/antora/modules/ROOT/pages/manual/messages.adoc b/src/site/antora/modules/ROOT/pages/manual/messages.adoc index 7db63200fa2..742e1e286ee 100644 --- a/src/site/antora/modules/ROOT/pages/manual/messages.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/messages.adoc @@ -36,7 +36,7 @@ The most important are the log level and the **log message**, which is a descrip Log4j API provides a rich set of methods to specify log messages, which will be described in this chapter. . Some fields are contextual and is either provided explicitly by developers of other parts of the application (see -xref:manual/thread-context.adoc[Thread context]) +xref:manual/thread-context.adoc[Thread Context]) or is injected by Java instrumentation. . The last category of fields is those that are computed automatically by the logging backend you use. @@ -160,27 +160,26 @@ This method will be called during event construction so that the Message has the name of the Logger used to log the event when Log4j formats the message. [#MapMessage] -=== MapMessage +=== `MapMessage` -A `MapMessage` contains a Map of String keys and values. +A `MapMessage` contains a `Map` of `String`-typed keys and values. `MapMessage` implements `FormattedMessage` and accepts the following format specifiers: -- "XML" - format the Map as XML -- "JSON" - format the Map as JSON -- "JAVA" - format the Map as a Java object +`XML`:: format the map as XML +`JSON`:: format the map as JSON +`JAVA`:: format the map as a Java object -Otherwise, it will format the Map as documented in -https://docs.oracle.com/javase/7/docs/api/java/util/AbstractMap.html#toString()[`java.util.AbstractMap.toString()`]. +Otherwise, it will format the map as documented in https://docs.oracle.com/javase/7/docs/api/java/util/AbstractMap.html#toString()[`java.util.AbstractMap.toString()`]. Some appenders convert the `MapMessage` objects differently when there is no layout: * JMS Appender converts to a JMS `javax.jms.MapMessage`. -* xref:manual/appenders.adoc#JDBCAppender[JDBC Appender] converts to values in a SQL INSERT statement +* xref:manual/appenders.adoc#JDBCAppender[JDBC Appender] converts to values in an `SQL INSERT` statement * xref:manual/appenders.adoc#NoSQLAppenderMongoDBMain[MongoDB Appender] converts to fields in MongoDB object [#MessageFormatMessage] -=== MessageFormatMessage +=== `MessageFormatMessage` link:../javadoc/log4j-api/org/apache/logging/log4j/message/MessageFormatMessage.html[`MessageFormatMessage`] handles messages that use a @@ -189,69 +188,67 @@ https://docs.oracle.com/javase/7/docs/api/java/text/MessageFormat.html[conversio While this `Message` has more flexibility than `ParameterizedMessage,` it is also about two times slower. [#MultiformatMessage] -=== MultiformatMessage +=== `MultiformatMessage` -A `MultiformatMessage` has the `getFormats()` and `getFormattedMessage()` method that accepts an array of format Strings. +A `MultiformatMessage` has the `getFormats()` and `getFormattedMessage()` methods that accept an array of format ``String``s. Layouts may use the `getFormats()` method to provide information on the formatting options the message supports. -The Layout may then call `getFormattedMessage()` with one or more formats. +The layout may then call `getFormattedMessage()` with one or more formats. The message will be shown in the default format when the format name is not recognized. -For example, the `StructuredDataMessage` accepts the "XML" format name to -format the message as XML instead of the default RFC5424 format. +For example, the `StructuredDataMessage` accepts the `XML` format name to format the message as XML instead of the default RFC5424 format. [#ObjectMessage] -=== ObjectMessage +=== `ObjectMessage` Formats an `Object` by calling its `toString()` method. Since Log4j 2.6, low-garbage or garbage-free layouts call the `formatTo(StringBuilder)` method instead. [#ParameterizedMessage] -=== ParameterizedMessage +=== `ParameterizedMessage` -link:../javadoc/log4j-api/org/apache/logging/log4j/message/ParameterizedMessage.html[`ParameterizedMessage`] -handles messages that contain "\{}" in the format to represent replaceable tokens and the replacement parameters. +link:../javadoc/log4j-api/org/apache/logging/log4j/message/ParameterizedMessage.html[`ParameterizedMessage`] handles messages that contain `{}` in the format to represent replaceable tokens and the replacement parameters. [#ReusableObjectMessage] -=== ReusableObjectMessage +=== `ReusableObjectMessage` `ReusableObjectMessage` provides functionally equivalent to <>. -Log4j uses this message in garbage-free mode to pass logged Objects to the Layout and Appenders. +Log4j uses this message in garbage-free mode to pass logged ``Object``s to layouts and appenders. [#ReusableParameterizedMessage] -=== ReusableParameterizedMessage +=== `ReusableParameterizedMessage` `ReusableParameterizedMessage` provides functionally equivalent to <>. Log4j uses this message in garbage-free mode to handle messages containing `{}` in the format representing replaceable tokens and the replacement parameters. [#ReusableSimpleMessage] -=== ReusableSimpleMessage +=== `ReusableSimpleMessage` `ReusableSimpleMessage` provides functionally equivalent to <>. Log4j uses this message in garbage-free mode to pass logged `String` and `CharSequence` objects to the Layout and Appenders. [#SimpleMessage] -=== SimpleMessage +=== `SimpleMessage` `SimpleMessage` contains a `String` or `CharSequence` that requires no formatting. [#StringFormattedMessage] -=== StringFormattedMessage +=== `StringFormattedMessage` link:../javadoc/log4j-api/org/apache/logging/log4j/message/StringFormattedMessage.html[`StringFormattedMessage`] handles messages that use a https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html#syntax[conversion format] that is compliant with -https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#format(java.lang.String,%20java.lang.Object...)[java.lang.String.format()]. +https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#format(java.lang.String,%20java.lang.Object...)[java.lang.String#format()]. -This message is more flexible than `ParameterizedMessage` but 5 to 10 times slower. +This message is more flexible than `ParameterizedMessage`, but significantly slower. [#StructuredDataMessage] -=== StructuredDataMessage +=== `StructuredDataMessage` link:../javadoc/log4j-api/org/apache/logging/log4j/message/StructuredDataMessage.html[`StructuredDataMessage`] allows applications to add items to a `Map` and set the ID to allow Log4j to format the message as a "Structured Data Element." @@ -259,18 +256,19 @@ allows applications to add items to a `Map` and set the ID to allow Log4j to for See: http://tools.ietf.org/html/rfc5424[RFC 5424] for details. [#ThreadDumpMessage] -=== ThreadDumpMessage +=== `ThreadDumpMessage` If a `ThreadDumpMessage` is logged, Log4j generates stack traces for all threads. These stack traces will include any held locks. [#TimestampMessage] -=== TimestampMessage +=== `TimestampMessage` A `TimestampMessage` provides a `getTimestamp()` method that Log4j calls during event construction. The timestamp in the Message will be used instead of the current timestamp. -== Performance and benefits +[#performance] +== Performance Although it may initially seem counterintuitive, there is no performance benefit to using strings instead of messages. Testing has shown that modern JVMs can create and destroy log events quickly, diff --git a/src/site/antora/modules/ROOT/pages/manual/resource-logger.adoc b/src/site/antora/modules/ROOT/pages/manual/resource-logger.adoc index 3951cd09dc7..90f5843b412 100644 --- a/src/site/antora/modules/ROOT/pages/manual/resource-logger.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/resource-logger.adoc @@ -15,7 +15,7 @@ limitations under the License. //// -= Resource Logging += Resource Loggers == ScopedResourceLogger