Spring Boot與Logback的運用(自定義異常+AOP)

在開發以及調試過程當中,程序員對日誌的需求是很是大的,出了什麼問題,都要經過日誌去進行排查,可是若是日誌不清或者雜亂無章,則不利於維護java

這邊就比較詳細的列舉幾種類型的日誌,供你們參考程序員

首先明白logback日誌是Spring Boot自帶的,不須要引入額外的包spring

            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-access</artifactId>
                <version>${logback.version}</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>${logback.version}</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>${logback.version}</version>
            </dependency>

點進pom裏的核心依賴,就能看見上面幾個,是由Spring Boot自動依賴配置好的,咱們只要直接使用就行了sql

比較簡單的是直接在application的配置文件裏 寫參數配置就好了,他提供了日誌級別,日誌輸出路徑等,也能知足基本的日誌輸出數據庫

咱們這經過xml文件進行配置 logback-spring.xmljson

這樣就能直接引用到xml了,可是爲何能引用到了數組

就是在logback裏有個默認的機制,內部會有幾種標準的文件格式,在LogbackLoggingSystem裏標註了app

@Override
    protected String[] getStandardConfigLocations() {
        return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy",
                "logback.xml" };
    }

因此最爲標準的爲這裏面的四種文件格式,可是若是項目中沒有,他還提供了擴展文件格式 就是在後面拼上-spring,例如logback.xml 擴展爲logback-spring.xml運維

okdom

下面看下xml裏面的內容:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--定義日誌文件的存儲地址 能夠在LogBack 的配置中使用相對路徑-->
    <property name="LOG_HOME" value="logs" />
    

    <!-- 彩色日誌 -->
    <!-- 彩色日誌依賴的渲染類 -->
    <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}}" />
    <!-- Console 輸出設置 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- 按照天天生成日誌文件 -->
    <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日誌文件輸出的文件名-->
            <FileNamePattern>${LOG_HOME}/category-server-log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日誌文件保留天數-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日誌消息,%n是換行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日誌文件最大的大小
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>DENY</onMatch>
            <onMismatch>NEUTRAL</onMismatch>
        </filter>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>DENY</onMatch>
            <onMismatch>NEUTRAL</onMismatch>
        </filter>

    </appender>

    <!-- 出錯日誌 appender  -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天回滾 daily -->
            <!-- log.dir 在maven profile裏配置 -->
            <FileNamePattern>${LOG_HOME}/category-server-error-log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!-- 日誌最大的歷史 60天 -->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- 本身打印的日誌文件,用於記錄重要日誌信息 -->
    <appender name="MY_INFO_FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日誌文件輸出的文件名-->
            <FileNamePattern>${LOG_HOME}/category-server-myinfo-log.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!--日誌文件保留天數-->
            <MaxHistory>15</MaxHistory>
            <!--日誌文件最大的大小-->
            <MaxFileSize>10MB</MaxFileSize>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>  
        </filter>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日誌消息,%n是換行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <logger name="my_info" additivity="true">
        <appender-ref ref="MY_INFO_FILE"/>
    </logger>
    <!--myibatis log configure-->
    <logger name="com.example.demo" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>
        <!-- 日誌輸出級別 -->
        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="FILE" />
            <appender-ref ref="ERROR_FILE" />
        </root>
</configuration>

這裏一共有四塊內容,第一是console的日誌輸出,第二是系統運行日誌,第三是警告以上的日誌輸出(基本上是程序出錯日誌),第四種是自定義日誌

每一塊日誌由一個appender標籤引入

CONSOLE是控制檯日誌輸出,只要規定個格式就好了

FILE是系統運行日誌,系統的全部運行信息都會保留,正常咱們會把這部分信息保存在硬盤日誌文件中,按天按文件大小保存,由於這個內容實在是比較多

ERROR_FILE是WARN級別以上的日誌,這塊是開發人員和運維人員最多關注的,由於基本上全部的bug都會在這個裏面體現

MY_INFO_FILE是自定義日誌,想定義本身的日誌文件,記錄一些重要的信息

這裏的日誌都是以文件的形式保存在本地,固然像WARN級別以上日誌能夠異步保存到數據庫

 

日誌文件定義好後,接下來就要開始定義業務邏輯了

在針對一些異常日誌,咱們想盡量完整準確的拋出異常,一眼就能知道是什麼問題,這裏咱們就須要自定義異常,最多的就是像空指針,數組越界等常見異常

定義基礎異常類BaseException繼承他的父類RuntimeException

public class BaseException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public BaseException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
        // TODO Auto-generated constructor stub
    }

    public BaseException(String message, Throwable cause) {
        super(message, cause);
        // TODO Auto-generated constructor stub
    }

    public BaseException(String message) {
        super(message);
        // TODO Auto-generated constructor stub
    }

    public BaseException(Throwable cause) {
        super(cause);
        // TODO Auto-generated constructor stub
    }
    
    
    
}

而後全局異常處理類:GlobalExceptionHandler

@CrossOrigin
@RestControllerAdvice
public class GlobalExceptionHandler{

    private static Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
    private static final String APPLICATION_JSON = "application/json";
    private static final String UTF_8 = "UTF-8";
     
    /**
     * BaseException 處理類
    * @Title: HandleBaseException  
    * @Description: TODO
    * @param @param e
    * @param @return
    * @return ResponseMsg
    * @throws
     */
    @ExceptionHandler(BaseException.class)
    @ResponseBody
    public ResponseMsg HandleBaseException(RuntimeException e){
        //只能輸出捕獲到的異常,未捕獲到的異常不輸出到日誌,或者經過aop攔截器攔截全部方法
        LOGGER.error(getExceptionDetail(e));
        //返回失敗信息
        Route route = new Route();
        ResponseMsg responseMsg = new ResponseMsg(route,ReturnMsgEnum.INTERNAL_ERROR.getCode(),
                 ReturnMsgEnum.INTERNAL_ERROR.getMsg(), "");
        return responseMsg;
    }
    
    @ExceptionHandler(GlobalException.class)
    @ResponseBody
    public ResponseMsg HandleGlobalException(Exception e){
        //只能輸出捕獲到的異常,未捕獲到的異常不輸出到日誌,或者經過aop攔截器攔截全部方法
        LOGGER.error(getExceptionDetail(e));
        //返回失敗信息
        Route route = new Route();
        ResponseMsg responseMsg = new ResponseMsg(route,ReturnMsgEnum.INTERNAL_ERROR.getCode(),
                 ReturnMsgEnum.INTERNAL_ERROR.getMsg(), "系統未捕獲該異常");
        return responseMsg;
    }
    
    public String getExceptionDetail(Exception e) {
        StringBuffer stringBuffer = new StringBuffer(e.toString() + "\n");
        StackTraceElement[] messages = e.getStackTrace();
        int length = messages.length;
        for (int i = 0; i < length; i++) {
            stringBuffer.append("\t"+messages[i].toString()+"\n");
        }
        return stringBuffer.toString();
    }
    
}
@RestControllerAdvice:代表他是一個Controller 而且是異常攔截的統一處理類
定義針對自定義異常的處理方法:用
@ExceptionHandler(BaseException.class)註解標註
BaseException就是剛纔的自定義異常
以後全部拋出的BaseException都會由他處理

自定義異常咱們都能輕鬆捕獲到了,而且輸出到日誌裏了

若是有些異常咱們沒有捕獲到,咱們就能夠定義一個切面,讓全部方法都通過這個切面處理
/**
 * 處理未捕獲到的異常
* @ClassName: SpringAOP  
* @author Mr.Chengjq
* @date 2018年10月17日  
* @Description: TODO
 */
@Aspect
@Configuration
public class SpringAOP {
    private static final Logger logger = LoggerFactory.getLogger(SpringAOP.class);
     
    /**
     * 定義切點Pointcut
     * 第一個*號:表示返回類型, *號表示全部的類型
     * 第二個*號:表示類名,*號表示全部的類
     * 第三個*號:表示方法名,*號表示全部的方法
     * 後面括弧裏面表示方法的參數,兩個句點表示任何參數
     */
    @Pointcut("execution(* com.example.demo..*.*(..))")
    public void executionService() {
 
    }
 
 
    /**
     * 方法調用以前調用
     * @param joinPoint
     */
    @Before(value = "executionService()")
    public void doBefore(JoinPoint joinPoint){
 
        //添加日誌打印
        String requestId = String.valueOf(UUID.randomUUID());
        MDC.put("requestId",requestId);
        logger.info("=====>@Before:請求參數爲:{}",Arrays.toString(joinPoint.getArgs()));
 
    }
 
    /**
     * 方法以後調用
     * @param joinPoint
     * @param returnValue 方法返回值
     */
    @AfterReturning(pointcut = "executionService()",returning="returnValue")
    public void  doAfterReturning(JoinPoint joinPoint,Object returnValue){
 
        logger.info("=====>@AfterReturning:響應參數爲:{}",returnValue);
        // 處理完請求,返回內容
        MDC.clear();
    }
 
    /**
     * 統計方法執行耗時Around環繞通知
     * @param joinPoint
     * @return
     */
    @Around("executionService()")
    public Object timeAround(ProceedingJoinPoint joinPoint) throws Throwable{
 
        //獲取開始執行的時間
        long startTime = System.currentTimeMillis();
 
        // 定義返回對象、獲得方法須要的參數
        Object obj = null;
        //Object[] args = joinPoint.getArgs();
        try {
            obj = joinPoint.proceed();
        } catch (Throwable e) {
            // TODO: handle exception
            logger.error(getExceptionDetail(e));
            throw new GlobalException();
        }
        
        // 獲取執行結束的時間
        long endTime = System.currentTimeMillis();
        //MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
        // 打印耗時的信息
        logger.info("=====>處理本次請求共耗時:{} ms",endTime-startTime);
        return obj;
    }
    
    
    public String getExceptionDetail(Throwable e) {
        StringBuffer stringBuffer = new StringBuffer(e.toString() + "\n");
        StackTraceElement[] messages = e.getStackTrace();
        int length = messages.length;
        for (int i = 0; i < length; i++) {
            stringBuffer.append("\t"+messages[i].toString()+"\n");
        }
        return stringBuffer.toString();
    }

}
這個切面裏未捕獲到的異常也所有作特定處理
相關文章
相關標籤/搜索