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
Chapter 5: 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>.