错误日志告警实战


1. 错误日志告警实战

1.1. 需求

为了更方便的实时了解系统报错情况,我开始寻找告警解决方案

1.2. 思路

1.2.1. 不差钱的方案

如果不差钱,更系统更完善的解决方案,我首先想到的是CAT,它不但能实现错误告警,且更加智能,告警的错误间隔,错误告警内容,QPS告警等等方式更多样化,还能查看接口QPS流量等等,奈何经费有限,放弃

1.2.2. 考虑自己实现

  1. 自己实现考虑可否对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. 配置文件解读

  1. 配置文件的重点
    
    
    
  1. 我已经把大多可抽出的可变参数拉出来了,该配置文件可以直接放入任意工程,日志名称随bootstrap.ymlspring.application.name参数变动
  2. 告警发送邮件人也可在配置文件中配置,这里注意onegene.alert.emailspring.application.name参数都最好在bootstrap.yml中配置,而不是application.yml,因为bootstrap.yml的读取优先级高于application.yml,否则可能读不到这两个参数

UTOOLS1586500939194.png

到这一步,只要我们打印log.error日志就会把错误日志都发到指定邮件上了,但这样肯定还不够,我们需要配合@ControllerAdvice可以做到只要报异常,就可以统一进行日志邮件发送,同时我们又会有特殊的需求,比如个别的错误日志频繁且不可避免,而且不需要处理,那么我们可以稍稍做些扩展,定义个接口注入,在业务代码中去处理是否不需要发送错误邮件

1.5. 代码

  1. 异常处理
@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. 总结

  1. 至此已经完全实现错误告警方案,后续就是优化工作了,实现效果如下

错误邮件列表
UTOOLS1586502074518.png

错误邮件内容
UTOOLS1586502211700.png