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}