需求
系统报ERROR错误时,能实时做到消息通知。
思路
当前项目比较小,不想过多的依赖额外的第三方组件。
项目在ERROR时,都会打印ERROR日志,所以可以在log4j接收到ERROR日志请求时,发送通知消息。
实践
Filter是log4j2的扩展点,从图中(图片来自如何编写Log4j2脱敏插件)流程可以看到,Filter分别可以在全局、Logger、Appender三个地方做过滤。
三个地方对应的log4j.xml配置地方如下:
log4j提供了过滤器的基类AbstractFilter:全局过滤器入口方法是filter(Logger logger, Level level, Marker marker, String msg, Object... params),msg是填充参数之前的内容,params是参数列表,包含Throwable对象。
Logger和Appender入口方式是filter(final LogEvent event),通过event.getMessage().getFormattedMessage()取到填充参数之后的内容,通过event.getThrown()获取异常对象。
代码@Plugin(name = "ErrorNotifyFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
public class ErrorNotifyLog4j2Filter extends AbstractFilter {
// 默认打印异常堆栈长度
public static final int DEFAULT_PRINT_EXCEPTIONSTACK_LENGTH = 3;
// 默认消息最长长度
// 2020-07-09 测试企业微信允许最大长度为4000
public static final int DEFAULT_MAX_MSG_LENGTH = 4000;
private String projectName;
private List rtxReceivers;
private boolean noPrintExceptionStack;
private int printExceptionStackLength;
private int maxMsgLength;
private Object lock = new Object();
private ErrorNotifyLog4j2Filter(String projectName, String rtxReceivers,
boolean noPrintExceptionStack, int printExceptionStackLength, int maxMsgLength) {
super();
this.projectName = projectName;
this.rtxReceivers = Lists.newArrayList(rtxReceivers.split(","));
this.noPrintExceptionStack = noPrintExceptionStack;
this.printExceptionStackLength = printExceptionStackLength <= 0 ? DEFAULT_PRINT_EXCEPTIONSTACK_LENGTH : printExceptionStackLength;
this.maxMsgLength = (maxMsgLength <= 0 || maxMsgLength > DEFAULT_MAX_MSG_LENGTH) ? DEFAULT_MAX_MSG_LENGTH : maxMsgLength;
}
@Override
public Result filter(LogEvent event) {
notify(event.getLevel(), event.getMessage().getFormattedMessage(), event.getThrown());
return super.filter(event);
}
@Override
public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) {
notify(level, msg.getFormattedMessage(), t);
return super.filter(logger, level, marker, msg, t);
}
@Override
public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) {
notify(level, msg == null ? "" : msg.toString(), t);
return super.filter(logger, level, marker, msg, t);
}
@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) {
notify(level, msg, getExceptionParam(params));
return super.filter(logger, level, marker, msg, params);
}
/**
* @param level
* @param msg
* @param t
* @author
* @date
*/
private void notify(Level level, String msg, Throwable t) {
try {
if (level == null || level.intLevel() != Level.ERROR.intLevel()) {
return;
}
if (StringUtils.isBlank(msg) && t == null) {
return;
}
Log4j2AsyncExecutor.executorService.submit(() -> {
try {
String notifyMsg = getNotifyMsg(msg, t); MessageUtil.postMessage(Lists.newArrayList(MessageTypeEnum.RTX),
rtxReceivers,
getNotifyTitle(),
notifyMsg);
} catch (Exception ignoreException) {
ignoreException.printStackTrace();
}
});
} catch (Throwable ignoreException) {
ignoreException.printStackTrace();
}
}
/**
* @param params
* @return java.lang.Throwable
* @author
* @date
*/
private Throwable getExceptionParam(Object... params) {
if (params == null || params.length == 0) {
return null;
}
for (Object param : params) {
if (param instanceof Throwable) {
return (Throwable) param;
}
}
return null;
}
/**
* 如果开启堆栈信息,能让告警更清晰,但同样的也就降低了性能
*
* @param msg
* @param t
* @return java.lang.String
* @author
* @date
*/
private String getNotifyMsg(String msg, Throwable t) {
String errorMsg = "信息:" + (msg == null ? "" : msg);
String exceptionMsg = "";<