1. 错误日志告警实战
1.1. 需求
为了更方便的实时了解系统报错情况,我开始寻找告警解决方案
1.2. 思路
1.2.1. 不差钱的方案
如果不差钱,更系统更完善的解决方案,我首先想到的是CAT,它不但能实现错误告警,且更加智能,告警的错误间隔,错误告警内容,QPS告警等等方式更多样化,还能查看接口QPS流量等等,奈何经费有限,放弃
1.2.2. 考虑自己实现
- 自己实现考虑可否对log.error方法进行拦截,于是各种找logback是否提供了拦截器过滤器等等,后查到官网发现logback本身提供了appender到邮件的方式,非常棒直接集成
1.3. 配置文件
pom
org.codehaus.janino janino 2.7.8javax.mail mail 1.4.7
logback${smtpHost}${smtpPort}${username}${password} true ${SSL}${email_to}${email_from}${email_subject} %date%level%thread%logger{0}%line%message ERRORACCEPTDENY1debug${CONSOLE_LOG_PATTERN}UTF-8${log.path}/${applicationName}-log.log%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%nUTF-8${log.path}/${applicationName}-log-%d{yyyyMMdd}.log.%i500MB15
1.4. 配置文件解读
- 配置文件的重点
- 我已经把大多可抽出的可变参数拉出来了,该配置文件可以直接放入任意工程,日志名称随bootstrap.yml中spring.application.name参数变动
- 告警发送邮件人也可在配置文件中配置,这里注意:onegene.alert.email和spring.application.name参数都最好在bootstrap.yml中配置,而不是application.yml,因为bootstrap.yml的读取优先级高于application.yml,否则可能读不到这两个参数
![7c49892e03c8ca41a59d47073067e408.png](https://i-blog.csdnimg.cn/blog_migrate/2e8df83eed53a0a6dec85dbf20604025.jpeg)
到这一步,只要我们打印log.error日志就会把错误日志都发到指定邮件上了,但这样肯定还不够,我们需要配合@ControllerAdvice可以做到只要报异常,就可以统一进行日志邮件发送,同时我们又会有特殊的需求,比如个别的错误日志频繁且不可避免,而且不需要处理,那么我们可以稍稍做些扩展,定义个接口注入,在业务代码中去处理是否不需要发送错误邮件
1.5. 代码
- 异常处理
@ControllerAdvice@Slf4jpublic class SystemExceptionHandler { @Autowired(required = false) private IExceptionFilter exceptionFilter; @ExceptionHandler(value = {DuplicateUniqueException.class, DuplicateKeyException.class}) @ResponseBody public Result duplicateUniqueExceptionExceptionHandler(HttpServletRequest request, Exception e) { return getExceptionResult(e, StatusCode.FAILURE_SYSTEM_CODE, "唯一主键重复(或联合唯一键)", false); } @ExceptionHandler(value = {FeignException.class, RuntimeException.class}) @ResponseBody public Result FeignExceptionHandler(HttpServletRequest request, Exception e) throws Exception { throw e; } @ExceptionHandler(value = Exception.class) @ResponseBody public Result commonExceptionHandler(HttpServletRequest request, Exception e) { return getExceptionResult(e, StatusCode.FAILURE_CODE, true); } private Result getExceptionResult(Exception e, int statusCode, boolean ignoreAlert) { return getExceptionResult(e, statusCode, e.getMessage(), ignoreAlert); } private Result getExceptionResult(Exception e, int statusCode, String msg, boolean ignoreAlert) { e.printStackTrace(); String exceptionName = ClassUtils.getShortName(e.getClass()); StackTraceElement[] stackTrace = e.getStackTrace(); StringBuilder sb = new StringBuilder(); for (StackTraceElement stackTraceElement : stackTrace) { sb.append(stackTraceElement.toString()).append(""); } String message = e.getMessage(); if (ignoreAlert && filter(e)) { log.error("ExceptionName ==> {},message:{},detail:{}", exceptionName, message, sb.toString()); } return Result.failure(statusCode, msg); } private boolean filter(Exception e) { if (exceptionFilter != null) { return exceptionFilter.filter(e); } return true; }}
接口很简单
public interface IExceptionFilter { boolean filter(Exception e);}
对于不需要处理的异常这里处理
/** * @author: laoliangliang * @description: 过滤不需要报警的异常 * @create: 2020/4/9 10:00 **/@Component@Slf4jpublic class FilterAlert implements IExceptionFilter { @Override public boolean filter(Exception e) { if (e instanceof ConnectException) { return false; } return true; }}
1.6. 总结
- 至此已经完全实现错误告警方案,后续就是优化工作了,实现效果如下
错误邮件列表
![cb11fe44ac6549021f6a7a5768ba2c54.png](https://i-blog.csdnimg.cn/blog_migrate/08226829eaae1fc4fb3924ba4101ad42.jpeg)
错误邮件内容
![e6e35da2398b4461c37a8e4036b3fc67.png](https://i-blog.csdnimg.cn/blog_migrate/ce3ad366b234e24cf955d4afcfd16533.jpeg)