SpringBoot查詢?nèi)罩荆禾砑尤罩靖檌d

1.起源:

(1)業(yè)務(wù)需求:

代碼的功能是寫一個(gè)api接口,完成與mysql的crud操作。
后臺使用SpringBoot+JdbcTemplate實(shí)現(xiàn),并在controller層添加了一些請求、響應(yīng)、異常處理的日志;

(2)問題產(chǎn)生:

由于該api接口是多方調(diào)用的,當(dāng)高并發(fā)場景調(diào)用這個(gè)api時(shí),從controller層一路下來,日志的記錄將會非?;靵y,所有業(yè)務(wù)日志都摻雜在一起。比如像下面這樣:

(3)一種解決方法:

當(dāng)產(chǎn)生異常日志時(shí),特別是高并發(fā)的情況下,作為服務(wù)端,你并不知道某條error日志報(bào)的錯(cuò)對應(yīng)的是哪條請求,除非將請求信息打印在日志中,但是仔細(xì)想想,這就需要所有需要添加日志的類都帶著請求信息,顯然這太難做到了,同時(shí)日志文件也將不堪重負(fù)。

2.更好的解決方案

有沒有想到過日志跟蹤呢?如果不管是誰調(diào)用接口,在其每次調(diào)用的時(shí)候,代碼都自動加上一個(gè)唯一的id該多好,不管這次請求是到了service層、dao層、甚至bean層,只要是調(diào)用controller里面的接口,所有這條請求的日志流都加上了這個(gè)唯一的id,這樣再查找日志的時(shí)候,將會很方便。

而怎么才能在不影響已經(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配置的需要補(bǔ)習(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);
 
    /**
     * 定義切點(diǎn)Pointcut
     * 第一個(gè)*號:表示返回類型, *號表示所有的類型
     * 第二個(gè)*號:表示類名,*號表示所有的類
     * 第三個(gè)*號:表示方法名,*號表示所有的方法
     * 后面括弧里面表示方法的參數(shù),兩個(gè)句點(diǎn)表示任何參數(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)在項(xiàng)目resources目錄下的logback-spring.xml中修改日志pattern(不了解的話,這里需要補(bǔ)充下日志配置的知識),添加[%X{requestId}],requestId就是在切面類中的MCD.put()方法中的key,如下圖所示:
   <pattern>
        [ %-5level] [%date{yyyy-MM-dd HH:mm:ss}] [%X{requestId}] %logger{96} [%line] - %msg%n
    </pattern>
添加了跟蹤id后的日志.png

有了跟蹤id,查找與定位日志是不是方便多了呢?

3.究其原理

依賴的是log4j 和 logback 提供的一種方便在多線程條件下記錄日志的功能MDC(Mapped Diagnostic Context,映射調(diào)試上下文)。

MDC 可以看成是一個(gè)與當(dāng)前線程綁定的哈希表,可以往其中添加鍵值對。MDC 中包含的內(nèi)容可以被同一線程中執(zhí)行的代碼所訪問。當(dāng)前線程的子線程會繼承其父線程中的 MDC 的內(nèi)容。當(dāng)需要記錄日志時(shí),只需要從 MDC 中獲取所需的信息即可。MDC 的內(nèi)容則由程序在適當(dāng)?shù)臅r(shí)候保存進(jìn)去。對于一個(gè) Web 應(yīng)用來說,通常是在請求被處理的最開始保存這些數(shù)據(jù)。
具體內(nèi)容請參考:
https://blog.csdn.net/sunzhenhua0608/article/details/29175283

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容