Messages
Unlike other logging APIs, which either restrict the description of log events to (possibly interpolated) Java String
s or allow generic Java Object
s, 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
SimpleMessage
from aString
argument, 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
ParameterizedMessage
from a formatString
and 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
MessageFormat
pattern, 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 MapMessage
s differently when there is no layout:
-
JMS Appender converts to a JMS
javax.jms.MapMessage
-
JDBC Appender converts to values in an
SQL INSERT
statement -
MongoDB NoSQL provider converts to fields in a MongoDB object
JSON Template Layout
JSON Template Layout has a specialized handling for MapMessage
s 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 StructuredDataMessage
s.
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, StructuredDataMessage
s 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.java
record 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 StringBuilder
s 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
.