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>
