錯誤日誌告警實戰

1. 錯誤日誌告警實戰

1.1. 需求

為了更方便的實時瞭解系統報錯情況,我開始尋找告警解決方案

1.2. 思路

1.2.1. 不差錢的方案

如果不差錢,更系統更完善的解決方案,我首先想到的是CAT,它不但能實現錯誤告警,且更加智能,告警的錯誤間隔,錯誤告警內容,QPS告警等等方式更多樣化,還能查看接口QPS流量等等,奈何經費有限,放棄

1.2.2. 考慮自己實現

  1. 自己實現考慮可否對log.error方法進行攔截,於是各種找logback是否提供了攔截器過濾器等等,後查到官網發現logback本身提供了appender到郵件的方式,非常棒直接集成

1.3. 配置文件

pom

<code> <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>
/<code>
<code><configuration>
<contextname>logback/<contextname>

<springproperty>
<springproperty>
<springproperty>


<property>

<property>

<property>

<property>

<property>

<property>

<property>

<property>

<appender>
<smtphost>${smtpHost}/<smtphost>
<smtpport>${smtpPort}/<smtpport>
<username>${username}/<username>
<password>${password}/<password>
<asynchronoussending>true/<asynchronoussending>
${SSL}
${email_to}
<from>${email_from}/<from>
<subject>${email_subject}/<subject>
    
<layout>
<pattern>%date%level%thread%logger{0}%line%message/<pattern>
/<layout>
    
<filter>
<level>ERROR/<level>
<onmatch>ACCEPT/<onmatch>
<onmismatch>DENY/<onmismatch>
/<filter>
<cyclicbuffertracker>

<buffersize>1/<buffersize>
/<cyclicbuffertracker>
/<appender>


<property>



<conversionrule>
<conversionrule> converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionrule> converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>

<property> 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>

<filter>
<level>debug/<level>
/<filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}/<pattern>

<charset>UTF-8/<charset>
/<encoder>
/<appender>



<appender>

<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>

<filenamepattern>${log.path}/${applicationName}-log-%d{yyyyMMdd}.log.%i/<filenamepattern>
<timebasedfilenamingandtriggeringpolicy>
<maxfilesize>500MB/<maxfilesize>
/<timebasedfilenamingandtriggeringpolicy>

<maxhistory>15/<maxhistory>
/<rollingpolicy>
/<appender>
<logger>
<logger>
<logger>
<springprofile>
<root>
<appender-ref>
<appender-ref>
/<root>
/<springprofile>
<springprofile>
<root>
<appender-ref>
<appender-ref>
<appender-ref>
/<root>
/<springprofile>
/<property>/<conversionrule>/<conversionrule>/<configuration>
/<code>

1.4. 配置文件解讀

  1. 配置文件的重點
<code>    <springproperty> 

<springproperty>
<springproperty>
/<code>
  1. 我已經把大多可抽出的可變參數拉出來了,該配置文件可以直接放入任意工程,日誌名稱隨bootstrap.yml中spring.application.name參數變動
  2. 告警發送郵件人也可在配置文件中配置,這裡注意:onegene.alert.email和spring.application.name參數都最好在bootstrap.yml中配置,而不是application.yml,因為bootstrap.yml的讀取優先級高於application.yml,否則可能讀不到這兩個參數
錯誤日誌告警實戰

到這一步,只要我們打印log.error日誌就會把錯誤日誌都發到指定郵件上了,但這樣肯定還不夠,我們需要配合@ControllerAdvice可以做到只要報異常,就可以統一進行日誌郵件發送,同時我們又會有特殊的需求,比如個別的錯誤日誌頻繁且不可避免,而且不需要處理,那麼我們可以稍稍做些擴展,定義個接口注入,在業務代碼中去處理是否不需要發送錯誤郵件

1.5. 代碼

  1. 異常處理
<code>@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;
}
}
/<code>

接口很簡單

<code>public interface IExceptionFilter {
boolean filter(Exception e);
}
/<code>

對於不需要處理的異常這裡處理

<code>/**
* @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;
}
}
/<code>

1.6. 總結

  1. 至此已經完全實現錯誤告警方案,後續就是優化工作了,實現效果如下

錯誤郵件列表

錯誤日誌告警實戰

錯誤郵件內容

錯誤日誌告警實戰


分享到:


相關文章: