Appender是Log4j2的日志輸出方式,Log4j2提供了很多自帶的Appender,包括控制台的、文件的、郵件的,kafka的等等。
但是這樣也是不能覆蓋全部需求的,需要提供自定義的Appender。
考慮我們需要提供的Appender所需要的功能:
1、分析Perf4j的日志
2、將分析的日志按時間間隔輸出到指定文件
提供自定義的Appender,需繼承AbstractAppender類,實現append方法。
考慮繼承FileAppender的實現將日志輸出到指定的文件之中。
GenericAsyncCoalescingStatisticsAppender 提供一個無參的構造方法,可以提供分析日志的功能。
import java.io.Serializable; import java.util.HashMap; import java.util.Map; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender; import org.apache.logging.log4j.core.appender.FileManager; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.core.net.Advertiser; import org.apache.logging.log4j.core.util.Booleans; import org.apache.logging.log4j.core.util.Integers; import org.apache.logging.log4j.message.ObjectMessage; import org.perf4j.GroupedTimingStatistics; import org.perf4j.helpers.GenericAsyncCoalescingStatisticsAppender; /** * 適配Perf4j的Appender * * 解決目前Log4j2 沒有提供支持Perf4j的Appender的問題 * <p> * PerfjFileAppender提供類似{@link org.apache.logging.log4j.core.appender.FileAppender FileAppender}的實現 *,並且內置Perfj的{@link org.perf4j.helpers.GenericAsyncCoalescingStatisticsAppender GenericAsyncCoalescingStatisticsAppender}對象 * </p> * PerfjFileAppender的append方法,將日志信息提供到<code>GenericAsyncCoalescingStatisticsAppender</code>對象baseImplementation處理 * 等到<code>GenericAsyncCoalescingStatisticsAppender</code>的silenceTime周期到了之后,就將<code>GenericAsyncCoalescingStatisticsAppender</code> * 的處理結果輸出到PerfjFileAppender的父類<code>AbstractOutputStreamAppender</code>的append方法處理,該Appender將負責把統計結果輸出到目標文件 * * <p> * <b>注意:</b> * <p> * 該實現中,始終會keep着第一次接收到的Perf4j的日志對象, * 並在每個輸出的時機將GenericAsyncCoalescingStatisticsAppender輸出的信息設置到日志對象, * 這就導致在應用啟動之后,需要觸發Perf4j才能輸出信息,不然連空統計信息也不會輸出 * </p> * </p> * @author sunwei3 * */ @Plugin(name = "Perf4jAppender", category = "Core", elementType = "appender", printObject = true) public class Perf4jFileAppender extends AbstractOutputStreamAppender<FileManager> { private final String fileName; private final Advertiser advertiser; private Object advertisement; private static final int DEFAULT_BUFFER_SIZE = 8192; private static final long serialVersionUID = 1L; private Log4jLogEvent result = null; private final GenericAsyncCoalescingStatisticsAppender baseImplementation = new GenericAsyncCoalescingStatisticsAppender(); protected Perf4jFileAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter, final FileManager manager, final String filename, final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser,long timeSlice) { super(name, layout, filter, ignoreExceptions, immediateFlush, manager); baseImplementation.setTimeSlice(timeSlice); if (advertiser != null) { final Map<String, String> configuration = new HashMap<>(layout.getContentFormat()); configuration.putAll(manager.getContentFormat()); configuration.put("contentType", layout.getContentType()); configuration.put("name", name); advertisement = advertiser.advertise(configuration); } this.fileName = filename; this.advertiser = advertiser; baseImplementation.start(new GenericAsyncCoalescingStatisticsAppender.GroupedTimingStatisticsHandler() { public void handle(GroupedTimingStatistics statistics) { if(result == null) { return; } //已經處理完timeSlice時間段的數據,需要把統計結果輸出到文件 fileAppend(result.asBuilder().setMessage(new ObjectMessage(statistics.toString())).build()); } public void error(String errorMessage) { getHandler().error(errorMessage); } }); } @Override public void stop() { //先結束GenericAsyncCoalescingStatisticsAppender baseImplementation.stop(); super.stop(); if (advertiser != null) { advertiser.unadvertise(advertisement); } } public void append(LogEvent event) { //仿照GenericAsyncCoalescingStatisticsAppender的實現,標准的Perf4j的輸出是以start開頭的 if(result == null && String.valueOf(event.getMessage().getFormattedMessage()).startsWith("start")){ //為了避免每次構造Log4jLogEvent,記錄下第一次的Log4jLogEvent,每次設置其Message就可以輸出到文件了 result = new Log4jLogEvent.Builder(event).build(); } //將日志交與GenericAsyncCoalescingStatisticsAppender處理,符合條件的日志將會被處理 baseImplementation.append(String.valueOf(event.getMessage().getFormattedMessage())); } public void fileAppend(LogEvent event) { super.append(event); } public String getFileName() { return this.fileName; } @PluginFactory public static Perf4jFileAppender createAppender( // @formatter:off @PluginAttribute("fileName") final String fileName, @PluginAttribute("append") final String append, @PluginAttribute("locking") final String locking, @PluginAttribute("name") final String name, @PluginAttribute("immediateFlush") final String immediateFlush, @PluginAttribute("ignoreExceptions") final String ignore, @PluginAttribute("bufferedIo") final String bufferedIo, @PluginAttribute("bufferSize") final String bufferSizeStr, @PluginElement("Layout") Layout<? extends Serializable> layout, @PluginElement("Filter") final Filter filter, @PluginAttribute("advertise") final String advertise, @PluginAttribute("advertiseUri") final String advertiseUri, @PluginConfiguration final Configuration config, @PluginAttribute("timeSlice") final long timeSlice ) { // @formatter:on final boolean isAppend = Booleans.parseBoolean(append, true); final boolean isLocking = Boolean.parseBoolean(locking); boolean isBuffered = Booleans.parseBoolean(bufferedIo, true); final boolean isAdvertise = Boolean.parseBoolean(advertise); if (isLocking && isBuffered) { if (bufferedIo != null) { LOGGER.warn("Locking and buffering are mutually exclusive. No buffering will occur for " + fileName); } isBuffered = false; } final int bufferSize = Integers.parseInt(bufferSizeStr, DEFAULT_BUFFER_SIZE); if (!isBuffered && bufferSize > 0) { LOGGER.warn("The bufferSize is set to {} but bufferedIO is not true: {}", bufferSize, bufferedIo); } final boolean isFlush = Booleans.parseBoolean(immediateFlush, true); final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); if (name == null) { LOGGER.error("No name provided for FileAppender"); return null; } if (fileName == null) { LOGGER.error("No filename provided for FileAppender with name " + name); return null; } if (layout == null) { layout = PatternLayout.createDefaultLayout(); } final FileManager manager = FileManager.getFileManager(fileName, isAppend, isLocking, isBuffered, advertiseUri, layout, bufferSize); if (manager == null) { return null; } return new Perf4jFileAppender(name, layout, filter, manager, fileName, ignoreExceptions, isFlush, isAdvertise ? config.getAdvertiser() : null,timeSlice); } }
log4j2的配置文件為:
<Perf4jAppender name="perfsStatistics" TimeSlice="1800000"
fileName="${profile.log.root.path}/${profile.log.name.web}/perfStats.log">
<PatternLayout>
<Pattern>%m%n</Pattern>
</PatternLayout>
</Perf4jAppender>