需求
之前博客有提到,就是需要监控程序异常,因为这个是后台运行,无法监控程序异常,所以需要监控应用异常是否出现大面积报错。
应用每天记录报错次数,如果大于预定次数,则发送邮件通知团队处理,发送之后就不需要进行记录了,当天不需要进行通知了,隔天再进行通知。
实现
直接上代码,比较简单,记录一下:
@Service
@Slf4j
@EnableConfigurationProperties(MonitorConfig.class)
public class MonitorExceptionService {
@Resource
private JavaMailSender mailSender;
@Resource
private MonitorConfig monitorConfig;
@Value("${spring.profiles.active:test}")
private String profile;
private volatile LocalDate lastDate;
private volatile int errorTimes = 0;
private volatile boolean mailFlag = false;
private final Map<String, List<String>> ERROR_STACK = new ConcurrentHashMap<>();
@Async
synchronized public void recordError(Exception e) {
LocalDate now = LocalDate.now(ZoneId.of("Asia/Shanghai"));
if (Objects.isNull(lastDate) || now.isAfter(lastDate)) {
lastDate = now;
errorTimes = 0;
mailFlag = false;
ERROR_STACK.clear();
log.info("clear old data done!");
}
if (mailFlag) {
return;
}
errorTimes ++;
String exceptionName = e.getClass().getName();
List<String> list = ERROR_STACK.get(exceptionName);
if (Objects.isNull(list)) {
list = new ArrayList<>();
ERROR_STACK.put(exceptionName, list);
}
list.add(e.getMessage());
if (errorTimes >= monitorConfig.getMaxErrorTimes()) {
try {
sendEmail();
} catch (MessagingException ex) {
log.error("send mail notification error", ex);
}
}
}
private void sendEmail() throws MessagingException {
log.info("start send email");
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,true, StandardCharsets.UTF_8.name());
messageHelper.setFrom(monitorConfig.getMailFrom());
messageHelper.setTo(monitorConfig.getMailTo());
messageHelper.setSubject(buildSubject());
messageHelper.setText(monitorConfig.getMailTemplate());
messageHelper.addAttachment("error.txt", new ByteArrayDataSource(buildAttachment(), "application/octet-stream"));
mailSender.send(mimeMessage);
log.info("end send email");
mailFlag = true;
}
private byte[] buildAttachment() {
StringBuilder sb = new StringBuilder();
ERROR_STACK.forEach((err, list) -> {
sb.append(err).append(": \n");
for (int i = 0; i < list.size(); i++) {
sb.append(" ").append(i).append(". ").append(list.get(i)).append("\n");
}
sb.append("\n");
});
return sb.toString().getBytes(StandardCharsets.UTF_8);
}
private String buildSubject() {
String format = LocalDate.now(ZoneId.of("Asia/Shanghai")).format(DateTimeFormatter.ISO_LOCAL_DATE);
return String.format("SYSTEM ERROR at %s (%s)",profile, format);
}
}
程序是异步处理,但是需要上锁,因为是单节点,只需要这一个就够了,双节点问题也不大,就是发送两次而已,也可以换成分布式锁,没有条件的话可以换成数据库的锁即可。