通过自定义实现Appender,实现日志的实时监控,并通过邮件、飞书机器人等方式提醒。
基础来源: https://juejin.cn/post/7222556620977635389
日志格式化代码来源: plumelog 的 LiteAppender
自定义实现Appender
package com.tzcxyh.appenders;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import org.slf4j.helpers.MessageFormatter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* @Author xuyuhang
* @Date 2023/6/8 3:04 PM
* @Description 自定义异常告警Appender
**/
public class ErrorWarnAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
protected void append(ILoggingEvent iLoggingEvent) {
//为空,不告警
if(null == iLoggingEvent){
return;
}
/**
* 下面去调用上报的时候,建议添加一些控制
* 例如:
* 过滤掉上报工具类的错误日志(死循环) if(iLoggingEvent.getLoggerName()..equals("com.test.SendUtils"))
* 过滤掉10分钟内重复上报的数据 缓存
*/
//输出信息看看
System.out.println("error method :\n" + getMethod(iLoggingEvent));
System.out.println("error message: \n" + getMessage(iLoggingEvent));
}
//=========== 以下代码来自plumelog的 LiteAppender================
/**
* 获取抛出异常的方法
* @param logEvent
* @return
*/
private static String getMethod(ILoggingEvent logEvent){
StackTraceElement[] stackTraceElements = logEvent.getCallerData();
if (stackTraceElements.length > 0) {
StackTraceElement stackTraceElement = stackTraceElements[0];
String method = stackTraceElement.getMethodName();
String line = String.valueOf(stackTraceElement.getLineNumber());
return method + "(" + stackTraceElement.getFileName() + ":" + line + ")";
} else {
return logEvent.getThreadName();
}
}
/**
* 获取日志堆栈信息
* @param logEvent
* @return
*/
private static String getMessage(ILoggingEvent logEvent) {
/**
* 如果是Error级别日志,取错误日志信息
* log.error("FormattedMessage", e), 获取e中的详细信息
*/
if (logEvent.getLevel().equals(Level.ERROR)) {
if (logEvent.getThrowableProxy() != null) {
ThrowableProxy throwableProxy = (ThrowableProxy)logEvent.getThrowableProxy();
String[] args = new String[]{logEvent.getFormattedMessage() + "\n" + erroStackTrace(throwableProxy.getThrowable()).toString()};
return packageMessage("{}", args);
}
Object[] args = logEvent.getArgumentArray();
if (args != null) {
for(int i = 0; i < args.length; ++i) {
if (args[i] instanceof Throwable) {
args[i] = erroStackTrace(args[i]);
}
}
return packageMessage(logEvent.getMessage(), args);
}
}
//直接返回 FormattedMessage
return logEvent.getFormattedMessage();
}
/**
* 组装成String格式
* 如果 message 中包含 {} 使用 slf4j 的 MessageFormatter
* 如果没有,使用自定义的 buildArgsString
* @param message
* @param args
* @return
*/
private static String packageMessage(String message, Object[] args) {
return message != null && message.contains("{}") ? MessageFormatter.arrayFormat(message, args).getMessage() : buildArgsString(message, args);
}
/**
* 将 message 和 args 合并成 String
* @param message
* @param args
* @return
*/
public static String buildArgsString(String message, Object[] args) {
StringBuilder builder = new StringBuilder(128);
builder.append(message);
Object[] var3 = args;
int var4 = args.length;
for(int var5 = 0; var5 < var4; ++var5) {
Object arg = var3[var5];
builder.append("\n").append(arg);
}
return builder.toString();
}
/**
* 错误堆栈数据
* @param obj
* @return
*/
public static Object erroStackTrace(Object obj) {
if (obj instanceof Exception) {
Exception eObj = (Exception)obj;
StringWriter sw = null;
PrintWriter pw = null;
Object var5;
try {
sw = new StringWriter();
pw = new PrintWriter(sw);
String exceptionStack = "\r\n";
eObj.printStackTrace(pw);
exceptionStack = sw.toString();
String var17 = exceptionStack;
return var17;
} catch (Exception var15) {
var15.printStackTrace();
var5 = obj;
} finally {
try {
pw.close();
sw.close();
} catch (IOException var14) {
var14.printStackTrace();
}
}
return var5;
} else {
return obj;
}
}
}
使用
配置logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="ERROR_WARN" class="com.tzcxyh.appenders.ErrorWarnAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!--配置只需要Error级别-->
<level>ERROR</level>
</filter>
</appender>
<logger name="com.tzcxyh.logback">
<appender-ref ref="ERROR_WARN"/>
</logger>
</configuration>
测试使用
注意
1、循环调用问题
在Appender中使用工具类,向监控后台发送错误日志数据,发送飞书、钉钉、邮件等通知用户时,容易出现循环调用
/**
* 飞书告警推送
*/
@Slf4j
@Component
public class FeishuUtil {
@Resource
private RestTemplate restTemplate;
/**
* send alert msg to receiver
*/
public void sendNoticeMsg(String alertContent) {
try {
String feishuHookUrl = PropertiesUtil.getInstanse().get("feishuHookUrl");
FeishuWebHookDto feishuWebHookDto = new FeishuWebHookDto();
FeishuWebHookDto.Content content = new FeishuWebHookDto.Content();
ResponseEntity<String> entity = restTemplate.postForEntity(feishuHookUrl, feishuWebHookDto, String.class);
if (entity.getStatusCode() == HttpStatus.OK) {
log.info("Send feiShu webHook: {} Success", feishuHookUrl);
} else {
log.warn("Send feiShu webHook: {} Failed: {}", feishuHookUrl, entity.getBody());
}
} catch (Exception e) {
//向日志中答应错误日志,然后开始循环发送。。。。
log.error("飞书发送消息出现异常", e);
}
}
}