001package com.randomnoun.common.log4j2;
002
003/* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a
004 * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html)
005 */
006import java.util.ArrayList;
007import java.util.LinkedList;
008import java.util.List;
009
010import org.apache.logging.log4j.core.Appender;
011import org.apache.logging.log4j.core.Core;
012import org.apache.logging.log4j.core.LogEvent;
013import org.apache.logging.log4j.core.appender.AbstractAppender;
014import org.apache.logging.log4j.core.config.Property;
015import org.apache.logging.log4j.core.config.plugins.Plugin;
016import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
017import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
018import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
019import org.apache.logging.log4j.core.config.plugins.PluginFactory;
020
021
022/**
023 * Log4j2 appender to capture logging events in an in-memory List. 
024 *
025 * <p>Maybe log4j2 already has one. That'd be nice.
026 * 
027 * <p>Apparently not.
028 *
029 * <p>The code in this class is based on the WriterAppender class
030 * in the log4j source code.
031 * 
032 * <p>This appender can be configured using the property "maximumLogSize" 
033 * which limits the number of logging events captured by this class (old events
034 * are popped off the list when the list becomes full).
035 * 
036 * <p>Use the {@link #getLogEvents()} to return the List of events written
037 * to this class. This list is a <b>copy</b> of the list contained within this class,
038 * so it can safely be iterated over even if logging events are still
039 * occurring in an application.
040 * 
041 * <p>Example log4j configuration:
042 * <pre class="code">
043 * log4j.rootLogger=DEBUG, MEMORY
044 * 
045 * log4j.appender.MEMORY=com.randomnoun.common.log4j.MemoryAppender
046 * log4j.appender.MEMORY.MaximumLogSize=1000
047 * </pre>
048 * 
049 * You can then obtain the list of events via the code:
050 * <pre>
051 * MemoryAppender memoryAppender = (MemoryAppender) Logger.getRootLogger().getAppender("MEMORY");
052 * List events = memoryAppender.getEvents();
053 * </pre>
054 *
055 * @see <a href="http://www.randomnoun.com/wp/2013/01/13/logging/">http://www.randomnoun.com/wp/2013/01/13/logging/</a>
056 * @author knoxg
057 */
058@Plugin(name = MemoryAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
059public class MemoryAppender extends AbstractAppender {
060
061        public static final String PLUGIN_NAME = "Memory";
062        
063    public final static int DEFAULT_LOG_SIZE = 1000;
064    private int maximumLogSize = DEFAULT_LOG_SIZE;
065    private LinkedList<LogEvent> logEvents;
066
067        
068    /**
069     * Builds HttpAppender instances.
070     * @param <B> The type to build
071     */
072    // this builder appears to create another builder, which some would consider pretty strange
073    public static class MemoryAppenderBuilder<B extends MemoryAppenderBuilder<B>> 
074        extends AbstractAppender.Builder<B>
075        implements org.apache.logging.log4j.core.util.Builder<MemoryAppender> {
076
077
078        @PluginBuilderAttribute
079        private int maximumLogSize = 1000;
080
081        @Override
082        public MemoryAppender build() {
083                MemoryAppender ma = new MemoryAppender(getName());
084                ma.setMaximumLogSize(maximumLogSize);
085                return ma;
086        }
087
088        public int getMaximumLogSize() {
089            return maximumLogSize;
090        }
091
092        public B setMaximumLogSize(final int maximumLogSize) {
093            this.maximumLogSize = maximumLogSize;
094            return asBuilder();
095        }
096    }
097
098    /**
099     * @return a builder for a MemoryAppender.
100     */
101    @PluginBuilderFactory
102    public static <B extends MemoryAppenderBuilder<B>> B newBuilder() {
103        return new MemoryAppenderBuilder<B>().asBuilder();
104    }
105    
106    // well log4j2 went all-in on the annotations, didn't they
107    
108        @PluginFactory
109        public static MemoryAppender createAppender(
110                @PluginAttribute(value = "name", defaultString = "null") final String name) 
111        {
112                return new MemoryAppender(name);
113        }
114
115        private MemoryAppender(final String name) {
116                super(name, null, null, true, Property.EMPTY_ARRAY);
117                logEvents = new LinkedList<LogEvent>();
118        }
119        
120    /** Set the maximum log size */
121    public void setMaximumLogSize(int logSize)
122    {
123        this.maximumLogSize = logSize;
124    }
125
126    /** Return the maximum log size */
127    public int getMaximumLogSize()
128    {
129        return maximumLogSize;
130    }
131    
132
133        @Override
134        public void append(final LogEvent event) {
135                synchronized(logEvents) {
136                if (logEvents.size() >= maximumLogSize) {
137                        logEvents.removeLast();
138                }
139                logEvents.addFirst(event);
140        }
141        }
142    
143        /** Returns a list of logging events captured by this appender. (This list 
144     *  is cloned in order to prevent ConcurrentModificationExceptions occuring
145     *  whilst iterating through it) */
146    public List<LogEvent> getLogEvents()
147    {
148        return new ArrayList<LogEvent>(logEvents);
149    }
150    
151}