本篇做爲SpringBoot2.1版本的我的開發框架 子章節,請先閱讀SpringBoot2.1版本的我的開發框架再次閱讀本篇文章php
後端項目地址:SpringBoot2.1版本的我的應用開發框架html
前端項目地址:ywh-vue-admin前端
在以前的章節咱們測試的時候,發現控臺臺輸出的日誌是默認的,而且有不少的日誌沒有打印,而且不能自定義設置咱們的想要輸出的信息,對於一個應用程序來講日誌記錄是必不可少的一部分。線上問題追蹤,基於日誌的業務邏輯統計分析等都離不日誌。vue
對於日誌的參考資料網上一搜一大堆,更詳細的介紹能夠輕鬆的得到,這裏貼出幾個參考資料:java
Java有不少經常使用的日誌框架,如Log4j、Log4j 二、Commons Logging、Slf4j、Logback等。git
Commons Logging和Slf4j是日誌門面,提供一個統一的高層接口,爲各類loging API提供一個簡單統一的接口。log4j和Logback則是具體的日誌實現方案。能夠簡單的理解爲接口與接口的實現,調用者只須要關注接口而無需關注具體的實現,作到解耦。程序員
比較經常使用的組合使用方式是Slf4j與Logback組合使用,Commons Logging與Log4j組合使用,基於下面的一些優勢,選用Slf4j+Logback的日誌框架:github
更快的執行速度,Logback重寫了內部的實現,在一些關鍵執行路徑上性能提高10倍以上。並且logback不只性能提高了,初始化內存加載也更小了spring
自動清除舊的日誌歸檔文件,經過設置TimeBasedRollingPolicy 或者 SizeAndTimeBasedFNATP的 maxHistory 屬性,你就能夠控制日誌歸檔文件的最大數量json
Logback擁有遠比log4j更豐富的過濾能力,能夠不用下降日誌級別而記錄低級別中的日誌。
Logback必須配合Slf4j使用。因爲Logback和Slf4j是同一個做者,其兼容性不言而喻。
默認狀況下,Spring Boot會用Logback來記錄日誌,並用INFO級別輸出到控制檯。
由上可知咱們springboot項目默認使用的就是Logback,咱們能夠經過設置yml文件的方式來設置日誌的格式,也能夠經過logback.xml的方式來設置日誌管理。
yml方式: 這種方式相對於xml的方式比較簡單,由於你不配置,springboot也會有默認的設置,在application-dev.yml開發環境添加如下配置便可生效,path的路徑在開發環境時能夠是windows下的路徑,當你部署到liunx服務器時須要使用application-prod生產環境的配置文件,文件中配置的路徑爲liunx的路徑便可。
logging:
file:
#存放文件的最大天數
max-history: 15
#存放日誌最大size
max-size: 100MB
#存放日誌文件位置
path: E:\logs
pattern:
#輸出到控制檯的格式
console: "YWH - %d{yyyy-MM-dd HH:mm:ss} -%-4r [%t] %-5level %logger{36} - %msg%n"
#日誌級別映射,能夠指定包下的日誌級別 也可指定root爲info級別
level:
root: info
com.ywh.core: debug
*************************************************
<!-- %d{HH: mm:ss.SSS}——日誌輸出時間 -->
<!-- %thread [%t] ——輸出日誌的進程名字,這在Web應用以及異步任務處理中頗有用 -->
<!-- %-4r —— "-"表明了左對齊 將輸出從程序啓動到建立日誌記錄的時間 進行左對齊 且最小寬度爲4 -->
<!-- %-5level——日誌級別,而且使用5個字符靠左對齊 -->
<!-- %logger{36}——日誌輸出者的名字 -->
<!-- %msg——日誌消息 -->
<!-- %n——平臺的換行符 -->
<!-- 更多的詳情可參考 : https://aub.iteye.com/blog/1103685 此博客最下方有解釋 複製代碼
如下圖片來自於:aub.iteye.com/blog/110368…
xml方式:這種方式須要配置多個標籤,相對與yml方式比較麻煩一點,在resources文件下建立logback-spring.xml文件,若是不想把xml文件直接放在resources下的話,須要在yml文件中配置logging.config= 指定位置
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日誌級別從低到高分爲TRACE < DEBUG < INFO < WARN < ERROR < FATAL,若是設置爲WARN,則低於WARN的信息都不會輸出 -->
<!-- scan:當此屬性設置爲true時,配置文檔若是發生改變,將會被從新加載,默認值爲true -->
<!-- scanPeriod:設置監測配置文檔是否有修改的時間間隔,若是沒有給出時間單位,默認單位是毫秒。 當scan爲true時,此屬性生效。默認的時間間隔爲1分鐘。 -->
<!-- debug:當此屬性設置爲true時,將打印出logback內部日誌信息,實時查看logback運行狀態。默認值爲false。 -->
<configuration scan="true" scanPeriod="60 seconds">
<contextName>Y-W-H</contextName>
<!-- name的值是變量的名稱,value的值時變量定義的值。經過定義的值會被插入到logger上下文中。定義後,可使「${}」來使用變量。 -->
<property name="log.path" value="E:/logs/" />
<!--0. 日誌格式和顏色渲染 -->
<!-- 彩色日誌依賴的渲染類 -->
<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} %contextName ) [%thread] %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}}"/>
<!--1. 輸出到控制檯-->
<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>
。。。。。。省略代碼,具體代碼可前往github查看
</configuration>
複製代碼
經過以上兩種方式的任意一種配置好之後啓動項目之後,就會發現咱們已經使用了咱們自定義的輸出格式來輸出日誌了,在咱們指定下的路徑下出現了日誌文件。
當日志級別設置到INFO級別後,只會輸出INFO以上的日誌,如INFO、WARN、ERROR,這沒毛病,問題是,程序中拋出的異常堆棧(運行時異常)都沒有打印了,不利於排查問題。
並且,在某些狀況下,咱們在Service中想直接把異常往Controller拋出不作處理,但咱們不能直接把異常信息輸出到客戶端,這是很是不友好的,並且咱們想要精準的定位錯誤的所在,這就要咱們本身來定義異常的輸出了,而且把錯誤的異常以咱們以前封裝的Result的統一格式返回給前端,因此咱們須要自定義異常以及定義全局異常類,咱們先定義自定義異常類而後再定義全局異常類。
根據菜鳥教程中的異常信息分類,異常分爲三種狀況
檢查性異常:最具表明的檢查性異常是用戶錯誤或問題引發的異常,這是程序員沒法預見的。例如要打開一個不存在文件時,一個異常就發生了,這些異常在編譯時不能被簡單地忽略。
運行時異常: 運行時異常是可能被程序員避免的異常。與檢查性異常相反,運行時異常能夠在編譯時被忽略。
錯誤: 錯誤不是異常,而是脫離程序員控制的問題。錯誤在代碼中一般被忽略。例如,當棧溢出時,一個錯誤就發生了,它們在編譯也檢查不到的。
而咱們所要作的就是繼承運行時異常,對此類異常進行自定義處理,在common下exception包中建立MyException類繼承RuntimeException。
package com.ywh.common.exception;
/** * CreateTime: 2018-11-21 19:07 * ClassName: MyXiyiException * Package: com.ywh.common.exception * Describe: * 自定義異常,能夠throws的時候用本身的異常類 * * @author YWH */
public class MyException extends RuntimeException {
public MyException(String msg) {
super(msg);
}
public MyException(String message, Throwable throwable) {
super(message, throwable);
}
public MyException(Throwable throwable) {
super(throwable);
}
}
複製代碼
在common下的utils包中建立MyExceptionUtil工具類快速建立異常類
package com.ywh.common.utils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ywh.common.exception.MyException;
/** * CreateTime: 2018-12-18 22:32 * ClassName: MyExceptionUtil * Package: com.ywh.common.utils * Describe: * 異常工具類 * * @author YWH */
public class MyExceptionUtil {
public MyExceptionUtil() {
}
public static MyException mxe(String msg, Throwable t, Object... params){
return new MyException(StringUtils.format(msg, params),t);
}
public static MyException mxe(String msg, Object... params){
return new MyException(StringUtils.format(msg, params));
}
public static MyException mxe(Throwable t){
return new MyException(t);
}
}
複製代碼
建立完自定義異常之後咱們要對自定義異常進行捕獲而後處理,這就須要咱們定義全局異常類來進行捕獲後進行處理了,在common下exception中添加GlobalExceptionHandler類。
package com.ywh.common.exception;
/** * @Author: YWH * @Description: 全局異常處理類,攔截controller RestControllerAdvice此註解爲ResponseBody和ControllerAdvice混合註解 * @Date: Create in 17:16 2018/11/17 */
@RestControllerAdvice
public class GlobalExceptionHandler {
/** * * 全局異常類中定義的異常均可以被攔截,只是觸發條件不同,如IO異常這種必須拋出異常到 * controller中才能夠被攔截,或者在類中用try..catch本身處理 * 絕大部分不須要向上拋出異常便可被攔截,返回前端json數據,如數組下標越界,404 500 400等錯誤 * 若是本身想要寫,按着如下格式增長異常便可 *HttpMessageNotReadableException */
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/** * 啓動應用後,被 @ExceptionHandler、@InitBinder、@ModelAttribute 註解的方法, * 都會做用在 被 @RequestMapping 註解的方法上。 * @param binder */
@InitBinder
public void initWebBinder(WebDataBinder binder){
}
/** * 系統錯誤,未知的錯誤 已測試 * @param ex 異常信息 * @return 返回前端異常信息 */
@ExceptionHandler({Exception.class})
public Result exception(Exception ex){
log.error("錯誤詳情:" + ex.getMessage(),ex);
return Result.errorJson(BaseEnum.SYSTEM_ERROR.getMsg(),BaseEnum.SYSTEM_ERROR.getIndex());
}
。。。。。。省略代碼,具體代碼請前往github查看
/** * 自定義異常信息攔截 * @param ex 異常信息 * @return 返回前端異常信息 */
@ExceptionHandler(MyException.class)
public Result myCustomizeException(MyException ex){
log.warn("錯誤詳情:" + ex);
return Result.errorJson(BaseEnum.CUSTOMIZE_EXCEPTION.getMsg(),BaseEnum.CUSTOMIZE_EXCEPTION.getIndex());
}
}
複製代碼
在GlobalExceptionHandler中咱們對不少異常進行了攔截後自定義處理,並把咱們上邊自定義的運行時異常進行攔截,我在類中的方法上都寫了註釋,並根據網上的資料應該很好理解,我對大部分的異常都作了測試,都是能夠進行攔截成功的。
咱們用postman經過post方式請求一個get的方法,能夠看到返回了咱們自定義的json格式,而且告訴咱們這是由於接口類型所致使的錯誤,這樣咱們很快就能定位到錯誤進行解決。
以上錯誤都是系統替咱們捕獲而且經過全局異常類進行了攔截以後返回自定義的json格式,而咱們的自定義異常如何使用呢,自定義異常須要咱們手動捕獲異常,而且拋出異常,這樣咱們的全局異常類才能攔截到。
咱們在ExampleServiceImpl中定義一個方法,並在Controller層中調用此方法,用postman調用此接口
/** * 測試自定義異常 * @return 返回字符串 */
@Override
public String myException() {
int i = 0;
int a = 10;
if( i > a){
System.out.println("測試!!!");
}else{
throw MyExceptionUtil.mxe("出錯了,比他小啊!!");
}
return "沒有進行攔截,失敗了";
}
複製代碼
@Autowired
private ExampleService exampleService;
@GetMapping("myExceptionTest")
public Result myExceptionTest(){
return Result.successJson(exampleService.myException());
}
複製代碼
能夠看到咱們的自定義異常被攔截到而且在控制檯中打印了咱們想要的信息。