1.起源:
(1)業(yè)務(wù)需求:
代碼的功能是寫一個api接口,完成與mysql的crud操作。
后臺使用SpringBoot+JdbcTemplate實現(xiàn)伊诵,并在controller層添加了一些請求丈冬、響應(yīng)、異常處理的日志币厕;
(2)問題產(chǎn)生:
由于該api接口是多方調(diào)用的列另,當(dāng)高并發(fā)場景調(diào)用這個api時,從controller層一路下來旦装,日志的記錄將會非骋逞茫混亂,所有業(yè)務(wù)日志都摻雜在一起阴绢。比如像下面這樣:
(3)一種解決方法:
當(dāng)產(chǎn)生異常日志時店乐,特別是高并發(fā)的情況下,作為服務(wù)端呻袭,你并不知道某條error日志報的錯對應(yīng)的是哪條請求眨八,除非將請求信息打印在日志中,但是仔細想想左电,這就需要所有需要添加日志的類都帶著請求信息廉侧,顯然這太難做到了页响,同時日志文件也將不堪重負。
2.更好的解決方案
有沒有想到過日志跟蹤呢段誊?如果不管是誰調(diào)用接口闰蚕,在其每次調(diào)用的時候,代碼都自動加上一個唯一的id該多好连舍,不管這次請求是到了service層没陡、dao層、甚至bean層索赏,只要是調(diào)用controller里面的接口盼玄,所有這條請求的日志流都加上了這個唯一的id,這樣再查找日志的時候参滴,將會很方便强岸。
而怎么才能在不影響已經(jīng)寫好業(yè)務(wù)邏輯的情況下,對日志流做統(tǒng)一處理呢砾赔?當(dāng)然是要想到AOP了~蝌箍!
具體操作如下:
(1)先配上aop的maven依賴(已經(jīng)有aop就不用再加了),這里要注意spring-aop的版本對應(yīng)上spring-core的版本:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
(2)再添加AOP處理類~暴心!這里只針對controller層妓盲。(不太了解aop配置的需要補習(xí)一下基本的配置方法)
package com.tools.pincollect.common;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.UUID;
@Aspect
@Configuration
public class SpringAOP {
private static final Logger logger = LoggerFactory.getLogger(SpringAOP.class);
/**
* 定義切點Pointcut
* 第一個*號:表示返回類型, *號表示所有的類型
* 第二個*號:表示類名专普,*號表示所有的類
* 第三個*號:表示方法名悯衬,*號表示所有的方法
* 后面括弧里面表示方法的參數(shù),兩個句點表示任何參數(shù)
*/
@Pointcut("execution(* com.tools.pincollect.controller.*.*(..))")
public void executionService() {}
/**
* 方法調(diào)用之前調(diào)用
* @param joinPoint
*/
@Before(value = "executionService()")
public void doBefore(JoinPoint joinPoint){
String requestId = String.valueOf(UUID.randomUUID());
MDC.put("requestId",requestId);
logger.info("=====>@Before:請求參數(shù)為:{}",Arrays.toString(joinPoint.getArgs()));
}
/**
* 方法之后調(diào)用
* @param joinPoint
* @param returnValue 方法返回值
*/
@AfterReturning(pointcut = "executionService()",returning="returnValue")
public void doAfterReturning(JoinPoint joinPoint,Object returnValue){
logger.info("=====>@AfterReturning:響應(yīng)參數(shù)為:{}",returnValue);
// 處理完請求檀夹,返回內(nèi)容
MDC.clear();
}
}
(3)在項目resources目錄下的logback-spring.xml中修改日志pattern(不了解的話筋粗,這里需要補充下日志配置的知識),添加[%X{requestId}]炸渡,requestId就是在切面類中的MCD.put()方法中的key娜亿,如下圖所示:
<pattern>
[ %-5level] [%date{yyyy-MM-dd HH:mm:ss}] [%X{requestId}] %logger{96} [%line] - %msg%n
</pattern>
有了跟蹤id,查找與定位日志是不是方便多了呢蚌堵?
3.究其原理
依賴的是log4j 和 logback 提供的一種方便在多線程條件下記錄日志的功能MDC(Mapped Diagnostic Context买决,映射調(diào)試上下文)。
MDC 可以看成是一個與當(dāng)前線程綁定的哈希表吼畏,可以往其中添加鍵值對督赤。MDC 中包含的內(nèi)容可以被同一線程中執(zhí)行的代碼所訪問。當(dāng)前線程的子線程會繼承其父線程中的 MDC 的內(nèi)容泻蚊。當(dāng)需要記錄日志時躲舌,只需要從 MDC 中獲取所需的信息即可。MDC 的內(nèi)容則由程序在適當(dāng)?shù)臅r候保存進去性雄。對于一個 Web 應(yīng)用來說孽糖,通常是在請求被處理的最開始保存這些數(shù)據(jù)枯冈。
具體內(nèi)容請參考:
https://blog.csdn.net/sunzhenhua0608/article/details/29175283