日志是一個Web項目中必不可少的部分,借助它我們可以做許多事情,比如問題排查、訪問統計、監控告警等。一般通過引入slf4j的一些實現框架來做日志功能,如log4j,logback,log4j2,其性能也是依次增強。在springboot中,默認使用的框架是logback。我們經常需要在方法開頭或結尾加日志記錄傳入參數或返回結果,以此來復現當時的請求情況。但是手動添加日志,不僅繁瑣重復,也影響代碼的美觀簡潔。本文引入一個基於AOP實現的日志框架,並通過spring-boot-starter的方式完成集成。
原文地址:http://blog.jboost.cn/2019/06/27/springboot-aoplog.html
1. aop-logging項目
項目地址: https://github.com/ronwxy/aop-logging
該項目基於 https://github.com/nickvl/aop-logging.git , 在其基礎上添加了ReqId來串聯某次客戶端請求(參考com.github.nickvl.xspring.core.log.aop.ReqIdFilter), 添加了方法執行時長(參考com.github.nickvl.xspring.core.log.aop.AOPLogger.logTheMethod方法中elapsedTime)。
該項目提供了基於注解的AOP日志功能。根據不同的日志級別,提供的注解有LogTrace,LogDebug,LogInfo,LogWarn,LogError,LogFatal,LogException,可修飾於類(等同於該類內所有方法上添加)與方法上,前面六個分別表示在不同日志級別下記錄方法被調用的日志,LogException表示在方法拋出異常時,記錄相應日志。這些注解都提供了一個LogPoint枚舉類型的屬性value,取值{IN,OUT,BOTH},分別表示在方法調用入口、方法調用返回前,以及包含兩者的位置打印對應日志,默認為BOTH。
2. 集成
可以通過基於xml或基於java配置的方式來集成AOP日志功能,我這里基於java配置(基於xml的方式參考源碼README文件)並且通過spring-boot-starter的形式進行封裝(源碼地址: https://github.com/ronwxy/base-spring-boot ),避免每個項目都需要配置。自動配置類如下
@Configuration
@ConditionalOnClass(AOPLogger.class)
@ConditionalOnMissingBean(AOPLogger.class)
public class AopLoggerAutoConfiguration {
private static final boolean SKIP_NULL_FIELDS = true;
private static final Set<String> EXCLUDE_SECURE_FIELD_NAMES = Collections.emptySet();
@Bean
public AOPLogger aopLogger() {
AOPLogger aopLogger = new AOPLogger();
aopLogger.setLogAdapter(new UniversalLogAdapter(SKIP_NULL_FIELDS, EXCLUDE_SECURE_FIELD_NAMES));
return aopLogger;
}
/**
* 注冊一個過濾器,用來生成一個reqId,標記一次請求,從而將本次請求所產生的日志串聯起來
* @param
* @return
*/
@Bean
public FilterRegistrationBean reqIdFilter() {
ReqIdFilter reqIdFilter = new ReqIdFilter();
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(reqIdFilter);
List<String> urlPatterns = Collections.singletonList("/*");
registrationBean.setUrlPatterns(urlPatterns);
registrationBean.setOrder(100);
return registrationBean;
}
}
將基礎框架base-spring-boot通過mvn clean install進行本地安裝后,即可在項目中通過依賴進行引入(基礎框架中已在spring-boot-parent中引入,直接繼承亦可),如
<dependency> <groupId>cn.jboost.springboot</groupId> <artifactId>aoplog-spring-boot-starter</artifactId> <version>1.2-SNAPSHOT</version> </dependency>
3. 使用
引入依賴之后,我們再定義一個日志配置文件logback-spring.xml,為了后面方便地將日志導入ELK做集中的日志分析管理,該配置文件中將日志以json格式輸出,並根據日志級別分別寫入debug.log,info.log,warn.log,error.log以及interface.log(專用於接口訪問日志),配置示例如下(完整配置參考: https://github.com/ronwxy/springboot-demos/blob/master/springboot-aoplog/src/main/resources/logback-spring.xml)
<appender name="interfaceLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/elk/interface.log</file>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<pattern>
<pattern>
{
"project": "${projectName}",
"timestamp": "%date{\"yyyy-MM-dd'T'HH:mm:ss,SSSZ\"}",
"log_level": "%level",
"thread": "%thread",
"class_name": "%X{callingClass}",
"class_method":"%X{callingMethod}",
"line_number": null,
"message": "%message",
"stack_trace": "%exception{5}",
"req_id": "%X{reqId}",
"elapsed_time": "#asLong{%X{elapsedTime}}"
}
</pattern>
</pattern>
</providers>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/interface.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
為了將該日志配置文件可以不經修改地達到復用,將一些參數配置外置了,故需在配置文件applicaiton.yml中配置如下參數
logger: path: D:\logs #默認當前項目路徑下的logs目錄 level: info # 默認info apiPackage: cn.jboost.springboot.aoplog.controller #必須配置, api接口類所在包 rootPackage: cn.jboost.springboot #必須配置,項目根包,記錄該包內各類通過slf4j輸出的日志
最后,直接在需要記錄訪問日志的接口類上加注解@LogInfo就行了,如
@RestController
@RequestMapping("test")
@LogInfo
public class AoplogTestController {
@GetMapping
public String test(@RequestParam String user){
return "Hi " + user;
}
}
注意:在pom.xml中默認添加的spring-boot-maven-plugin下需要添加repackage的goal才能自動生成日志目錄與日志文件,如下所示
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
啟動程序,調用@LogInfo標注的接口類下的API時,可以看到控制台有打印接口訪問日志,如執行demo程序(源碼: https://github.com/ronwxy/springboot-demos/tree/master/springboot-aoplog ),調用 http://localhost:8080/test?user=jboost 時,控制台打印日志如下
[2019-06-27 14:29:59] [INFO ] [http-nio-8080-exec-1] [cn.jboost.springboot.aoplog.controller.AoplogTestController:184] --calling: test(user=jboost) [2019-06-27 14:29:59] [INFO ] [http-nio-8080-exec-1] [cn.jboost.springboot.aoplog.controller.AoplogTestController:189] --returning: test(1 arguments):Hi jboost
日志文件interface.log中打印日志如下,(其中req_id在本次請求的所有日志都相同,這樣就可以將一次請求的所有日志串聯起來,便於分析與定位問題;elapsed_time標明了方法執行時長,可用於接口性能監測)
{"project":"aoplog-test","timestamp":"2019-06-27T14:29:59,030+0800","log_level":"INFO","thread":"http-nio-8080-exec-1","class_name":"cn.jboost.springboot.aoplog.controller.AoplogTestController","class_method":"test","line_number":null,"message":"calling: test(user=jboost)","stack_trace":"","req_id":"5d146267aa147904bc014e71","elapsed_time":null}
{"project":"aoplog-test","timestamp":"2019-06-27T14:29:59,036+0800","log_level":"INFO","thread":"http-nio-8080-exec-1","class_name":"cn.jboost.springboot.aoplog.controller.AoplogTestController","class_method":"test","line_number":null,"message":"returning: test(1 arguments):Hi jboost","stack_trace":"","req_id":"5d146267aa147904bc014e71","elapsed_time":2}
4. 總結
Web項目中經常需要通過查看接口請求及返回參數來定位問題,手動編寫代碼打印顯得繁瑣而重復。使用aop-logging通過簡單的注解即可實現接口日志自動打印。本文介紹的方案與日志配置模板可直接用於實際項目開發。當然,注解不僅可用於Controller層,也可以用於Service等其它層,但一般Controller層加上即可,避免日志打印過多。

