自定义 Appender
大约 4 分钟
AppenderBase
和 UnsynchronizedAppenderBase
是 Logback 框架中的两个抽象基类,专门用于定义日志输出(Appender)的基础结构。 自定义 Appender 可以通过继承这他们其中一个抽象类来实现,它为我们实现了最基础的 Appender 应该有的功能,我们只需要关注实现 append 方法即可。
在 Logback 自己提供的常用的 Appender 中 SMTPAppender
就继承自 AppenderBase
;而 ConsoleAppender
、FileAppender
、AsyncAppender
和 AsyncAppender
继承的是 UnsynchronizedAppenderBase
。
Appender 接口
Appender 接口是 Logback 框架中 appender 的最基础的抽象接口;通常并不会直接使用它,而是使用 AppenderBase
和 UnsynchronizedAppenderBase
。
public interface Appender<E> extends LifeCycle, ContextAware, FilterAttachable<E> {
/**
* Get the name of this appender. The name uniquely identifies the appender.
*/
String getName();
/**
* This is where an appender accomplishes its work. Note that the argument
* is of type Object.
* @param event
*/
void doAppend(E event) throws LogbackException;
/**
* Set the name of this appender. The name is used by other components to
* identify this appender.
*
*/
void setName(String name);
}
AppenderBase 抽象类
abstract public class AppenderBase<E> extends ContextAwareBase implements Appender<E> {
protected volatile boolean started = false;
/**
* 该保护装置可防止附加器重复调用其自己的 doAppend 方法。
*/
private boolean guard = false;
/**
* Appender 的名称,对应配置文件中 appender 标签中的 name 属性的值
*/
protected String name;
private FilterAttachableImpl<E> fai = new FilterAttachableImpl<E>();
public String getName() {
return name;
}
private int statusRepeatCount = 0;
private int exceptionCount = 0;
static final int ALLOWED_REPEATS = 5;
/*
* 这是 AppenderBase 的核心方法之一,用于将日志事件传递到 append() 方法进行处理。doAppend 会在调用 append() 之前执行一些通用的处理,比如过滤器的应用。
*/
public synchronized void doAppend(E eventObject) {
// WARNING: The guard check MUST be the first statement in the
// doAppend() method.
// prevent re-entry.
if (guard) {
return;
}
try {
guard = true;
if (!this.started) {
if (statusRepeatCount++ < ALLOWED_REPEATS) {
addStatus(new WarnStatus("Attempted to append to non started appender [" + name + "].", this));
}
return;
}
if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
return;
}
// ok, we now invoke derived class' implementation of append
this.append(eventObject);
} catch (Exception e) {
if (exceptionCount++ < ALLOWED_REPEATS) {
addError("Appender [" + name + "] failed to append.", e);
}
} finally {
guard = false;
}
}
/*
* 这是一个抽象方法,子类必须实现这个方法来定义日志事件的具体处理逻辑。eventObject 参数代表一个日志事件,通常是 ILoggingEvent 类型的实例。
*/
abstract protected void append(E eventObject);
/**
* Set the name of this appender.
*/
public void setName(String name) {
this.name = name;
}
/*
* 用于启动 Appender。在 Logback 配置文件加载时,会调用此方法来启动 Appender。如果有必要的初始化步骤,比如打开文件或连接到数据库,可以在这里完成。
*/
public void start() {
started = true;
}
/*
* 用于停止 Appender。在应用程序关闭时,会调用此方法来停止 Appender。如果需要关闭资源(如文件、网络连接等),可以在这里完成。
*/
public void stop() {
started = false;
}
/*
* 检查 Appender 是否已经启动。如果 Appender 还未启动,通常不会处理任何日志事件。
*/
public boolean isStarted() {
return started;
}
public String toString() {
return this.getClass().getName() + "[" + name + "]";
}
/**
* 添加一个过滤器。过滤器用于在日志事件被处理前,对其进行筛选或变更。只有通过过滤器的日志事件才会被传递到 append() 方法
*/
public void addFilter(Filter<E> newFilter) {
fai.addFilter(newFilter);
}
public void clearAllFilters() {
fai.clearAllFilters();
}
public List<Filter<E>> getCopyOfAttachedFiltersList() {
return fai.getCopyOfAttachedFiltersList();
}
public FilterReply getFilterChainDecision(E event) {
return fai.getFilterChainDecision(event);
}
}
局部变量 guard 的作用
guard 变量是用来记录当前线程或者任务是否已经在执行 doAppender 方法了。
为什么需要它? 在日志记录过程中,可能会发生如下情况,特别是我们在自定义 Appender 的情况下。
- 在 doAppend 方法中调用了一些可能再次触发日志记录的代码,例如格式化消息或向外部系统发送日志。
- 如果这些代码触发了日志记录,AppenderBase 会重新调用 doAppend,从而导致递归调用。
- 递归调用在没有适当处理的情况下,可能导致堆栈溢出或死循环。
Demo
自定义的 Appender
public class CustomAppender extends AppenderBase<ILoggingEvent> {
@Override
public void start() {
System.out.println("CustomAppender start");
super.start();
}
@Override
protected void append(ILoggingEvent eventObject) {
Map<String, String> mdcPropertyMap = eventObject.getMDCPropertyMap();
String requestUri = mdcPropertyMap.get(ClassicConstants.REQUEST_REQUEST_URI);
String methodHostMdcKey = mdcPropertyMap.get(ClassicConstants.REQUEST_REMOTE_HOST_MDC_KEY);
String requestUrl = mdcPropertyMap.get(ClassicConstants.REQUEST_REQUEST_URL);
String requestMethod = mdcPropertyMap.get(ClassicConstants.REQUEST_METHOD);
String requestQueryString = mdcPropertyMap.get(ClassicConstants.REQUEST_QUERY_STRING);
String requestUserAgentMdcKey = mdcPropertyMap.get(ClassicConstants.REQUEST_USER_AGENT_MDC_KEY);
String requestXForwardedFor = mdcPropertyMap.get(ClassicConstants.REQUEST_X_FORWARDED_FOR);
String message = eventObject.getFormattedMessage();
System.out.println(eventObject.getLevel().toString());
System.out.println(methodHostMdcKey);
System.out.println(requestUri);
System.out.println(requestUrl);
System.out.println(requestMethod);
System.out.println(requestQueryString);
System.out.println(requestUserAgentMdcKey);
System.out.println(requestXForwardedFor);
System.out.println(message);
}
@Override
public void stop() {
System.out.println("CustomAppender stop");
super.stop();
}
}
在 append
方法中可以访问 MDC 中缓存的内容,可以根据需要将日志、MDC 缓存内容进一步处理,如发送到远程服务器、数据库或是中间件中。
logback.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CUSTOM_APPENDER" class="cn.youyo.test.log.CustomAppender"/>
<!-- 使用 logger 方式,只有在 cn.youyo.test 包下的日志输出才到这个 appender 中 -->
<logger name="cn.youyo.test" level="debugger">
<appender-ref ref="CUSTOM_APPENDER"/>
</logger>
<!-- 使用 root 方式,所有的日志输出都回到这个 appender 中 -->
<root level="info">
<appender-ref ref="CUSTOM_APPENDER"/>
</root>
</configuration>