How to Write a Custom Appender in Log4j2
Ever wanted to test whether a log statement is triggered? Or whether the format is the way you want? In this post, we’re going to create a custom log appender so we can be sure logging is behaving the way we expect.
/* package declaration, imports... */
@Plugin(name = "CustomListAppender",
        category = Core.CATEGORY_NAME,
        elementType = Appender.ELEMENT_TYPE,
        printObject = true)
public final class CustomListAppender extends AbstractAppender {
    // for storing the log events
    private List<LogEvent> events = new ArrayList<>();
    protected CustomListAppender(
            String name,
            Filter filter,
            Layout<? extends Serializable> layout,
            boolean ignoreExceptions) {
        super(name, filter, layout, ignoreExceptions);
    }
    @Override
    public void append(LogEvent event) {
        if (event instanceof MutableLogEvent) {
            events.add(((MutableLogEvent) event).createMemento());
        } else {
            events.add(event);
        }
    }
    public List<LogEvent> getEvents() {
        return events;
    }
    @PluginFactory
    public static CustomListAppender createAppender(
            @PluginAttribute("name") String name,
            @PluginElement("Layout") Layout<? extends Serializable> layout,
            @PluginElement("Filter") Filter filter) {
        if (name == null) {
            LOGGER.error("No name provided for TestLoggerAppender");
            return null;
        }
        if (layout == null) layout = PatternLayout.createDefaultLayout();
        return new CustomListAppender(name, filter, layout, true);
    }
}
Our CustomListAppender extends AbstractAppender, because that
implements a lot of the methods from the Appender interface for us
that we would otherwise have to implement ourselves.
The @Plugin annotation identifies this class a plugin that should be
picked up by the PluginManager:
- The nameattribute defines the name of the appender that can be used in the configuration.
- The categoryattribute should be"Core", because “Core plugins are those that are directly represented by an element in a configuration file, such as an Appender, Layout, Logger or Filter” (source). And we are creating an appender.
- The elementTypeattribute defines which type of element in the Core category this plugin should be. In our case,"appender".
- The printObjectattribute defines whether our custom plugin class defines a usefultoString()method. We do, because theAbstractAppenderclass we’re extending is taking care of that for us.
We implement the Appender#append(LogEvent) method to add each event to
our events list. If the LogEvent happens to be mutable, we must take
care to create an immutable copy of the event, otherwise subsequent log
events will overwrite it (we will get a list of, say, three log events
that are all referencing the same object). We also add a simple getter
method to retrieve all log events.
For the PluginManager to create our custom plugin, it needs a way to
instantiate it. log4j2 uses a factory method for that, indicated by the
annotation @PluginFactory. An appender contains attributes, such as a
name, and other elements, such as layouts and filters. To allow for
these, we use the corresponding annotations @PluginAttribute to
indicate that a parameter represents an attribute, and @PluginElement
to indicate that a parameter represents an element.
To log errors that might occur during this setup, we can make use of the
StatusLogger. This logger is available as LOGGER, and is defined in
one of the parents of our custom plugin, AbstractLifeCycle. (The level
of log messages that should be visible can be adjusted in the
<Configuration status="warn" ...> element.)
Configuration
Configuration:
  packages: com.relentlesscoding.logging.plugins
  status: warn
  appenders:
    Console:
      name: STDOUT
    CustomListAppender:
      name: MyVeryOwnListAppender
  Loggers:
    logger:
      -
        name: com.relentlesscoding.logging
        level: info
        AppenderRef:
          ref: MyVeryOwnListAppender
    Root:
      level: error
      AppenderRef:
        ref: STDOUT
The packages attribute on the Configuration element indicates the
package that should be scanned by the PluginManager for custom plugins
during initialization.
How to use our custom list appender?
private CustomListAppender appender;
@Before
public void setupLogging() {
    LoggerContext context = LoggerContext.getContext(false);
    Configuration configuration = context.getConfiguration();
    appender = configuration.getAppender("MyVeryOwnListAppender");
    appender.getEvents().clear();
}
When we run tests now, we are able to see all logged events by calling
appender.getEvents(). Before each test, we take care to clear the list
of the previous log statements.