## Chapter 4: Appenders

Authors: Ceki Gülcü, Sébastien Pennec

| Copyright © 2000-2006, QOS.ch This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License .

## What is an Appender

Logback delegates the task of writing a logging event to appenders. Appenders must imple-ment the `ch.qos.logback.core.Appender` interface. The salient methods of this interface are summarized below:

    package ch.qos.logback.core;

    import ch.qos.logback.core.spi.ContextAware;
    import ch.qos.logback.core.spi.FilterAttachable;
    import ch.qos.logback.core.spi.LifeCycle;

    public interface Appender extends LifeCycle, ContextAware, FilterAttachable {

      public String getName();
      void doAppend(Object event);
      public void setLayout(Layout layout);
      public Layout getLayout();
      public void setName(String name);

    }

Most of the methods in the Appender interface are made of setter and getter methods. A notable exception is the `doAppend()` method taking an Object instance as its only parameter. This method is perhaps the most important in the logback framework. It is responsible for outputting the logging events in a suitable format to the appropriate output device. Appenders are named entities. This ensures that they can be referenced by name, a quality confirmed to be especially significant in configuration scripts. An appender can contain multiple filters, thus the `Appender` interface extending the `FilterAttachable` interface. Filters are discussed in detail in a subsequent chapter.

Appenders are ultimately responsible for outputting logging events. However, they may delegate the actual formatting of the event to a `Layout` object. Each layout is associated with one and only one appender, referred to as the containing appender. Some appenders have a built-in or fixed event format, such that they do not require a layout. For example, the `SocketAppender` simply serialize logging events before transmitting them over the wire.

## AppenderBase

The `ch.qos.logback.core.AppenderSkeleton` class is an abstract class implementing the `Appender` interface. It provides basic functionality shared by all appenders, such as methods for getting or setting their name, their started status, their layout and their filters. It is the super-class of all appenders shipped with logback. Although an abstract class, AppenderBase actually implements the `doAppend()` method in the `Append` interface. Perhaps the clearest way to discuss AppenderBase class is by presenting a bit of its actual source code. `AppenderBase`'s complete source code is also available for viewing.

    public synchronized void doAppend(Object eventObject) {

      // prevent re-entry.
      if (guard) {
        return;
      }

      try {
        guard = true;

        if (!this.started) {
          if (statusRepeatCount++ < ALLOWED_REPEATS) {
            addStatus(new WarnStatus(
                "Attempted to append to non started appender [" + name + "].",this));
          }
          return;
        }

        if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
          return;
        }

        // ok, we now invoke derived class' implementation of append
        this.append(eventObject);

      } finally {
        guard = false;
      }
    }

This implementation of the `doAppend()` method is synchronized. It follows that logging to the same appender from different threads is safe. While a thread, say T, is executing the `doAppend()` method, subsequent calls by other threads are queued until T leaves the `doAppend()` method, ensuring T's exclusive access to the appender.

EXPLAIN GUARD

The first statement of the `doAppend()` method, once the `try` block is reached is to check whether the `started` field is true. If it is not, `doAppend()` will send a warning message and return. In other words, once stopped, it is impossible to write to a closed appender. `Appender` object implement the `LifeCycle` interface, which implies that they implement `start()`, `stop()` and `isStarted()` methods. After setting all the options of an appender, Joran, logback's configuration framework, calls this method to signal the appender to bind or activate its options. Indeed, depending on the appender, certain options cannot be activated because of interferences with other options, or appenders can even not start at all if some options are missing. For example, since file creation depends on truncation mode, `FileAppender` cannot act on the value of its `File` option until the value of the Append option is also known for certain. Sub-classes of `AppenderBase` are required to set the boolean variable `started` to false when their `stop()` method is invoked.

If a warning message is sent due to incorrect calls to the `doAppend()` method, logback's powerful `Status` error reporting system is used. In case several incorrect calls on `doAppend()` are issued, `AppenderBase` does not send an unlimited number of warnings. Once a certain limit is reached, the `AppenderBase` instance stops its warnings.

The next `if` statement checks the result of the attached `Filter` objects. Depending on the decision made by the filters in the filter chain, events can be denied or alternatively accepted. In the absence of a decision by the filter chain, events are accepted by default.

Lastly, the `doAppend()` method invoke the derived classes' implementation of the `append()` method, which does the actual work of appending the event to the appropriate device.

DO WE ADD A UML DIAGRAM?? YES!!

In appenders, the term option or property is reserved for named attributes that are dynamically inferred using JavaBeans introspection.

## Logback Core

### WriterAppender

`WriterAppender` appends events to a `java.io.Writer`. This class provides basic services that other appenders build upon. Users do not usually instantiate `WriterAppender` objects directly. Since `java.io.Writer` type cannot be mapped to a string, there is no way to specify the target `Writer` object in a configuration script. Simply put, you cannot configure a `WriterAppender` from a script. However, this does not mean that `WriterAppender` lacks configurable options. These options are described next.

Option Name | Type | Description
--- | --- | ---

Encoding

`String`

The encoding specifies the method of conversion between 16-bit Unicode characters into raw 8-bit bytes. This appender will use the local platform's default encoding unless you specify otherwise using the Encoding option. According to the `java.lang` package documentation, acceptable values are dependent on the VM implementation although all implementations are required to support at least the following encodings: US-ASCII, ISO-8859-1, UTF-8, UTF-16BE, UTF-16LE and UTF-16. By default, the Encoding option is null such that the platform's default encoding is used.

ImmediateFlush

`boolean`

If set to true, each write of a logging event is followed by a flush operation on the underlying `Writer` object. Conversely, if the option is set to false, each write will not be followed by a flush. In general, skipping the flush operation improves logging throughput by roughly 15%. The downside is that if the application exits abruptly, the unwritten characters buffered inside the `Writer` might be lost. This can be particularly troublesome as those unwritten characters may contain crucial information needed in identifying the reasons behind a crash. By default, the ImmediateFlush option is set to true.

In general, if you disable immediate flushing, then make sure to flush any output streams when your application exits. Otherwise, log messages will be lost as illustrated by the next example.

Example 4.1: Exiting an application without flushing (logback-examples/src/main/java/chapter4/ ExitWoes.java)

    package chapter4;

    import java.io.FileOutputStream;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import ch.qos.logback.classic.LoggerContext;
    import ch.qos.logback.core.WriterAppender;
    import ch.qos.logback.core.layout.DummyLayout;

    public class ExitWoes {

      public static void main(String[] args) throws Exception {
        LoggerContext lc = new LoggerContext();
        WriterAppender writerAppender = new WriterAppender();
        writerAppender.setContext(lc);
        writerAppender.setLayout(new DummyLayout());

        OutputStream os = new FileOutputStream("exitWoes1.log");
        writerAppender.setWriter(new OutputStreamWriter(os));
        writerAppender.setImmediateFlush(false);
        writerAppender.start();

        Logger logger = LoggerFactory.getLogger(ExitWoes1.class);

        logger.debug("Hello world.");
      }
    }

This example creates a `WriterAppender` that uses an `OutputStreamWriter` wrapping a `FileOutputStream` as its underlying `Writer` object, with immediate flushing disabled. It then proceeds to log a single debug message. According to `OutputStreamWriter` javadocs, each invocation of a `write()` method causes the encoding converter to be invoked on the given character(s). The resulting bytes are accumulated in a buffer before being written to the underlying output stream. As astonishing as this may seem, running `ExitWoes1` will not produce any output in the file exitWoes1.log because the Java VM does not flush output streams when it exits. Calling the `shutdown()` method of a XXXXXX ensures that all appenders in the hierarchy are closed and their buffers are flushed. For most applications this is as simple as including the following statement before exiting the application.

WHAT TO DO IN LB ???

The `WriterAppender` is the super class of four other appenders, namely `ConsoleAppender`, `FileAppender` which in turn is the super class of `RollingFileAppender`.

include UML?? Figure 4 2 illustrates the class diagram for WriterAppender and its subclasses

### ConsoleAppender

The `ConsoleAppender`, as the name indicates, appends on the console, or more precisely on System.out or System.err, the former being the default target. `ConsoleAppender` formats events with a layout specified by the user. Both System.out and System.err are `java.io.PrintStream` objects. Consequently, they are wrapped inside an `OutputStreamWriter` which buffers I/O operations but not character conversions.

Option Name | Type | Description
--- | --- | ---
Encoding | `String` | See `WriterAppender` options.
ImmediateFlush | `boolean` | See `WriterAppender` options.
Target | `String` | One of the String values System.out or System.err. The default target is System.out.

### FileAppendery

The `FileAppender`, a subclass of `WriterAppender`, appends log events into a file. The file to write to is specified by the File option. If the file already exists, it is either appended to, or truncated depending on the value of the Append option. It uses a `FileOutputStream` which is wrapped by an `OutputStreamWriter`. Note that `OutputStreamWriter` buffers I/O operations but not character conversions. To optimize character conversions one can set the BufferedIO option to true which effectively wraps the `OutputStreamWriter` with a `BufferedWriter`. Options for `FileAppender` are summarized below.

Option Name | Type | Description
--- | --- | ---
Append | `boolean` | If true, events are appended at the end of an existing file. Otherwise, if Append is false, any existing file is truncated. The Append option is set to true by default.
Encoding | `String` | See `WriterAppender` options.

BufferedIO

`boolean`

The BufferedIO option is set to false by default. If set to true, the underlying `OutputStreamWriter` is wrapped by a `BufferedWriter` object. Setting BufferedIO to true automatically sets the ImmediateFlush option to false. The name BufferedIO is slightly misleading because buffered IO is already supported by `OutputStreamWriter`. Setting BufferedIO to true has the effect of buffering I/O as well as character to raw byte conversions, saving a few CPU cycles in the process.

BufferSize | `int` | Size of `BufferedWriter` buffer. The default value is 8192.

File

`int`

The name of the file to write to. If the file does not exist, it is created.
On the MS Windows platform users frequently forget to escape back slashes. For example, the value c:\temp\test.log is not likely to be interpreted properly as '\t' is an escape sequence interpreted as a single tab character (\u0009). Correct values can be specified as c:/temp/test.log or alternatively as c:\\temp\\test.log. The File option has no default value.

By default, `FileAppender` performs a flush operation for each event, ensuring that events are immediately written to disk. Setting the ImmediateFlush option to false can drastically reduce I/O activity by letting `OutputStreamWriter` buffer bytes before writing them on disk. For short messages, we have observed 2 or 3 fold increases in logging throughput, i.e. the number of logs output per unit of time. For longer messages, the throughput gains are somewhat less dramatic, and range between 1.4 and 2 fold. Enabling the BufferedIO option, that is buffering character to byte conversions, increases performance by an additional 10% to 40% compared to only disk I/O buffering (ImmediateFlush=false). Performance varies somewhat depending on the host machine as well as JDK version. Throughput measurements are based on the `chapter4.IO` application. Please refer to the file logback-examples/src/main/java/chapter4/IO.java for actual source code. Online viewing of this file is also available.

## Logback Classic

## Logback Access