错误日志告警实战
程序员文章站
2022-06-22 08:19:03
1. 错误日志告警实战 1.1. 需求 为了更方便的实时了解系统报错情况,我开始寻找告警解决方案 1.2. 思路 1.2.1. 不差钱的方案 如果不差钱,更系统更完善的解决方案,我首先想到的是 ,它不但能实现错误告警,且更加智能,告警的错误间隔,错误告警内容,QPS告警等等方式更多样化,还能查看接口 ......
1. 错误日志告警实战
1.1. 需求
为了更方便的实时了解系统报错情况,我开始寻找告警解决方案
1.2. 思路
1.2.1. 不差钱的方案
如果不差钱,更系统更完善的解决方案,我首先想到的是cat
,它不但能实现错误告警,且更加智能,告警的错误间隔,错误告警内容,qps告警等等方式更多样化,还能查看接口qps流量等等,奈何经费有限,放弃
1.2.2. 考虑自己实现
- 自己实现考虑可否对
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}"/><!--收件人账号多个可以逗号隔开--> <!-- 指定发件人名称。如果设置成“<admin> ”,则邮件发件人将会是“<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. 配置文件解读
- 配置文件的重点
<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"/>
- 我已经把大多可抽出的可变参数拉出来了,该配置文件可以直接放入任意工程,日志名称随
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. 总结
- 至此已经完全实现错误告警方案,后续就是优化工作了,实现效果如下
错误邮件列表
错误邮件内容