Messages
Unlike other logging APIs, which either restrict the description of log events to (possibly interpolated) Java Strings or allow generic Java Objects, the Log4j API encapsulates every log message into the logging-specific Message interface, before passing it to the logging implementation.
Such an approach opens to developers a wide range of customization possibilities.
|
Log messages are often used interchangeably with log events.
While this simplification holds for several cases, it is not technically correct.
A log event, capturing the logging context (level, logger name, instant, etc.) along with the log message, is generated by the logging implementation (e.g., Log4j Core) when a user issues a log using a logger, e.g., Click for an introduction to log event fieldsLog events contain fields that can be classified into three categories:
For clarity’s sake let us look at a log event formatted as JSON:
|
Usage
While internally Log4j uses Message objects, the Logger interface provides various shortcut methods to create the most commonly used messages:
-
To create a
SimpleMessagefrom aStringargument, the following logger calls are equivalent:LOGGER.error("Houston, we have a problem.", exception); LOGGER.error(new SimpleMessage("Houston, we have a problem."), exception); -
To create a
ParameterizedMessagefrom a formatStringand an array of object parameters, the following logger calls are equivalent:LOGGER.error("Unable process user with ID `{}`", userId, exception); LOGGER.error(new ParameterizedMessage("Unable process user with ID `{}`", userId), exception);
In most cases, this is sufficient.
Nex to use cases sufficed with String-based messages, the Message interface abstraction also allows users to log custom objects.
This effectively provides logging convenience in certain use cases.
For instance, imagine a scenario that uses a domain event to signal authentication failures:
record LoginFailureEvent(String userName, InetSocketAddress remoteAddress) {}
When the developer wants to log a message reporting the event, we can see that the string construction becomes more challenging to read:
LOGGER.info(
"Connection closed by authenticating user {} {} port {} [preauth]",
event.userName(),
event.remoteAddress().getHostName(),
event.remoteAddress().getPort());
By extending the Message interface, developers can simplify the reporting of a login failure:
record LoginFailureEvent(String userName, InetSocketAddress remoteAddress) implements Message { (1)
@Override
public String getFormattedMessage() { (2)
return "Connection closed by authenticating user " + userName() + " "
+ remoteAddress().getHostName() + " port " + remoteAddress().getPort() + " [preauth]";
}
// Other methods
}
| 1 | Domain model needs to implement the Message interface |
| 2 | getFormattedMessage() provides the String to be logged |
As a result, logging of LoginFailureEvent instances can be simplified as follows:
LOGGER.info(event);
Collection
This section explains predefined Log4j Message implementations addressing certain use cases.
We will group this collection into following titles:
String-based types
This section explains message types intended for human-readable String-typed output.
FormattedMessage
FormattedMessage is intended as a generic entry point to actual message implementations that use pattern-based formatting.
It works as follows:
-
If the input is a valid
MessageFormatpattern, useMessageFormatMessage -
If the input is a valid
String.format()pattern, useStringFormattedMessage -
Otherwise, use
ParameterizedMessage
|
Due to checks involved, |
LocalizedMessage
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.
|
|
MessageFormatMessage
MessageFormatMessage formats its input using Java’s MessageFormat.
|
While |
ObjectMessage
ObjectMessage is a wrapper Message implementation to log custom domain model instances.
It formats an input Object by calling its toString() method.
If the object is found to be extending from StringBuilderFormattable, it uses formatTo(StringBuilder) instead.
ObjectMessage can be thought as a convenience for ParameterizedMessage such that the following message instances are analogous:
-
new ObjectMessage(obj) -
new ParameterizedMessage("{}", obj)
That is,
-
They will both be formatted in the same way
-
Message#getParameters()will return anObject[]containing onlyobj
Hence, ObjectMessage is intended more as a marker interface to indicate the single value it encapsulates.
|
|
ParameterizedMessage
ParameterizedMessage accepts a formatting pattern containing {} placeholders and a list of arguments.
It formats the message such that each {} placeholder in the pattern is replaced with the corresponding argument.
|
|
SimpleMessage
SimpleMessage encapsulates a String or CharSequence that requires no formatting.
|
|
StringFormattedMessage
StringFormattedMessage accepts a format string and a list of arguments.
It formats the message using java.lang.String#format().
|
While |
ThreadDumpMessage
If a ThreadDumpMessage is logged, Log4j generates stack traces for all threads.
These stack traces will include any held locks.
Structured types
Log4j strives to provide top of the class support for structured logging. It complements structured layouts with message types allowing users to create structured messages effectively resulting in an end-to-end structured logging experience. This section will introduce the predefined structured message types.
What is structured logging?
In almost any modern production deployment, logs are no more written to files read by engineers while troubleshooting, but forwarded to log ingestion systems (Elasticsearch, Google Cloud Logging, etc.) for several observability use cases ranging from logging to metrics. This necessitates the applications to structure their logs in a machine-readable way ready to be delivered to an external system. This act of encoding logs following a certain structure is called structured logging.
MapMessage
MapMessage is a Message implementation that models a Java Map with String-typed keys and values.
It is an ideal generic message type for passing structured data.
MapMessage implements MultiformatMessage to facilitate encoding of its content in multiple formats.
It supports following formats:
| Format | Description |
|---|---|
|
format as XML |
|
format as JSON |
|
format as |
|
format as |
Some appenders handle MapMessages differently when there is no layout:
-
JMS Appender converts to a JMS
javax.jms.MapMessageorjakarta.jms.MapMessage -
JDBC Appender converts to values in an
SQL INSERTstatement -
MongoDB NoSQL provider converts to fields in a MongoDB object
JSON Template Layout
JSON Template Layout has a specialized handling for MapMessages to properly encode them as JSON objects.
StructuredDataMessage
StructuredDataMessage formats its content in a way compliant with the Syslog message format described in RFC 5424.
RFC 5424 Layout
StructuredDataMessage is mostly intended to be used in combination with RFC 5424 Layout, which has specialized handling for StructuredDataMessages.
By combining two, users can have complete control on how their message is encoded in a way compliant with RFC 5424, while RFC 5424 Layout will make sure the rest of the information attached to the log event is properly injected.
JSON Template Layout
Since StructuredDataMessage extends from MapMessage, which JSON Template Layout has a specialized handling for, StructuredDataMessages will be properly encoded by JSON Template Layout too.
Performance
As explained in Usage, SimpleMessage and ParameterizedMessage instances are created indirectly while interacting with Logger methods; info(), error(), etc.
In a modern JVM, the allocation cost difference between these Message instances and plain String objects is marginal.
If you observe this cost to be significant enough for your use case, you can enable Garbage-free logging.
This will effectively cause Message instances to be recycled and avoid creating pressure on the garbage collector.
In such a scenario, if you also have custom message types, consider implementing StringBuilderFormattable and introducing a message recycling mechanism.
Extending
If predefined message types fall short of addressing your needs, you can extend from the Message interface to either create your own message types or make your domain models take control of the message formatting.
Example custom message class
CustomMessageExample.javarecord LoginFailureEvent(String userName, InetSocketAddress remoteAddress)
implements Message, StringBuilderFormattable { (1)
@Override
public void formatTo(StringBuilder buffer) { (2)
buffer.append("Connection closed by authenticating user ")
.append(userName())
.append(" ")
.append(remoteAddress().getHostName())
.append(" port ")
.append(remoteAddress().getPort())
.append(" [preauth]");
}
@Override
public String getFormattedMessage() { (3)
StringBuilder buffer = new StringBuilder();
formatTo(buffer);
return buffer.toString();
}
}
| 1 | Extending from both Message and StringBuilderFormattable interfaces |
| 2 | Formats the message directly into a StringBuilder |
| 3 | getFormattedMessage() reuses formatTo() |
Format type
You can extend from MultiformatMessage (and optionally from MultiFormatStringBuilderFormattable) to implement messages that can format themselves in one or more encodings; JSON, XML, etc.
Layouts leverage this mechanism to encode a message in a particular format.
For instance, when JSON Template Layout figures out that the array returned by getFormats() of a MultiformatMessage contains JSON, it injects the MultiformatMessage#getFormattedMessage({"JSON"}) output as is without quoting it.
Marker interfaces
There are certain Log4j API interfaces that you can optionally extend from in your Message implementations to enable associated features:
LoggerNameAwareMessage
LoggerNameAwareMessage is a marker interface with a setLoggerName(String) method.
This method will be called during event construction to pass the associated Logger to the Message.
MultiformatMessage
MultiformatMessage extends from Message to support multiple format types.
For example, see MapMessage.java extending from MultiformatMessage to support multiple formats; XML, JSON, etc.
MultiFormatStringBuilderFormattable
MultiFormatStringBuilderFormattable extends StringBuilderFormattable to support multiple format types.
StringBuilderFormattable
Many layouts recycle StringBuilders to encode log events without generating garbage, and this effectively results in significant performance benefits.
StringBuilderFormattable is the primary interface facilitating the formatting of objects to a StringBuilder.
TimestampMessage
TimestampMessage provides a getTimestamp() method that will be called during log event construction to determine the instant instead of using the current timestamp.
Message implementations that want to control the timestamp of the log event they are encapsulated in, they can extend from TimestampMessage.