實際開發中,需要對各種異常進行處理,并記錄日志,常見的異常包括以下幾種:
- 系統錯誤,最常見的,系統未處理的
- 404,請求url不存在
- 參數錯誤,請求的參數不符合校驗規則
在springBoot中采用controllerAdvice來進行公共異常的攔截。先設置系統的日志,用于記錄異常信息。
logback設置
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- appender -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} - %m%n
</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 系統日志配置 -->
<appender name="sysLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/sys.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_PATH}/%d{yyyyMMdd}/sys-%d{yyyyMMdd}.log
</FileNamePattern>
<maxHistory>365</maxHistory>
</rollingPolicy>
<encoder>
<ImmediateFlush>true</ImmediateFlush>
<charset>utf-8</charset>
<Pattern>[%d{yyyyMMdd HH:mm:ss}] - %m%n</Pattern>
</encoder>
</appender>
<appender name="daoLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/dao.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_PATH}/%d{yyyyMMdd}/dao-%d{yyyyMMdd}.log
</FileNamePattern>
<maxHistory>365</maxHistory>
</rollingPolicy>
<encoder>
<ImmediateFlush>true</ImmediateFlush>
<charset>utf-8</charset>
<Pattern>[%d{yyyyMMdd HH:mm:ss}] [%-5level] [%-30logger{0}][%-3L]
[SeqId:%X{SeqId}] - %m%n
</Pattern>
</encoder>
</appender>
<appender name="serviceLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/service.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_PATH}/%d{yyyyMMdd}/service-%d{yyyyMMdd}.log
</FileNamePattern>
<maxHistory>365</maxHistory>
</rollingPolicy>
<encoder>
<ImmediateFlush>true</ImmediateFlush>
<charset>utf-8</charset>
<Pattern>[%d{yyyyMMdd HH:mm:ss}] - %m%n</Pattern>
</encoder>
</appender>
<appender name="controllerLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/controller.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_PATH}/%d{yyyyMMdd}/controller-%d{yyyyMMdd}.log
</FileNamePattern>
<maxHistory>365</maxHistory>
</rollingPolicy>
<encoder>
<ImmediateFlush>true</ImmediateFlush>
<charset>utf-8</charset>
<Pattern>[%d{yyyyMMdd HH:mm:ss}]- %m%n</Pattern>
</encoder>
</appender>
<appender name="operationLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/operation.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_PATH}/%d{yyyyMMdd}/operation-%d{yyyyMMdd}.log
</FileNamePattern>
<maxHistory>365</maxHistory>
</rollingPolicy>
<encoder>
<ImmediateFlush>true</ImmediateFlush>
<charset>utf-8</charset>
<Pattern>%m%n</Pattern>
</encoder>
</appender>
<appender name="performanceLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/performance.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_PATH}/%d{yyyyMMdd}/performance-%d{yyyyMMdd}.log
</FileNamePattern>
<maxHistory>365</maxHistory>
</rollingPolicy>
<encoder>
<ImmediateFlush>true</ImmediateFlush>
<charset>utf-8</charset>
<Pattern>[%d{yyyyMMdd HH:mm:ss}] - %m%n</Pattern>
</encoder>
</appender>
<!-- additivity為false不向root控制臺輸出 -->
<logger name="sysLog" additivity="false" level="info">
<appender-ref ref="stdout"/>
<appender-ref ref="sysLogAppender"/>
</logger>
<logger name="daoLog" additivity="false" level="ERROR">
<appender-ref ref="stdout"/>
<appender-ref ref="daoLogAppender"/>
</logger>
<logger name="serviceLog" additivity="false" level="ERROR">
<appender-ref ref="stdout"/>
<appender-ref ref="serviceLogAppender"/>
</logger>
<logger name="controllerLog" additivity="false" level="INFO">
<appender-ref ref="stdout"/>
<appender-ref ref="controllerLogAppender"/>
</logger>
<logger name="operationLog" additivity="false" level="INFO">
<appender-ref ref="stdout"/>
<appender-ref ref="operationLogAppender"/>
</logger>
<logger name="performanceLog" additivity="false" level="INFO">
<appender-ref ref="stdout"/>
<appender-ref ref="performanceLogAppender"/>
</logger>
<!-- root 默認日志配置 -->
<root level="info">
<appender-ref ref="stdout"/>
<appender-ref ref="sysLogAppender"/>
</root>
<logger name="org.springframework" level="INFO"/>
</configuration>
和正常的logback日志日志沒有什么不同,把日志分為以下幾類:
- sys:記錄工程啟動和停止的日志
- controller:記錄controller層的異常
- service:service層異常,主要是用AOP來記錄
- dao:dao層的異常,主要是用AOP來記錄
- performace:系統性能監測記錄,也是用AOP來記錄
- operation:操作記錄,主要記錄接口請求記錄
controllerAdvice
@ControllerAdvice
public class ApiControllerAdvice {
/**
* 定義日志處理
*/
private static Logger logger = LoggerFactory.getLogger("controllerLog");
/**
* 系統異常處理,比如:404,500
*
* @param request request
* @param response response
* @param e 異常
* @return JsonResult結構
* @throws Exception 異常
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public JsonResult defaultErrorHandler(HttpServletRequest request, HttpServletResponse response, Exception e) throws Exception {
logger.error("系統異常", e);
logException(request);
if (e instanceof org.springframework.web.servlet.NoHandlerFoundException) {
response.setStatus(HttpStatus.NOT_FOUND.value());
return new JsonResult(false, GlobalReturnCode.SYSTEM_PATH_NOEXIST);
} else {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return new JsonResult(false, GlobalReturnCode.SYSTEM_ERROR);
}
}
/**
* 取得header的所有屬性
*
* @param headers 請求的headers
* @param request request
* @return 把header拼成字符串
*/
private String getHeaderValue(Enumeration<String> headers, HttpServletRequest request) {
String str = "";
while (headers.hasMoreElements()) {
String header = headers.nextElement();
str += header + "=" + request.getHeader(header) + "&";
}
return str;
}
/**
* 打印所有異常信息
*
* @param request request
*/
private void logException(HttpServletRequest request) {
try {
logger.error("請求路徑:" + request.getServletPath());
logger.error("請求參數:" + request.getParameterMap().toString());
logger.error("請求header:" + getHeaderValue(request.getHeaderNames(), request));
logger.error("請求body:" + StringUtil.getBodyString(request.getReader()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
這里先記錄404錯誤和500錯誤,如果接口請求有異常,直接返回對應的json串
404錯誤
為了使springBoot能攔截404錯誤,需要在application.yml中增加如下配置:
spring:
mvc:
throw-exception-if-no-handler-found: true
resources:
add-mappings: false
如果不加上面配置,404是無法獲取攔截的。
如果沒有配置異常攔截,直接請求springboot會返回如下錯誤:
404默認返回.png
配置后返回如下信息:
配置后返回.png
500錯誤
為了整體攔截系統異常,需要定制返回的500錯誤,避免返回異常給調用端。如下圖所示:
系統異常.png