ACTION THIS DAY Make sure they have all they want on extreme priority and report to me that this has been done.
—CHURCHILL on October 1941 to General Hastings Ismay in response to a request for more resources requested by Alan Turing and his cryptanalyst colleagues at Bletchley Park
Encoders
What is an encoder
Encoders are responsible for transforming an event into a byte
array as well as writing out that byte array into an
OutputStream. Encoders were introduced in logback
version 0.9.19. In previous versions, most appenders relied on a
layout to transform an event into a string and write it out using
a java.io.Writer. In previous versions of logback,
users would nest a PatternLayout within
FileAppender. Since logback 0.9.19,
FileAppender and subclasses expect an encoder and no
longer take a layout.
Why the breaking change?
Layouts, as discussed in detail in the next chapter, are only able to transform an event into a String which restricts their scope to non-binary output.
Encoder interface
Encoders are responsible for transforming an incoming event into a byte array. Here is the Encoder interface:
package ch.qos.logback.core.encoder;
/**
* Encoders are responsible for transform an incoming event into a byte array
*/
public interface Encoder<E> extends ContextAware, LifeCycle {
/**
* Get header bytes. This method is typically called upon opening of an output
* stream.
*
* @return header bytes. Null values are allowed.
*/
byte[] headerBytes();
/**
* Encode an event as bytes.
*
* @param event
*/
byte[] encode(E event);
/**
* Get footer bytes. This method is typically called prior to the closing of the
* stream where events are written.
*
* @return footer bytes. Null values are allowed.
*/
byte[] footerBytes();
}
As you can see, the Encoder interface consists of
few methods, but surprisingly many useful things can be
accomplished with these methods.
LayoutWrappingEncoder
Until logback version 0.9.19, many appenders relied on the Layout instances to control the format of log output. As there exists substantial amount of code based on the layout interface, we needed a way for encoders to interoperate with layouts. LayoutWrappingEncoder bridges the gap between encoders and layouts. It implements the encoder interface and wraps a layout to which it delegates the work of transforming an event into string.
Below is an excerpt from the LayoutWrappingEncoder
class illustrating how delegation to the wrapped layout instance
is done.
package ch.qos.logback.core.encoder;
public class LayoutWrappingEncoder<E> extends EncoderBase<E> {
protected Layout<E> layout;
private Charset charset;
// encode a given event as a byte[]
public byte[] encode(E event) {
String txt = layout.doLayout(event);
return convertToBytes(txt);
}
private byte[] convertToBytes(String s) {
if (charset == null) {
return s.getBytes();
} else {
return s.getBytes(charset);
}
}
}
The doEncode() method starts by having the wrapped
layout convert the incoming event into string. The resulting text
string is converted to bytes according to the charset encoding
chosen by the user.
PatternLayoutEncoder
Given that PatternLayout is the most commonly used
layout, logback caters for this common use-case with
PatternLayoutEncoder, an extension of
LayoutWrappingEncoder restricted to wrapping
instances of PatternLayout.
As of logback version 0.9.19, whenever a
FileAppender or one of its subclasses was configured
with a PatternLayout, a
PatternLayoutEncoder must be used instead. This is
explained in the relevant entry in the
logback error codes.
immediateFlush property
As of logback 1.2.0, the immediateFlush property is part of the enclosing Appender.
Output pattern string as header
In order to facilitate parsing of log files, logback can insert
the pattern used for the log output at the top of log files. This
feature is disabled by default. It can be enabled by
setting the outputPatternAsHeader
property to 'true' for relevant
PatternLayoutEncoder. Here is an example:
<configuration>
<!-- omitted lines ... -->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>foo.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d %-5level [%thread] %logger{0}: %msg%n</pattern>
<outputPatternAsHeader>true</outputPatternAsHeader>
</encoder>
</appender>
<!-- omitted lines ... -->
</configuration>
Requires a server call.
Requires a server call.
This will result output akin to the following in the log file:
#logback.classic pattern: %d [%thread] %-5level %logger{36} - %msg%n
2012-04-26 14:54:38,461 [main] DEBUG com.foo.App - Hello world
2012-04-26 14:54:38,461 [main] DEBUG com.foo.App - Hi again
...
The line starting with "#logback.classic pattern" is newly inserted pattern line.
JsonEncoder
since versions 1.3.8/1.4.8
JsonEncoder follows
the JSON Lines standard. It
transforms a logging event into a valid json text in conformance
with
[RFC-8259]. Each
logging event in JSON text format is followed by a new line. More
specifically, certain characters such as quotations marks,
backslash, forward slash, form feed, line feed, carriage return
characters are escaped according to the rules described
in section
7 of RFC-8259.
Here is sample output post-formatted for readability:
{
"sequenceNumber":0,
"timestamp":1686403686358,
"nanoseconds":358357100,
"level":"INFO",
"threadName":"main",
"loggerName":"org.foo.Bar",
"context":{
"name":"default",
"birthdate":1686403685679,
"properties":{
"moo":"299857071"
}
},
"mdc":{
"a1":"v1299857071"
},
"kvpList":[
{
"ik299857071":"iv299857071"
},
{
"a":"b"
}
],
"message":"Hello \"Alice\"",
"throwable":{
"className":"java.lang.RuntimeException",
"message":"an error",
"stepArray":[
{
"className":"org.foo.Bar",
"methodName":"httpCall",
"fileName":"Bar.java",
"lineNumber":293
},
{
"className":"jdk.internal.reflect.DirectMethodHandleAccessor",
"methodName":"invoke",
"fileName":"DirectMethodHandleAccessor.java",
"lineNumber":104
},
.... omitted lines
]
}
}
Note that actual output will be denser, with only one logging event per line.
Here is a sample configuration file using JsonEncoder.
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>foo.json</file>
<encoder class="ch.qos.logback.classic.encoder.JsonEncoder"/>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
Requires a server call.
Requires a server call.
since versions 1.5.0 Output of all top-level (json) members can be enabled or disabled. By default all top-level members are enabled except for the "formattedMessage". Below is a configuration file which enables "formattedMessage" and disables output of the raw "message" and the "arguments" members.
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>foo.json</file>
<encoder class="ch.qos.logback.classic.encoder.JsonEncoder">
<withFormattedMessage>true</withFormattedMessage>
<withMessage>false</withMessage>
<withArguments>false</withArguments>
</encoder>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
Requires a server call.
Requires a server call.
As the above example suggests, output of a top-level member with the name "pellam" can be enabled/disabled using the XML element <withPellam>.
KryoEncoder
since version 1.6.7
KryoEncoder
transforms logging events into a compact binary stream with the
help of the Kryo
5 serialization library. KryoDecoder can read the
resulting stream back and reconstitute the logging events, so that
they can be inspected or replayed through the appenders of a
LoggerContext. Compared to text-based encoders,
KryoEncoder produces smaller output at a lower CPU
cost. The decoded events retain the fields of the original event,
including the MDC, markers, key-value pairs, the throwable (if any)
and caller data (if available).
KryoEncoder ships in the
logback-classic-kryo module, which in turn depends on the
logback-core-kryo module. Both modules require Java 11 or
later. Their JPMS module names are
ch.qos.logback.classic.kryo and
ch.qos.logback.core.kryo. If you are using Maven, add
the following dependency to your project.
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic-kryo</artifactId> <version>1.6.7</version> </dependency>
Here is a sample configuration file
using KryoEncoder.
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>app.kryo</file>
<encoder class="ch.qos.logback.classic.kryo.KryoEncoder"/>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
Requires a server call.
Requires a server call.
Stream formats
A Kryo stream starts with a 6-byte header consisting of the
magic bytes "STWD", followed by the stream version and a flags
byte indicating the serializer type. The appender writes the
header when it starts, so that KryoDecoder can
determine the format of the stream on its own. Two stream formats
are supported, selected by the serializerType
property of KryoEncoder.
| serializerType | Stream version | Description |
|---|---|---|
Custom (default) |
5 | Uses serializers written specifically for logging events. Repeated values, such as logger names, thread names, MDC keys or class names in stack traces, are written once and are subsequently referred to by a short identifier. Timestamps are stored as deltas. Each event is wrapped in a frame protected by a checksum, so that the decoder can skip over torn or truncated writes, for example after a crash of the application, and resume decoding at the next intact segment. |
Vanilla |
0 | Uses the default serializers of Kryo. Events are not framed. If a vanilla stream is damaged, the remainder of the stream is lost. |
The Custom serializer type is recommended in most
cases. Setting serializerType to
Vanilla automatically selects stream version 0.
Properties of KryoEncoder
| Property Name | Type | Description |
|---|---|---|
| serializerType | Custom or Vanilla |
The serializer type, as described above. The default
is Custom. |
| streamVersion | int |
The stream version written into the header. The default
is 5. There is usually no need to set this property, as the
stream version follows from the serializer type. If it is set
to a value other than 0 while serializerType
is Vanilla, a warning is emitted and stream
version 0 is used instead. |
| maxEventsBeforeReset | int |
Applies to the Custom serializer type
only. After this many events, the encoder closes the current
segment of the stream, writes a new header and forgets the
values it previously assigned identifiers to. This bounds the
memory used by the encoder and the decoder, and limits the
number of events affected by a damaged region of the stream.
The default is 8192. The value must be at least 1. |
Note that KryoEncoder is stateful, as the encoding
of an event may refer to values written by previous events in the
same segment.
Arguments of the logging message and values of key-value pairs
are restricted to strings and the numeric wrapper types
Integer, Long, Short,
Float and Double. Other objects are
converted to strings by invoking their toString()
method before encoding. Thus, no application classes are ever
written into or instantiated from a Kryo stream.
Reading Kryo streams with KryoDecoder
KryoDecoder reads a file produced by
KryoEncoder. It takes the stream version and the
serializer type from the header of the file. The decoded events,
of type LoggingEventKryoVO, implement the
ILoggingEvent interface. They can be collected into a
list, passed to a Consumer, or replayed through the
appenders of a LoggerContext.
import ch.qos.logback.classic.kryo.KryoDecoder;
import ch.qos.logback.classic.kryo.LoggingEventKryoVO;
KryoDecoder decoder = new KryoDecoder(new File("app.kryo"));
List<LoggingEventKryoVO> events = decoder.decodeAsList();
// alternatively, process each event as it is decoded
// KryoDecoder.DecodeStats stats = decoder.decodeWithConsumer(event -> ...);
// or replay the events through the appenders of a LoggerContext
// KryoDecoder.DecodeStats stats = decoder.decodeAndAppend(loggerContext);
A KryoDecoder instance reads its file only
once. The DecodeStats object returned
by decodeWithConsumer and decodeAndAppend
reports the number of decoded events. Its truncated()
method returns true if some events may be missing
because the stream was damaged or cut short. A stream of an
unsupported version is rejected with
an IOException.
The KryoDecoderApp application replays a Kryo file
through the appenders defined in a logback configuration file,
for example to convert a Kryo file into a human-readable log
file. The first argument is the Kryo file and the second is the
configuration file.
java ch.qos.logback.classic.kryo.KryoDecoderApp app.kryo logback-replay.xml