如果不差钱,更系统更完善的解决方案,我首先想到的是 CAT
,它不但能实现错误告警,且更加智能,告警的错误间隔,错误告警内容,QPS告警等等方式更多样化,还能查看接口QPS流量等等,奈何经费有限,放弃
log.error
方法进行拦截,于是各种找logback是否提供了拦截器过滤器等等,后查到官网发现logback本身提供了appender到邮件的方式,非常棒直接集成 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> 复制代码
<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
可以做到 只要报异常,就可以统一进行日志邮件发送 ,同时我们又会有特殊的需求,比如个别的错误日志频繁且不可避免,而且不需要处理,那么我们可以 稍稍做些扩展,定义个接口注入 ,在业务代码中去处理是否不需要发送错误邮件
@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; } } 复制代码