1. 程式人生 > >Spring Boot使用AOP搭建統一處理請求日誌和使用log4j記錄不同級別的日誌

Spring Boot使用AOP搭建統一處理請求日誌和使用log4j記錄不同級別的日誌

http://blog.didispace.com/springbootaoplog/啟發,今天給Spring Boot專案搭建了統一處理請求日誌的切面並引入log4j記錄不同層級日誌。 mark一下這個過程,以及原文中沒有涉及到的一些疑問

一.  新增要使用的依賴 

<!--日誌-->
<dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.17</version>
</dependency>
<!--aop-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
   <version>2.0.4.RELEASE</version>
</dependency>

在新匯入依賴的過程中剛好遇到公司網路從maven中央倉庫下載依賴受阻,所以也切換了專案的依賴源,在此順便記錄一下

   修改apache-maven-3.3.9中的conf資料夾下的setting.xml檔案內容,在<mirrors>節點下新增

<mirror>
  <id>alimaven</id> 
  <name>aliyun maven</name> 
  <url>http://maven.aliyun.com/nexus/content/groups/public/</url> 
  <mirrorOf>central</mirrorOf> 
</mirror>

再將IDEA中maven配置的“User settings file”修改成setting.xml檔案所在路徑(預設路徑/Users/xxx/.m2/setting.xml)

 

二.實現Web層的日誌切面,關於這個類的相關疑問可以參考頂部連結的文章

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

@Aspect
@Order(0)
@Component
public class WebLogAspect {

    ThreadLocal<Long> startTime = new ThreadLocal<>();

    private Logger logger = Logger.getLogger(getClass());

    @Pointcut("execution(public * com.example.controller..*.*(..))")
    public void webLog() {
    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        startTime.set(System.currentTimeMillis());
        // 接收到請求,記錄請求內容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();

            // 記錄下請求內容
            System.out.println("\r\n");
            logger.info("地址 : " + request.getRequestURL().toString());
            logger.info("請求方式 : " + request.getMethod());
            logger.info("IP : " + request.getRemoteAddr());
            logger.info("執行的方法 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
            Object[] args = joinPoint.getArgs().clone();
            logger.info("引數 : " + Arrays.toString(args));
        }
    }

    /**
     * 處理完請求,返回內容
     * @param ret
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) {
        logger.info("返回內容 : " + ret);
        logger.info("花費時間 : " + (System.currentTimeMillis() - startTime.get()) + "毫秒");
    }
}

通過@Pointcut定義切入點,此處是com.example.controller包下的所有Controller(對controller層所有請求處理做切入點),然後通過@Before實現對請求內容的日誌記錄,最後通過@AfterReturning記錄請求返回的物件。

實現AOP的切面主要有以下幾個要素:(轉)

  • 使用@Aspect註解將一個java類定義為切面類
  • 使用@Pointcut定義一個切入點,可以是一個規則表示式,比如下例中某個package下的所有函式,也可以是一個註解等。
  • 根據需要在切入點不同位置的切入內容
    • 使用@Before在切入點開始處切入內容
    • 使用@After在切入點結尾處切入內容
    • 使用@AfterReturning在切入點return內容之後切入內容(可以用來對處理返回值做一些加工處理)
    • 使用@Around在切入點前後切入內容,並自己控制何時執行切入點自身的內容
    • 使用@AfterThrowing用來處理當切入內容部分丟擲異常之後的處理邏輯

 

三. 使用log4j.properties記錄更詳細的日誌

在resources資料夾下與application.yml檔案同級目錄 建立log4j.properties,這樣將在專案根目錄下建立一個logs的目錄,用於存放不同的日誌檔案。

andlers= java.util.logging.ConsoleHandler
redirect.commons.logging = enabled

log4j.rootLogger=DEBUG, STDOUT, FILE, DAILY_FILE, ROLLING_FILE

### 輸出DEBUG 級別以上的日誌 ###
log4j.appender.DEBUG = org.apache.log4j.DailyRollingFileAppender
log4j.appender.DEBUG.File = logs/debug.log
log4j.appender.DEBUG.Append = true
log4j.appender.DEBUG.Threshold = DEBUG 
log4j.appender.DEBUG.layout = org.apache.log4j.PatternLayout
log4j.appender.DEBUG.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### direct log messages to stdout ###
log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
log4j.appender.STDOUT.Target=System.out
log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
log4j.appender.STDOUT.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} - %c{1}:%L -%-4r - %-5p - %m%n

### direct log messages to file ###
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.File=logs/file.log
###log4j.appender.FILE.File=${webapp.root}/WEB-INF/logs/file.log ###
### log4j:ERROR setFile(null,true) call failed.java.io.FileNotFoundException ###
log4j.appender.FILE.Append=true
log4j.appender.FILE.Threshold=ERROR
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### direct log messages to daily file ###
log4j.appender.DAILY_FILE=org.apache.log4j.DailyRollingFileAppender
log4j.appender.DAILY_FILE.File=logs/daily.log
log4j.appender.DAILY_FILE.Encoding=UTF-8
log4j.appender.DAILY_FILE.Threshold=INFO
log4j.appender.DAILY_FILE.DatePattern='.'yyyy-MM-dd
log4j.appender.DAILY_FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.DAILY_FILE.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L : %m%n

### direct log messages to rolling file ###
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLING_FILE.Threshold=ERROR
log4j.appender.ROLLING_FILE.File=logs/rolling.log
log4j.appender.ROLLING_FILE.Append=true
log4j.appender.CONSOLE_FILE.Encoding=UTF-8
log4j.appender.ROLLING_FILE.MaxFileSize=1000KB
log4j.appender.ROLLING_FILE.MaxBackupIndex=1
log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %-d{yyyy-MM-dd HH:mm:ss} - %c -%-4r [%t] %-5p %c %x - %m%n

### direct log messages to socket ###
log4j.appender.SOCKET=org.apache.log4j.RollingFileAppender
log4j.appender.SOCKET.RemoteHost=localhost
log4j.appender.SOCKET.Port=5001
log4j.appender.SOCKET.LocationInfo=true
# Set up for Log Facter 5
log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout
log4j.appender.SOCET.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD]%n%c[CATEGORY]%n%m[MESSAGE]%n%n
# Log Factor 5 Appender
log4j.appender.LF5_APPENDER=org.apache.log4j.lf5.LF5Appender
log4j.appender.LF5_APPENDER.MaxNumberOfRecords=2000

 

四. 搭建過程中遇到的一些疑問

  1. 報錯:log4j:WARN No appenders could be found for logger (com.example.aspect.WebLogAspect).
                 log4j:WARN Please initialize the log4j system properly.
                 log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. 

由於log4j.properties檔案配置不夠完整,使用上述的檔案就可以了

 2. Log4J配置後,啟動專案時控制檯出現  log4j:ERROR setFile(null,true) call failed. 報錯:java.io.FileNotFoundException

由於之前log4j.properties檔案中配置的是log4j.appender.FILE.File=${webapp.root}/WEB-INF/logs/file.log。tomcat中也有這個同名的檔案,tomcat啟動是預設去找log4j.properties,但此時Listener還沒有起來,tomcat就要往/WEB-INF/logs/log4j.log 寫日誌就找不到了。 解決: 將路徑改成 logs/xxx.log, 這樣就將這些不同日誌檔案生成在專案根目錄下的logs目錄中。

3. 關於Spring Boot專案中使用AOP是否需要在yml檔案中配置

根據查閱各位大佬的文章發現,Spring BootAOP的預設配置屬性是開啟的,也就是說spring.aop.auto屬性的值預設是true;同時我們只要引入了AOP依賴後,預設就已經增加了@EnableAspectJAutoProxy功能,不需要我們在程式啟動類上面加入註解@EnableAspectJAutoProxy,也不需要在yml中配置spring.aop.proxy-target-class,不過這個預設值是false即使用的是JDK動態代理,當我們需要使用CGLIB來實現AOP的時候,需要配置spring.aop.proxy-target-class=true。