logback错误日志告警推送
背景
项目上线后,对于线上问题及关键业务未能及时推送给相关人员。如果没有基础的监控服务,可以自己针对异常日志进行消息推送。
技术方案
目前主流的技术方案有以下几种:
1.ELK采集分析,将异常结果发送给用户
2.接入Cat告警系统,(在日志写到磁盘前,就可以通过拦截手段拿到日志并分析日志的级别,或者在异常处理时就触发日志警告)
3.通过javaAgent skywaking使用探针技术收集
这3项告警都需要搭建一套完整的告警系统,实现成本较高,因此我们选用logback对接基础服务平台实现消息推送。(企业微信或者其他OA系统)
实现方案
1.继承Appender
logback 通过appender将日志分析并写入到磁盘上,那么我们可以在appender获取到日志内容,并判断是否需要发送告警推送。
Appender 两个基类 UnsynchronizedAppenderBase、AppenderBase
在Cat告警系统中也对应实现了具体的Appender CatLogbackAppender
public class CatLogbackAppender extends AppenderBase<ILoggingEvent> {
@Override
protected void append(ILoggingEvent event) {
try {
boolean isTraceMode = Cat.getManager().isTraceMode();
Level level = event.getLevel();
if (level.isGreaterOrEqual(Level.ERROR)) {
logError(event);
} else if (isTraceMode) {
logTrace(event);
}
} catch (Exception ex) {
throw new LogbackException(event.getFormattedMessage(), ex);
}
}
private void logError(ILoggingEvent event) {
ThrowableProxy info = (ThrowableProxy) event.getThrowableProxy();
if (info != null) {
Throwable exception = info.getThrowable();
Object message = event.getFormattedMessage();
if (message != null) {
Cat.logError(String.valueOf(message), exception);
} else {
Cat.logError(exception);
}
}
}
private void logTrace(ILoggingEvent event) {
String type = "Logback";
String name = event.getLevel().toString();
Object message = event.getFormattedMessage();
String data;
if (message instanceof Throwable) {
data = buildExceptionStack((Throwable) message);
} else {
data = event.getFormattedMessage().toString();
}
ThrowableProxy info = (ThrowableProxy) event.getThrowableProxy();
if (info != null) {
data = data + '\n' + buildExceptionStack(info.getThrowable());
}
Cat.logTrace(type, name, "0", data);
}
private String buildExceptionStack(Throwable exception) {
if (exception != null) {
StringWriter writer = new StringWriter(2048);
exception.printStackTrace(new PrintWriter(writer));
return writer.toString();
} else {
return "";
}
}
}
@Slf4j
public abstract class AlarmSendAppender extends UnsynchronizedAppenderBase<LoggingEvent> {
private static final String template_offline = "服务名: %s \n日志等级: %s \n异常时间: %s \n异常描述: %s \n异常详细信息: %s";
@Override
protected void append(LoggingEvent loggingEvent) {
List<Filter<LoggingEvent>> copyOfAttachedFiltersList = getCopyOfAttachedFiltersList();
Boolean contains = false;
for (Filter<LoggingEvent> filter : copyOfAttachedFiltersList) {
FilterReply filterReply = filter.decide(loggingEvent);
if (filterReply == FilterReply.ACCEPT) {
contains = true;
break;
}
}
if (contains) {
// 获取用户在日志中输出的语句,一般涵盖异常上下文
LoggerContextVO loggerContextVO = loggingEvent.getLoggerContextVO();
Map<String, String> propertyMap = loggerContextVO.getPropertyMap();
Boolean alarmOpen = Boolean.valueOf(propertyMap.get("alarmOpen"));
//开关关闭不推送告警
if (!alarmOpen) {
log.info("错误日志告警消息已关不进行推送");
return;
}
String alarmReceivers = propertyMap.get("msgAlarmReceivers");
IThrowableProxy proxy = loggingEvent.getThrowableProxy();
// 获取异常堆栈
Throwable t = ((ThrowableProxy) proxy).getThrowable();
// 原文链接:https://blog.csdn.net/weixin_45423451/article/details/123203736
//发送第三放告警消息
String messageText = String.format(template_offline,
loggerContextVO.getName(),
loggingEvent.getLevel(),
DateUtils.format(new Date(),"yyyy-MM-dd HH:mm:sss"),
t.toString().length() <= 1000 ? t : t.toString().substring(0, 1000),
Arrays.toString(t.getStackTrace()).length() <= 2000 ? Arrays.toString(t.getStackTrace()) : Arrays.toString(t.getStackTrace()).substring(0, 1888));
System.out.println("-----" + messageText);
String[] receivers = alarmReceivers.split(",");
//进行参数组装
AlarmMessage alarmMessage = new AlarmMessage();
alarmMessage.setMessageText(messageText);
alarmMessage.setPropertyMap(propertyMap);
alarmMessage.setReceiverList(Arrays.asList(receivers));
//发送消息告警
sendAlarmMessage(alarmMessage);
}
}
/**
* 模板设计方法,可以对接各种平台进行抽象方法推送告警消息
* @param alarmMessage
*/
protected abstract void sendAlarmMessage(AlarmMessage alarmMessage);
}
/**
* <p>
* 告警信息
* </p>
*
* @author:
* @date: 2023-01-17
* @desc:
*/
@Data
public class AlarmMessage {
/**
* 告警标题
*/
private String title;
/**
* 告警内容
*/
private String messageText;
/**
* 接收者
*/
private List<String> receiverList;
/**
* 变量map
*/
private Map<String, String> propertyMap;
/**
* 消息类型 1文本消息
*/
private String messageType;
}
2.具体对接服务
可以对接各类消息或者企业微信实现推送,logback的SMTPAppender已经邮件推送。
/**
* <p>
* 对接消息中心告警
* </p>
*
* @author:
* @date: 2023-01-17
* @desc:
*/
@Slf4j
public class MsgCenterAlarmSendAppender extends AlarmSendAppender{
@Override
protected void sendAlarmMessage(AlarmMessage alarmMessage) {
//日志比较频繁,使用多线程处理
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
//todo 具体处理
System.out.println("=========告警信息发送结果:" + result);
}
}
});
}
}
}
3.logback配置
1. logback-spring.xml的加载顺序早于springboot的application.yml 配置文件当然读不到application.yml文件中的值了。通过springProperty标签来引用。
2. 配置具体的appender
3. 在root中引入