Spring Boot项目监控异常,发送邮件
- 需求
- 实现
- 打完收工!
需求
之前博客有提到,就是需要监控程序异常,因为这个是后台运行,无法监控程序异常,所以需要监控应用异常是否出现大面积报错。
应用每天记录报错次数,如果大于预定次数,则发送邮件通知团队处理,发送之后就不需要进行记录了,当天不需要进行通知了,隔天再进行通知。
实现
直接上代码,比较简单,记录一下:
@Service
@Slf4j
@EnableConfigurationProperties(MonitorConfig.class)
public class MonitorExceptionService {@Resourceprivate JavaMailSender mailSender;@Resourceprivate 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<>();@Asyncsynchronized 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);}
}
程序是异步处理,但是需要上锁,因为是单节点,只需要这一个就够了,双节点问题也不大,就是发送两次而已,也可以换成分布式锁,没有条件的话可以换成数据库的锁即可。