SpringBoot系列(十三)統一日誌處理,logback+slf4j AOP+自定義註解,走起!

往期精彩推薦

SpringBoot系列(一)idea新建Springboot項目

SpringBoot系列(二)入門知識

springBoot系列(三)配置文件詳解

SpringBoot系列(四)web靜態資源配置詳解

SpringBoot系列(五)Mybatis整合完整詳細版

SpringBoot系列(六)集成thymeleaf詳解版

Springboot系列(七) 集成介面文檔swagger,使用,測試

SpringBoot系列(八)分分鐘學會Springboot多種解決跨域方式

SpringBoot系列(九)單,多文件上傳的正確姿勢

SpringBoot系列(十)優雅的處理統一異常處理與統一結果返回

SpringBoot系列(十一)攔截器與攔截器鏈的配置與使用詳解,你知道多少?

SpringBoot系列(十二)過濾器配置詳解

本文目錄

一、SpringBoot中的日誌

 在我們運行項目的時候,你會發現控制台是有日誌列印的,這個日誌就是SpringBoot默認配置的日誌框架處理的。SpringBoot默認是運用logback+slf4j處理日誌,slf4j是抽象層,logback是實現層。

 但是不同的框架可能會有不同日誌處理方式,如果我們在SpringBoot中集成了不同的框架的話,是不是日誌的輸出也會混亂呢?很顯然,如果你有一點經驗的話,你會發現,只要你不修改SpringBoot的默認日誌配置,它的日誌輸出格式是不會變得。這是因為,在SpringBoot管理日誌的時候,它都將其他框架的日誌通過一些中間包的形式將其他的日誌抽象成了slf4j介面,而統一用logback的形式實現。

 本文我們來講講怎麼來配置日誌格式以及運用AOP+自定義註解簡化日誌的記錄。

二、自定義日誌常用配置

1. 日誌輸出級別

 SpringBoot中默認的日誌輸出級別是info,也就是說我們平常在控制台輸出的那些日誌都是info級別以及更高級別的日誌。我們可以自己定義日誌的輸出級別,一般有以下幾個級別:

trace,debug,info,warn,error  //級別遞增

 trace 是追蹤日誌,debug是調式日誌,info一般是自定義日誌或者是資訊日誌,warn是警告日誌,error則是錯誤日誌。

 可能這麼說你也不知道這個級別有什麼用,來看看這個程式碼:

@RestController
public class TestLogController {

    Logger logger = LoggerFactory.getLogger(TestLogController.class);

    @GetMapping("/testLog")
    public void testLog(){
        logger.trace("這是trace級別的日誌");
        logger.debug("這是debug級別的日誌");
        logger.info("這是正常自定義日誌");
        logger.warn("這是警告日誌");
        logger.error("這是錯誤日誌");
    }
}

程式碼說明:(上面的Logger包這裡是使用的org.slf4j.Logger)

 首先我們獲取一個日誌記錄器Logger對象,然後分別在程式碼中記錄不同級別日誌的輸出。運行項目,然後訪問介面。

 你會發現前面的trace日誌和debug日誌是不會輸出的,這你就知道了吧,不同等級的日誌有不同的功效,只會在特定的情況下輸出。這時候我們也可以自定義日誌級別了,在配置文件(yml)

logging:
  level:
    com:
      example: 
        demolog: debug

配置說明:

 這是什麼意思呢?我的包名是com.example.demolog,所以說這個配置就是說配置日誌所在包的輸出級別,是不是很高級。這樣就能輸出debug日誌了。如果你想輸出trace日誌你就將等級設置為trace就行了。

2. 日誌輸出到文件

 日誌輸出到控制台查看起來不是很方便,怎麼辦?沒關係,SpringBoot中還能將日誌輸出到指定的文件中,yml,添加如下配置。

logging:
  file:
    path: /spring/test/

 這個配置是說將日誌輸出到指定的目錄文件,並且會生成一個spring.log的日誌文件用來記錄日誌(如果你自己指定了文件名,它就會按照你自己設定的名字生成文件。),運行項目你就能直接看到生成的日誌所在,這個目錄如果你寫的和上面一致,那麼你的日誌文件就會在項目的運行根路徑,比如D盤,然後在D盤生成你寫的文件目錄/spring/test/,最後在文件目錄下面生成spring.log的日誌文件。這個路徑你也可以直接寫絕對路徑(直接指定這個文件在那個盤,那個文件夾)。

 file下面還有一個配置就是name屬性,

logging:
  file:
    name: test.log

 這個是直接指定你的日誌的文件名稱,默認生成的位置是在項目所在的目錄,你也可以自己寫絕對路徑配置日誌文件的位置,但是必須要自己設定文件的名稱。

 file的name屬性和path屬性只能指定一個,如果兩個同時指定的話,只有name屬性會生效。

3. 自定義日誌輸出格式

 有時候你可能會覺得這個日誌的輸出格式太難看了,想自己定義一個日誌輸出格式,完全ojbk!SpringBoot說:滿足你,自己想怎麼玩就怎麼玩!

logging:
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss}----- 這是全棧學習筆記 [%thread] %-5level %logger{50} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss}----- 這是全棧學習筆記 [%thread] %-5level %logger{50} - %msg%n"

#  d表示日期時間,        
#  %thread表示執行緒名,        
#  %‐5level:級別從左顯示5個字元寬度        
#  %logger{50} 表示logger名字最長50個字元,否則按照句點分割。  
#  %msg:日誌消息,        
#  %n是換行符

 上面的配置分別定義了控制台的日誌輸出格式與文件的日誌輸出格式,是不是很方便。輸出的格式大概就是這樣。

 當然我們還有一個更好的日誌配置,利用xml文件進行配置,一步到位就是這麼爽。

三、xml文件實現日誌配置的方式

 直接上xml文件的內容,建議將文件命名為logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:當此屬性設置為true時,配置文件如果發生改變,將會被重新載入,默認值為true。
scanPeriod:設置監測配置文件是否有修改的時間間隔,如果沒有給出時間單位,默認單位是毫秒當scan為true時,此屬性生效。默認的時間間隔為1分鐘。
debug:當此屬性設置為true時,將列印出logback內部日誌資訊,實時查看logback運行狀態。默認值為false。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定義日誌的根目錄   建議寫絕對路徑   如果不寫默認在項目運行的根路徑( D盤,C盤這種)-->
    <property name="LOG_HOME" value="D:/app/log" />
    <!-- 定義日誌文件名稱 -->
    <property name="appName" value="testxml"></property>
    <!-- ch.qos.logback.core.ConsoleAppender 表示控制台輸出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!--
        日誌輸出格式:
			%d表示日期時間,
			%thread表示執行緒名,
			%-5level:級別從左顯示5個字元寬度
			%logger{50} 表示logger名字最長50個字元,否則按照句點分割。
			%msg:日誌消息,
			%n是換行符
        -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} --學習筆記--[%thread] %-5level %logger{50} - %msg%n</pattern>
<!--            <springProfile name="dev">-->
<!--                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} == 學習筆記 == [%thread] -&ndash;&gt; %-5level %logger{50} - %msg%n</pattern>-->
<!--            </springProfile>-->
<!--            <springProfile name="prod">-->
<!--                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}  ==學習筆記==[%thread] %-5level %logger{50} - %msg%n</pattern>-->
<!--            </springProfile>-->

<!--            如果將這個文件的名字改成logback-spring.xml   就可以使用上面的功能,
                上面的功能是說可以根據不同的生產環境做不同的日誌列印
-->
        </layout>
    </appender>

    <!-- 滾動記錄文件,先將日誌記錄到指定文件,當符合某個條件時,將日誌記錄到其他文件 -->
    <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日誌文件的名稱 -->
        <file>${LOG_HOME}/${appName}.log</file>
        <!--
        當發生滾動時,決定 RollingFileAppender 的行為,涉及文件移動和重命名
        TimeBasedRollingPolicy: 最常用的滾動策略,它根據時間來制定滾動策略,既負責滾動也負責出發滾動。
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--
            滾動時產生的文件的存放位置及文件名稱 %d{yyyy-MM-dd}:按天進行日誌滾動
            %i:當文件大小超過maxFileSize時,按照i進行文件滾動
            -->
            <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <!--
            可選節點,控制保留的歸檔文件的最大數量,超出數量就刪除舊文件。假設設置每天滾動,
            且maxHistory是365,則只保存最近365天的文件,刪除之前的舊文件。注意,刪除舊文件是,
            那些為了歸檔而創建的目錄也會被刪除。
            -->
            <MaxHistory>365</MaxHistory>
            <!--
            當日誌文件超過maxFileSize指定的大小是,根據上面提到的%i進行日誌文件滾動 注意此處配置SizeBasedTriggeringPolicy是無法實現按文件大小進行滾動的,必須配置timeBasedFileNamingAndTriggeringPolicy
            -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日誌輸出格式: -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
        </layout>
    </appender>

    <!--
		logger主要用於存放日誌對象,也可以定義日誌類型、級別
		name:表示匹配的logger類型前綴,也就是包的前半部分
		level:要記錄的日誌級別,包括 TRACE < DEBUG < INFO < WARN < ERROR
		additivity:作用在於children-logger是否使用 rootLogger配置的appender進行輸出,
		false:表示只用當前logger的appender-ref,true:
		表示當前logger的appender-ref和rootLogger的appender-ref都有效
    -->
    <!-- hibernate logger -->
    <logger name="com.example.demolog" level="debug" ></logger>
    <!-- Spring framework logger -->
    <logger name="org.springframework" level="debug" additivity="false"></logger>



    <!--
    root與logger是父子關係,沒有特別定義則默認為root,任何一個類只會和一個logger對應,
    要麼是定義的logger,要麼是root,判斷的關鍵在於找到這個logger,然後判斷這個logger的appender和level。
    -->
    <root level="info">
        <appender-ref ref="stdout" />
        <appender-ref ref="appLogAppender" />
    </root>
</configuration>

 上面的xml配置文件配置就不細說了,裡面都有詳細的注釋說明。配置文件默認位置應該是直接放在resources下面,和yml,properties文件同級,當然你也可以自己配置文件位置的。

logging
  config: classpath:static/logback.xml

 這樣就將xml配置文件放在static路徑下面時能自動識別了。也可以設置為絕對路徑。

 上面我們建議將日誌文件設置為logback-spring.xml,如果我們的xml文件的名稱是logback.xml,它就會直接被日誌框架識別,如果你的xml文件是用logback-spring.xml命名,那麼他會被SpringBoot來識別並解析日誌配置,可以使用SpringBoot的高級Profile功能。這個高級功能我在xml文件中有注釋說明。往上看。你也可以去看Spring的官網,有詳細的配置說明。

四、AOP + 自定義註解實現統一日誌處理

 引入aop依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

 自定義註解,還不會註解的,看這裡,註解詳細介紹註解乾貨

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
    String value() default "";
}

 自定義切面類:


@Aspect
@Component
public class LogAspect {
    private Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Pointcut("@annotation(com.example.demolog.annotation.MyLog)")
    public void myPointCut(){
        //簽名,可以理解成這個切入點的一個名稱
    }
    @Before("myPointCut()")
    public void doBefore(JoinPoint joinPoint){
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //獲取url,請求方法,ip地址,類名以及方法名,參數
        logger.info("url={},method={},ip={},class_method={},args={}", request.getRequestURI(),request.getMethod(),request.getRemoteAddr(),joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(),joinPoint.getArgs());

    }
    @AfterReturning(pointcut = "myPointCut()")
    public void printLog(JoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        MyLog myLog = method.getAnnotation(MyLog.class);
        String value = null;
        if (myLog!=null){
            value = myLog.value();
        }
        logger.info(new Date()+"-----"+value);
    }
}

 上面配置完成之後再去controller的方法之上添加一個自定義的 @Mylog註解

@GetMapping("/testLog")
@MyLog("測試一個日誌")
public void testLog(){
    //和上面的一致
}

程式碼說明:

  • @Aspect:標明這是一個切面類

  • @Component:標明這是一個bean

  • @Pointcut(“@annotation(com.example.demolog.annotation.MyLog)”) 定義切入點為自定義的註解,也可以是一個類或者是一個包,包的寫法如下:

@Pointcut("execution(public * com.example.demolog.*(..))") 

 上面的意思是切入點是 所有在com.example,demolog包下面的以public為修飾,不限制返回值(*),不限制參數不限制名稱的類。

擴展知識:

  • @befor:前置通知,在一個方法執行之前被調用。
  • @after:在方法執行之後調用的通知,無論方法執行是否成功。
  • @after-returning:僅當方法成功完成之後通知。
  • @after-throwing:在方法拋出異常退出時執行的通知。
  • @around:在方法執行之前和之後調用的通知。

 然後我們再來測試一下介面:localhost:8098/testLog

本期分享到此結束,總結一下下!

五、總結

 本文先講解SpringBoot的默認日誌配置,然後自己在配置文件配置日誌的輸出等級,輸出格式,將日誌輸出到文件中,然後通過xml文件來配置日誌。最後我們引出了利用aop,簡化日誌的輸出,並且統一日誌的輸出格式。如果你覺得本文有用的話,點個贊吧!另外需要源碼的看下面。