| //// |
| 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 |
| |
| https://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| //// |
| = Log4j 2 API |
| Ralph Goers <rgoers@apache.org> |
| |
| [#Messages] |
| == 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: |
| |
| [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: |
| |
| [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. |
| |
| 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. |
| |
| Borrowing from the earlier example illustrating Markers to identify SQL |
| statements being logged, Messages can also be leveraged. First, the |
| Message is defined. |
| |
| [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; |
| } |
| } |
| |
| 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(); |
| } |
| } |
| ---- |
| |
| Next we can use the message in our application. |
| |
| [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); |
| |
| public String doQuery(String table) { |
| logger.entry(param); |
| |
| logger.debug(QUERY_MARKER, new SQLMessage(SQLMessage.SQLType.QUERY, table)); |
| |
| return logger.exit(); |
| } |
| |
| public String doUpdate(String table, Map<String, String> params) { |
| logger.entry(param); |
| |
| logger.debug(UPDATE_MARKER, new SQLMessage(SQLMessage.SQLType.UPDATE, table, parmas); |
| |
| return logger.exit(); |
| } |
| } |
| ---- |
| |
| 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. |
| |
| [#FormattedMessage] |
| === FormattedMessage |
| |
| The message pattern passed to a |
| link:../log4j-api/apidocs/org/apache/logging/log4j/message/FormattedMessage.html[`FormattedMessage`] |
| is first checked to see if it is a valid `java.text.MessageFormat` |
| pattern. If it is, a `MessageFormatMessage` is used to format it. If not |
| it is next checked to see if it contains any tokens that are valid |
| format specifiers for `String.format()`. If so, a `StringFormattedMessage` |
| 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 |
| |
| link:../log4j-api/apidocs/org/apache/logging/log4j/message/LocalizedMessage.html[`LocalizedMessage`] |
| is provided primarily to provide compatibility with Log4j 1.x. |
| Generally, the best approach to localization is to have the client UI |
| render the events in the client's locale. |
| |
| `LocalizedMessage` incorporates a `ResourceBundle` and allows the message |
| pattern parameter to be the key to the message pattern in the bundle. If |
| no bundle is specified, `LocalizedMessage` will attempt to locate a bundle |
| 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` is an interface with a `setLoggerName` method. This |
| method will be called during event construction so that the Message has |
| the name of the Logger used to log the event when the message is being |
| formatted. |
| |
| [#MapMessage] |
| === MapMessage |
| |
| A `MapMessage` contains a Map of String keys and values. `MapMessage` |
| implements `FormattedMessage` and accepts format specifiers of "XML", |
| "JSON" or "JAVA", in which case the Map will be formatted as XML, JSON |
| or as documented by |
| https://docs.oracle.com/javase/7/docs/api/java/util/AbstractMap.html#toString()[`java.util.AbstractMap.toString()`]. |
| Otherwise, the Map will be formatted as `"key1=value1 key2=value2 ..."`. |
| |
| Some Appenders make special use of `MapMessage` objects: |
| |
| * When a link:appenders.html#JMSAppender[JMS Appender] is configured |
| with a `MessageLayout`, it converts a Log4j `MapMessage` to a JMS |
| `javax.jms.MapMessage`. |
| * When a link:appenders.html#JDBCAppender[JDBC Appender] is configured |
| with a `MessageLayout`, it converts a Log4j `MapMessage` to values in a |
| SQL INSERT statement. |
| * When a link:appenders.html#NoSQLAppenderMongoDB3[MongoDB3 Appender] or |
| link:appenders.html#NoSQLAppenderMongoDB4[MongoDB4 Appender] is |
| configured with a `MessageLayout`, it converts a Log4j `MapMessage` to |
| fields in a MongoDB object. |
| |
| When an Appender is `MessageLayout`-aware, the object Log4j sends to |
| target is not a Log4j Log Event but a custom object. |
| |
| [#MessageFormatMessage] |
| === MessageFormatMessage |
| |
| link:../log4j-api/apidocs/org/apache/logging/log4j/message/MessageFormatMessage.html[`MessageFormatMessage`] |
| handles messages that use a |
| https://docs.oracle.com/javase/7/docs/api/java/text/MessageFormat.html[conversion |
| format]. While this `Message` has more flexibility than |
| `ParameterizedMessage`, it is also about two times slower. |
| |
| [#MultiformatMessage] |
| === MultiformatMessage |
| |
| A `MultiformatMessage` will have a getFormats method and a |
| `getFormattedMessage` method that accepts and array of format Strings. The |
| `getFormats` method may be called by a Layout to provide it information on |
| what formatting options the Message supports. The Layout may then call |
| `getFormattedMessage` with one or more for the formats. If the Message |
| doesn't recognize the format name it will simply format the data using |
| its default format. An example of this is `StructuredDataMessage` |
| 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 |
| |
| 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 |
| |
| link:../log4j-api/apidocs/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 |
| |
| In garbage-free mode, this message is used to pass logged Objects to the |
| Layout and Appenders. Functionally equivalent to |
| <<ObjectMessage>>. |
| |
| [#ReusableParameterizedMessage] |
| === ReusableParameterizedMessage |
| |
| In garbage-free mode, this message is used to handle messages that |
| contain "\{}" in the format to represent replaceable tokens and the |
| replacement parameters. Functionally equivalent to |
| <<ParameterizedMessage>>. |
| |
| [#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` contains a `String` or `CharSequence` that requires no |
| formatting. |
| |
| [#StringFormattedMessage] |
| === StringFormattedMessage |
| |
| link:../log4j-api/apidocs/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()]. |
| While this Message has more flexibility than `ParameterizedMessage`, it is |
| also 5 to 10 times slower. |
| |
| [#StructuredDataMessage] |
| === StructuredDataMessage |
| |
| link:../log4j-api/apidocs/org/apache/logging/log4j/message/StructuredDataMessage.html[`StructuredDataMessage`] |
| allows applications to add items to a `Map` as well as set the id to allow |
| a message to be formatted as a Structured Data element in accordance |
| with http://tools.ietf.org/html/rfc5424[RFC 5424]. |
| |
| [#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 |
| |
| A TimestampMessage will provide a `getTimestamp` method that is called |
| during event construction. The timestamp in the Message will be used in |
| lieu of the current timestamp. |