zoukankan      html  css  js  c++  java
  • 错误日志告警实战

    1. 错误日志告警实战

    1.1. 需求

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

    1.2. 思路

    1.2.1. 不差钱的方案

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

    1.2.2. 考虑自己实现

    1. 自己实现考虑可否对log.error方法进行拦截,于是各种找logback是否提供了拦截器过滤器等等,后查到官网发现logback本身提供了appender到邮件的方式,非常棒直接集成

    1.3. 配置文件

    pom

     <dependency>
        <groupId>org.codehaus.janino</groupId>
        <artifactId>janino</artifactId>
        <version>2.7.8</version>
    </dependency>
    <dependency>
        <groupId>javax.mail</groupId>
        <artifactId>mail</artifactId>
        <version>1.4.7</version>
    </dependency>
    
    <configuration>
        <contextName>logback</contextName>
        <!--配置文件中参数-->
        <springProperty scope="context" name="applicationName" source="spring.application.name"/>
        <springProperty scope="context" name="alertEmail" source="onegene.alert.email"/>
        <springProperty scope="context" name="profile" source="spring.profiles.active"/>
        <!--  邮件 -->
        <!-- SMTP server的地址,必需指定。如网易的SMTP服务器地址是: smtp.163.com -->
        <property name="smtpHost" value="hwhzsmtp.qiye.163.com"/><!--填入要发送邮件的smtp服务器地址(问DBA或者经理啥的就知道)-->
        <!-- SMTP server的端口地址。默认值:25 -->
        <property name="smtpPort" value="465"/>
        <!-- 发送邮件账号,默认为null -->
        <property name="username" value="xxxx@163.com.cn"/><!--发件人账号-->
        <!-- 发送邮件密码,默认为null -->
        <property name="password" value="rVgkwPL4WsWmGV72"/><!--发件人密码-->
        <!-- 如果设置为true,appender将会使用SSL连接到日志服务器。默认值:false -->
        <property name="SSL" value="true"/>
        <!-- 指定发送到那个邮箱,可设置多个<to>属性,指定多个目的邮箱 -->
        <property name="email_to" value="${alertEmail}"/><!--收件人账号多个可以逗号隔开-->
        <!-- 指定发件人名称。如果设置成“&lt;ADMIN&gt; ”,则邮件发件人将会是“<ADMIN> ” -->
        <property name="email_from" value="xxxx@163.com"/>
        <!-- 指定emial的标题,它需要满足PatternLayout中的格式要求。如果设置成“Log: %logger - %msg ”,就案例来讲,则发送邮件时,标题为“【Error】: com.foo.Bar - Hello World ”。 默认值:"%logger{20} - %m". -->
        <property name="email_subject" value="【${applicationName}:${profile}:Error】: %logger"/>
        <!-- ERROR邮件发送 -->
        <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
            <smtpHost>${smtpHost}</smtpHost>
            <smtpPort>${smtpPort}</smtpPort>
            <username>${username}</username>
            <password>${password}</password>
            <asynchronousSending>true</asynchronousSending>
            <SSL>${SSL}</SSL>
            <to>${email_to}</to>
            <from>${email_from}</from>
            <subject>${email_subject}</subject>
                 <!-- html格式-->
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <Pattern>%date%level%thread%logger{0}%line%message</Pattern>
            </layout>
                 <!-- 这里采用等级过滤器 指定等级相符才发送 -->
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <cyclicBufferTracker class="ch.qos.logback.core.spi.CyclicBufferTracker">
                <!-- 每个电子邮件只发送一个日志条目 -->
                <bufferSize>1</bufferSize>
            </cyclicBufferTracker>
        </appender>
    
        <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
        <property name="log.path" value="log"/>
    
        <!-- 彩色日志 -->
        <!-- 彩色日志依赖的渲染类 -->
        <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
        <conversionRule conversionWord="wex"
                        converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
        <conversionRule conversionWord="wEx"
                        converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
        <!-- 彩色日志格式 -->
        <property name="CONSOLE_LOG_PATTERN"
                  value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    
    
        <!--输出到控制台-->
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>debug</level>
            </filter>
            <encoder>
                <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
                <!-- 设置字符集 -->
                <charset>UTF-8</charset>
            </encoder>
        </appender>
    
        <!--输出到文件-->
        <!-- 时间滚动输出 level为 DEBUG 日志 -->
        <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!-- 正在记录的日志文件的路径及文件名 -->
            <file>${log.path}/${applicationName}-log.log</file>
            <!--日志文件输出格式-->
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
                <charset>UTF-8</charset> <!-- 设置字符集 -->
            </encoder>
            <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!-- 日志归档 -->
                <fileNamePattern>${log.path}/${applicationName}-log-%d{yyyyMMdd}.log.%i</fileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>500MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
                <!--日志文件保留天数-->
                <maxHistory>15</maxHistory>
            </rollingPolicy>
        </appender>
        <logger name="com.onegene.platform" level="debug"/>
        <logger name="com.onegene.platform.auth.authority" level="info"/>
        <logger name="org.springframework.security.oauth2.provider.endpoint" additivity="false"/>
        <springProfile name="local">
            <root level="info">
                <appender-ref ref="CONSOLE"/>
                <appender-ref ref="DEBUG_FILE"/>
            </root>
        </springProfile>
        <springProfile name="dev,pro">
            <root level="info">
                <appender-ref ref="CONSOLE"/>
                <appender-ref ref="DEBUG_FILE"/>
                <appender-ref ref="EMAIL"/>
            </root>
        </springProfile>
    </configuration>
    

    1.4. 配置文件解读

    1. 配置文件的重点
        <springProperty scope="context" name="applicationName" source="spring.application.name"/>
        <springProperty scope="context" name="alertEmail" source="onegene.alert.email"/>
        <springProperty scope="context" name="profile" source="spring.profiles.active"/>
    
    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("
    ");
            }
            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

  • 相关阅读:
    dB是乘以10还是乘以20
    FFT快速傅里叶变换的python实现
    f(t) = t的傅里叶系数
    如何从二进制文件中读取int型序列
    python中的bytes和str类型
    python文件读写
    matplotlib浅析
    什么是语法糖
    怎样查看一个可迭代对象的值
    十六进制颜色码及其表示-(6 digit color code)
  • 原文地址:https://www.cnblogs.com/sky-chen/p/12673647.html
Copyright © 2011-2022 走看看