错误日志告警实战
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.8
javax.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
ERROR
ACCEPT
DENY
1
debug
${CONSOLE_LOG_PATTERN}
UTF-8
${log.path}/${applicationName}-log.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${log.path}/${applicationName}-log-%d{yyyyMMdd}.log.%i
500MB
15
1.4. 配置文件解读
- 配置文件的重点
- 我已经把大多可抽出的可变参数拉出来了,该配置文件可以直接放入任意工程,日志名称随
bootstrap.yml
中spring.application.name
参数变动 - 告警发送邮件人也可在配置文件中配置,这里注意:
onegene.alert.email
和spring.application.name
参数都最好在bootstrap.yml
中配置,而不是application.yml
,因为bootstrap.yml
的读取优先级高于application.yml
,否则可能读不到这两个参数
到这一步,只要我们打印log.error
日志就会把错误日志都发到指定邮件上了,但这样肯定还不够,我们需要配合@ControllerAdvice
可以做到只要报异常,就可以统一进行日志邮件发送,同时我们又会有特殊的需求,比如个别的错误日志频繁且不可避免,而且不需要处理,那么我们可以稍稍做些扩展,定义个接口注入,在业务代码中去处理是否不需要发送错误邮件
1.5. 代码
- 异常处理
@ControllerAdvice
@Slf4j
public 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("\n");
}
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
@Slf4j
public class FilterAlert implements IExceptionFilter {
@Override
public boolean filter(Exception e) {
if (e instanceof ConnectException) {
return false;
}
return true;
}
}
1.6. 总结
- 至此已经完全实现错误告警方案,后续就是优化工作了,实现效果如下
错误邮件列表
错误邮件内容