[prev in list] [next in list] [prev in thread] [next in thread]
List: flume-commits
Subject: (logging-log4j2) 01/01: rewrote intro and example - first draft
From: grobmeier () apache ! org
Date: 2024-05-16 21:28:48
Message-ID: 20240516212847.C47BF44086E () gitbox2-he-fi ! apache ! org
[Download RAW message or body]
This is an automated email from the ASF dual-hosted git repository.
grobmeier pushed a commit to branch doc/2.x/manual-messages-2535
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit d9dfb040e6d790add9128d710e34a5ef7ace2a21
Author: Christian Grobmeier <cg@grobmeier.de>
AuthorDate: Thu May 16 23:28:13 2024 +0200
rewrote intro and example - first draft
---
.../antora/modules/ROOT/pages/manual/messages.adoc | 311 ++++++++++++---------
1 file changed, 174 insertions(+), 137 deletions(-)
diff --git a/src/site/antora/modules/ROOT/pages/manual/messages.adoc \
b/src/site/antora/modules/ROOT/pages/manual/messages.adoc index \
8f2128f430..e1e7043dd8 100644
--- a/src/site/antora/modules/ROOT/pages/manual/messages.adoc
+++ b/src/site/antora/modules/ROOT/pages/manual/messages.adoc
@@ -16,165 +16,202 @@
////
= Messages
-Although Log4j 2 provides Logger methods that accept Strings and
-Objects, all of these are ultimately captured in Message objects that
-are then associated with the log event. Applications are free to
-construct Messages of their own and pass them to the Logger. Although it
-may seem more expensive than passing the message format and parameters
-directly to the event, testing has shown that with modern JVMs the cost
-of creating and destroying events is minor, especially when complex
-tasks are encapsulated in the Message instead of the application. In
-addition, when using the methods that accept Strings and parameters, the
-underlying Message object will only be created if any configured global
-filters or the Logger's log level allow the message to be processed.
-
-Consider an application that has a Map object containing \{"Name" =
-"John Doe", "Address" = "123 Main St.", "Phone" = "(999) 555-1212"} and
-a User object that has a getId method that returns "jdoe". The developer
-would like to add an informational message that returns "User John Doe
-has logged in using id jdoe". The way this could be accomplished is by
-doing:
+Messages are a way to encapsulate the creation of logging statements.
+They can be used to make code more readable and reuseable.
-[source,java]
+== Introduction
+
+Log4j provides various methods for creating log messages.
+
+The most popular ones are probably the ones that take a string as an argument:
+
+[source, java]
+----
+logger.info("Hello World");
+----
+
+In simple environments, this is sufficient. However, in more complex scenarios,
+the string one wants to log may require more complex construction.
+
+Imagine a scenario that uses a `Map` and a `User` object that may look like this:
+
+[source, java]
+----
+Map<String, String> map = new HashMap<>();
+map.put("Name", "John Doe");
+
+User user = new User();
+user.setId("jdoe");
+----
+
+When the developer wants to log a message that says "User John Doe has logged in \
using id jdoe", +we can see that the string construction becomes more challenging to \
read: +
+[source, java]
----
logger.info("User {} has logged in using id {}", map.get("Name"), user.getId());
----
-While there is nothing inherently wrong with this, as the complexity of
-the objects and desired output increases this technique becomes harder
-to use. As an alternative, using Messages allows:
+Messages will change your code to allow these complex constructions to be \
encapsulated in a class.
-[source,java]
+Developers could create a `LoggedInMessage` class that is responsible for formatting \
the message. +The result is more readable and reusable across the code, like the \
custom-made `LoggedInMessage` class: +
+[source, java]
+----
+logger.info(new LoggedInMessage(map, user));
+----
+
+== Performance and benefits
+
+Although it may initially seem unintuitive, there is no performance benefit to using \
strings instead of messages. +Ultimately, Log4j captures all log messages in a \
`Message` object under the hood. +There is no difference when creating a custom \
`Message` object. +
+Testing has shown that modern JVMs can create and destroy log events quickly,
+especially when complex tasks are encapsulated in the `Message` object instead of \
the application. +
+Implementing a `Layout` is also simpler with `Message` objects.
+Instead of requiring the `Layout` to loop through the parameters and determine
+what to do based on the objects encountered, the `Layout` can delegate the \
formatting to the `Message` +or format it based on the type of message encountered.
+
+== Example
+
+To achieve the same result as the previous example, we can create a \
`LoggedInMessage` class. +This class implements the `Message` interface and provides \
a `getFormattedMessage` method that returns the desired message. +The constructor can \
take the arguments that the `getFormattedMessage` method needs to construct the \
message. +
+[source, java]
+----
+public class LoggedInMessage implements Message { <1>
+ private final Map<String, String> map;
+ private final User user;
+
+ public LoggedInMessage(Map<String, String> map, User user) {
+ this.map = map;
+ this.user = user;
+ }
+
+ @Override
+ public String getFormattedMessage() { <2>
+ return "User " + map.get("Name") + " has logged in using id " + \
user.getId(); <3> + }
+}
+----
+<1> The `Message` interface must be implemented.
+<2> The `getFormattedMessage` provides the `String` to be logged.
+<3> The message is constructed here.
+
+Implementing a `Message` is basically only a way to concatenate a string.
+
+With this class, the log statement can be simplified as already shown above:
+
+[source, java]
----
logger.info(new LoggedInMessage(map, user));
----
-In this alternative the formatting is delegated to the `LoggedInMessage`
-object's `getFormattedMessage` method. Although in this alternative a new
-object is created, none of the methods on the objects passed to the
-`LoggedInMessage` are invoked until the `LoggedInMessage` is formatted. This
-is especially useful when an `Object`'s `toString` method does not produce
-the information you would like to appear in the log.
+In this example, we may create a `LoggedInMessage` object, but we will not invoke
+the construction logic of this message until the `getFormattedMessage` method is \
called. +Logging might be turned off for this statement, and the construction logic \
will not be executed, saving resources. +
+== A more complex example
-Another advantage to Messages is that they simplify writing Layouts. In
-other logging frameworks the Layout must loop through the parameters
-individually and determine what to do based on what objects are
-encountered. With Messages the Layout has the option of delegating the
-formatting to the Message or performing its formatting based on the type
-of Message encountered.
+Messages can be complex, if necessary, and make them work for different scenarios.
-Borrowing from the earlier example illustrating Markers to identify SQL
-statements being logged, Messages can also be leveraged. First, the
-Message is defined.
+Imagine a scenario where you want to log SQL queries and updates.
+You might want to change the formatted message based on the type of SQL operation.
+
+The first step could be to define a type for the two SQL operations.
[source,java]
----
+public enum SQLType {
+ UPDATE,
+ QUERY
+}
+----
+
+Next, we can create a `SQLMessage` class that implements the `Message` interface.
+It may take the `SQLType`, the table name, and a map of query parameters as \
arguments. +Based on this information, the `SQLMessage` class can format the message \
accordingly. +
+[source, java]
+----
public class SQLMessage implements Message {
- public enum SQLType {
- UPDATE,
- QUERY
- }
-
- private final SQLType type;
- private final String table;
- private final Map<String, String> cols;
-
- public SQLMessage(SQLType type, String table) {
- this(type, table, null);
- }
-
- public SQLMessage(SQLType type, String table, Map<String, String> cols) {
- this.type = type;
- this.table = table;
- this.cols = cols;
- }
-
- public String getFormattedMessage() {
- switch (type) {
- case UPDATE:
- return createUpdateString();
- break;
- case QUERY:
- return createQueryString();
- break;
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- public String getMessageFormat() {
- return type + " " + table;
- }
-
- public Object getParameters() {
- return cols;
- }
-
- private String createUpdateString() {
- }
-
- private String createQueryString() {
- }
-
- private String formatCols(Map<String, String> cols) {
- StringBuilder sb = new StringBuilder();
- boolean first = true;
- for (Map.Entry<String, String> entry : cols.entrySet()) {
- if (!first) {
- sb.append(", ");
- }
- sb.append(entry.getKey()).append("=").append(entry.getValue());
- first = false;
- }
- return sb.toString();
- }
+ private final SQLType type;
+ private final String table;
+ private final Map<String, String> queryParams;
+
+ public SQLMessage(SQLType type, String table, Map<String, String> queryParams) {
+ this.type = type;
+ this.table = table;
+ this.queryParams = queryParams;
+ }
+
+ @Override
+ public String getFormattedMessage() {
+ switch (type) { <1>
+ case UPDATE:
+ return createUpdateString();
+ break;
+ case QUERY:
+ return createQueryString();
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private String createUpdateString() { <2>
+ // Complex logic for creating an update log string
+ }
+
+ private String createQueryString() { <3>
+ // Complex logic for creating a query log string
+ }
}
----
+<1> The type decides which message to create.
+<2> The `createUpdateString` method creates the message for an update.
+<3> The `createQueryString` method creates the message for a query.
-Next we can use the message in our application.
+After the logic for string creation is inserted, this message can be used
+for two different scenarios already.
-[source,java]
+[source, java]
----
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import java.util.Map;
public class MyApp {
-
- private Logger logger = LogManager.getLogger(MyApp.class.getName());
- private static final Marker SQL_MARKER = MarkerManager.getMarker("SQL");
- private static final Marker UPDATE_MARKER = \
MarkerManager.getMarker("SQL_UPDATE", SQL_MARKER);
- private static final Marker QUERY_MARKER = MarkerManager.getMarker("SQL_QUERY", \
SQL_MARKER);
-
+ private static final Logger logger = LogManager.getLogger();
+
public String doQuery(String table) {
- logger.entry(param);
-
- logger.debug(QUERY_MARKER, new SQLMessage(SQLMessage.SQLType.QUERY, table));
-
- return logger.exit();
+ logger.debug(new SQLMessage(SQLType.QUERY, table));
+ String result = // ... do the query;
+ return result;
}
public String doUpdate(String table, Map<String, String> params) {
- logger.entry(param);
+ logger.debug(new SQLMessage(SQLType.UPDATE, table, params));
- logger.debug(UPDATE_MARKER, new SQLMessage(SQLMessage.SQLType.UPDATE, table, \
parmas));
-
- return logger.exit();
+ String result = // ... do the query;
+ return result;
}
}
----
-Notice that in contrast to the prior version of this example, the
-`logger.debug` in `doUpdate` no longer needs to be wrapped in an
-`isDebugEnabled` call as creation of the `SQLMessage` is on the same order
-of magnitude of performing that check. Furthermore, all the formatting
-of the SQL columns is now hidden in the `SQLMessage` instead of having to
-take place in the business logic. Finally, if desired, Filters and/or
-Layouts can be written to take special action when an `SQLMessage` is
-encountered.
+Even when the creation of the SQL message might be complex, the business logic is \
kept clean and readable. +
+== Message types
+
+Log4j provides several message types that developers can use to create log messages.
[#FormattedMessage]
-== FormattedMessage
+=== FormattedMessage
The message pattern passed to a
link:../javadoc/log4j-api/org/apache/logging/log4j/message/FormattedMessage.html[`FormattedMessage`]
@@ -186,7 +223,7 @@ is used to format it. Finally, if the pattern doesn't match \
either of those then a `ParameterizedMessage` is used to format it.
[#LocalizedMessage]
-== LocalizedMessage
+=== LocalizedMessage
link:../javadoc/log4j-api/org/apache/logging/log4j/message/LocalizedMessage.html[`LocalizedMessage`]
is provided primarily to provide compatibility with Log4j 1.x.
@@ -200,7 +237,7 @@ with the name of the Logger used to log the event. The message \
retrieved from the bundle will be formatted using a FormattedMessage.
[#LoggerNameAwareMessage]
-== LoggerNameAwareMessage
+=== LoggerNameAwareMessage
`LoggerNameAwareMessage` is an interface with a `setLoggerName` method. This
method will be called during event construction so that the Message has
@@ -208,7 +245,7 @@ the name of the Logger used to log the event when the message is \
being formatted.
[#MapMessage]
-== MapMessage
+=== MapMessage
A `MapMessage` contains a Map of String keys and values. `MapMessage`
implements `FormattedMessage` and accepts format specifiers of "XML",
@@ -230,7 +267,7 @@ configured with no layout, it converts a Log4j `MapMessage` to
fields in a MongoDB object.
[#MessageFormatMessage]
-== MessageFormatMessage
+=== MessageFormatMessage
link:../javadoc/log4j-api/org/apache/logging/log4j/message/MessageFormatMessage.html[`MessageFormatMessage`]
handles messages that use a
@@ -239,7 +276,7 @@ format]. While this `Message` has more flexibility than
`ParameterizedMessage`, it is also about two times slower.
[#MultiformatMessage]
-== MultiformatMessage
+=== MultiformatMessage
A `MultiformatMessage` will have a getFormats method and a
`getFormattedMessage` method that accepts and array of format Strings. The
@@ -252,28 +289,28 @@ which accepts a format String of "XML" which will cause it to \
format the event data as XML instead of the RFC 5424 format.
[#ObjectMessage]
-== ObjectMessage
+=== ObjectMessage
Formats an `Object` by calling its `toString` method. Since Log4j 2.6,
Layouts trying to be low-garbage or garbage-free will 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.
[#ReusableObjectMessage]
-== ReusableObjectMessage
+=== ReusableObjectMessage
In garbage-free mode, this message is used to pass logged Objects to the
Layout and Appenders. Functionally equivalent to
<<ObjectMessage>>.
[#ReusableParameterizedMessage]
-== ReusableParameterizedMessage
+=== ReusableParameterizedMessage
In garbage-free mode, this message is used to handle messages that
contain "\{}" in the format to represent replaceable tokens and the
@@ -281,20 +318,20 @@ replacement parameters. Functionally equivalent to
<<ParameterizedMessage>>.
[#ReusableSimpleMessage]
-== ReusableSimpleMessage
+=== ReusableSimpleMessage
In garbage-free mode, this message is used to pass logged `String`s and
`CharSequence`s to the Layout and Appenders. Functionally equivalent to
<<SimpleMessage>>.
[#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
@@ -305,7 +342,7 @@ While this Message has more flexibility than \
`ParameterizedMessage`, it is also 5 to 10 times slower.
[#StructuredDataMessage]
-== StructuredDataMessage
+=====StructuredDataMessage
link:../javadoc/log4j-api/org/apache/logging/log4j/message/StructuredDataMessage.html[`StructuredDataMessage`]
allows applications to add items to a `Map` as well as set the id to allow
@@ -313,13 +350,13 @@ a message to be formatted as a Structured Data element in \
accordance with http://tools.ietf.org/html/rfc5424[RFC 5424].
[#ThreadDumpMessage]
-== ThreadDumpMessage
+=== ThreadDumpMessage
A ThreadDumpMessage, if logged, will generate stack traces for all
threads. The stack traces will include any locks that are held.
[#TimestampMessage]
-== TimestampMessage
+=== TimestampMessage
A TimestampMessage will provide a `getTimestamp` method that is called
during event construction. The timestamp in the Message will be used in
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic