How to Create a Custom Appender in log4j2?

JavaLoggingLog4jLog4j2

Java Problem Overview


As disscussed in this link : https://stackoverflow.com/questions/6072389/how-to-create-a-own-appender-in-log4j

For creating a custom appender in log4j 1.x we have to extend the AppenderSkeleton class and implements its append method.

Similarly How we can create a custom appender in log4j2 as we dont have AppenderSkelton class to extend and all other appender extend AppenderBase class .

Java Solutions


Solution 1 - Java

This works quite differently in log4j2 than in log4j-1.2.

In log4j2, you would create a plugin for this. The manual has an explanation with an example for a custom appender here: http://logging.apache.org/log4j/2.x/manual/extending.html#Appenders

It may be convenient to extend org.apache.logging.log4j.core.appender.AbstractAppender, but this is not required.

When you annotate your custom Appender class with @Plugin(name="MyCustomAppender", ...., the plugin name becomes the configuration element name, so a configuration with your custom appender would then look like this:

<Configuration packages="com.yourcompany.yourcustomappenderpackage">
  <Appenders>
    <MyCustomAppender name="ABC" otherAttribute="...">
    ...
  </Appenders>
  <Loggers><Root><AppenderRef ref="ABC" /></Root></Loggers>
</Configuration>

Note that the packages attribute on the configuration is a comma-separated list of all the packages with custom log4j2 plugins. Log4j2 will search these packages in the classpath for classes annotated with @Plugin.

Here is a sample custom appender that prints to the console:

package com.yourcompany.yourcustomappenderpackage;

import java.io.Serializable;
import java.util.concurrent.locks.*;
import org.apache.logging.log4j.core.*;
import org.apache.logging.log4j.core.config.plugins.*;
import org.apache.logging.log4j.core.layout.PatternLayout;

// note: class name need not match the @Plugin name.
@Plugin(name="MyCustomAppender", category="Core", elementType="appender", printObject=true)
public final class MyCustomAppenderImpl extends AbstractAppender {

    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();

	protected MyCustomAppenderImpl(String name, Filter filter,
			Layout<? extends Serializable> layout, final boolean ignoreExceptions) {
		super(name, filter, layout, ignoreExceptions);
	}

    // The append method is where the appender does the work.
    // Given a log event, you are free to do with it what you want.
    // This example demonstrates:
    // 1. Concurrency: this method may be called by multiple threads concurrently
    // 2. How to use layouts
    // 3. Error handling
	@Override
	public void append(LogEvent event) {
        readLock.lock();
        try {
            final byte[] bytes = getLayout().toByteArray(event);
			System.out.write(bytes);
		} catch (Exception ex) {
			if (!ignoreExceptions()) {
                throw new AppenderLoggingException(ex);
			}
        } finally {
            readLock.unlock();
		}
	}

    // Your custom appender needs to declare a factory method
    // annotated with `@PluginFactory`. Log4j will parse the configuration
    // and call this factory method to construct an appender instance with
    // the configured attributes.
	@PluginFactory
	public static MyCustomAppenderImpl createAppender(
			@PluginAttribute("name") String name,
			@PluginElement("Layout") Layout<? extends Serializable> layout,
			@PluginElement("Filter") final Filter filter,
			@PluginAttribute("otherAttribute") String otherAttribute) {
		if (name == null) {
			LOGGER.error("No name provided for MyCustomAppenderImpl");
			return null;
		}
		if (layout == null) {
			layout = PatternLayout.createDefaultLayout();
		}
		return new MyCustomAppenderImpl(name, filter, layout, true);
	}
}

For more details on plugins: http://logging.apache.org/log4j/2.x/manual/plugins.html

If the manual is not enough, it may be useful to look at the source code for the built-in appenders in log4j-core.

Solution 2 - Java

> It looks like plugin appenders are scanned at startup and cannot be added during runtime. Is that true?

to add new appender while running you can use monitorInterval property to update log configuration i.e. every 60 sec:

    <Configuration monitorInterval="60">

Solution 3 - Java

As you pointed out AppenderSkeleton is not available anymore so the solutions in https://stackoverflow.com/questions/6072389/how-to-create-my-own-appender-in-log4j will not work.

Using Mockito, or similar library to create an Appender with an ArgumentCaptor will not work if you're expecting multiple logging messages because the MutableLogEvent is reused over multiple log messages.

The most generic solution I found for log4j2 is to provide a mock implementation that records all the messages. It does not require any additional libraries like Mockito or JMockit.

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.AbstractAppender;    

private static MockedAppender mockedAppender;
private static Logger logger;

@Before
public void setup() {
    mockedAppender.message.clear();
}

/**
 * For some reason mvn test will not work if this is @Before, but in eclipse it works! As a
 * result, we use @BeforeClass.
 */
@BeforeClass
public static void setupClass() {
    mockedAppender = new MockedAppender();
    logger = (Logger)LogManager.getLogger(ClassWithLoggingToTest.class);
    logger.addAppender(mockedAppender);
    logger.setLevel(Level.INFO);
}

@AfterClass
public static void teardown() {
    logger.removeAppender(mockedAppender);
}

@Test
public void test() {
    // do something that causes logs
    for (String e : mockedAppender.message) {
        // add asserts for the log messages
    }
}

private static class MockedAppender extends AbstractAppender {

    List<String> message = new ArrayList<>();

    protected MockedAppender() {
        super("MockedAppender", null, null);
    }

    @Override
    public void append(LogEvent event) {
        message.add(event.getMessage().getFormattedMessage());
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionsaurabh goyalView Question on Stackoverflow
Solution 1 - JavaRemko PopmaView Answer on Stackoverflow
Solution 2 - JavaJavoslawView Answer on Stackoverflow
Solution 3 - JavajosephView Answer on Stackoverflow