最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot中全局日志記錄與請(qǐng)求鏈路追蹤實(shí)戰(zhàn)教學(xué)

 更新時(shí)間:2026年07月29日 09:08:20   作者:(farerboy)  
本文將手把手教你如何使用MDC、AOP和Filter搭建一套生產(chǎn)級(jí)全局日志系統(tǒng),自動(dòng)記錄接口入?yún)⒊鰠⒑秃臅r(shí),并實(shí)現(xiàn)TraceId全鏈路追蹤和敏感信息脫敏,讓你的系統(tǒng)可觀測(cè)性瞬間提升

在微服務(wù)和高并發(fā)架構(gòu)下,日志是排查問(wèn)題的“救命稻草”。本文將講解如何利用 AOP、MDC 等技術(shù),構(gòu)建一套包含請(qǐng)求追蹤、出入?yún)⒂涗?、敏感信息脫敏的全局日志系統(tǒng)。

一、 為什么需要全局日志和鏈路追蹤?

想象一下這樣的場(chǎng)景:用戶反饋下單失敗,客服給你一個(gè)訂單號(hào)。你開(kāi)始查日志…

  • 如果不帶唯一標(biāo)識(shí):你需要在一堆雜亂的并發(fā)請(qǐng)求中找到屬于那個(gè)用戶的那一次請(qǐng)求,猶如大海撈針。
  • 如果接口多:如果涉及多個(gè)服務(wù)調(diào)用,你需要跨機(jī)器拼接日志片段。

為了解決這些問(wèn)題,我們需要:

  1. 鏈路追蹤 (TraceId) :為每個(gè)請(qǐng)求生成唯一的 ID,貫穿整個(gè)處理流程,串聯(lián)起所有相關(guān)日志。
  2. 全局日志 :自動(dòng)記錄接口入?yún)ⅰ⒊鰠?、耗時(shí),減少手動(dòng)寫(xiě) log.info(...) 的工作量。

二、 核心組件:MDC (Mapped Diagnostic Context)

MDC 是 Slf4j 提供的一個(gè)機(jī)制,它實(shí)際上是一個(gè)線程安全的 ThreadLocal<Map>。我們可以往里面放入鍵值對(duì)(如 TraceId),然后在 Logback 配置中通過(guò) %X{key} 的格式輸出。

1. 生成 TraceId

我們需要一個(gè)工具類來(lái)管理 TraceId。

public class TraceIdUtil {
    
    private static final String TRACE_ID_KEY = "traceId";

    /**
     * 獲取當(dāng)前線程的 TraceId
     */
    public static String getTraceId() {
        String traceId = MDC.get(TRACE_ID_KEY);
        return StringUtils.isEmpty(traceId) ? "" : traceId;
    }

    /**
     * 設(shè)置 TraceId
     */
    public static void setTraceId(String traceId) {
        MDC.put(TRACE_ID_KEY, traceId);
    }

    /**
     * 清除 TraceId (非常重要,防止線程池復(fù)用導(dǎo)致數(shù)據(jù)污染)
     */
    public static void removeTraceId() {
        MDC.remove(TRACE_ID_KEY);
    }
}

2. 通過(guò) Filter 攔截請(qǐng)求并設(shè)置 TraceId

在請(qǐng)求進(jìn)入 Controller 之前,我們通過(guò)一個(gè) Filter 來(lái)生成并設(shè)置 TraceId。

@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE) // 確保最先執(zhí)行
public class TraceIdFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        try {
            // 嘗試從請(qǐng)求頭獲取 TraceId(適用于網(wǎng)關(guān)傳遞過(guò)來(lái)的場(chǎng)景)
            String traceId = httpRequest.getHeader("X-Trace-Id");
            if (StringUtils.isEmpty(traceId)) {
                traceId = UUID.randomUUID().toString().replace("-", "");
            }
            TraceIdUtil.setTraceId(traceId);
            
            chain.doFilter(request, response);
        } finally {
            // 務(wù)必清理,防止內(nèi)存泄漏或線程池復(fù)用導(dǎo)致的臟數(shù)據(jù)
            TraceIdUtil.removeTraceId();
        }
    }
}

3. 配置 Logback 輸出格式

修改 logback-spring.xml,在 pattern 中加入 %X{traceId}

<configuration>
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n"/>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

現(xiàn)在,你的日志輸出將類似這樣:2023-10-01 12:00:00.001 [http-nio-8080-exec-1] [a1b2c3d4e5f6...] INFO c.e.controller.UserController - User created successfully

三、 AOP 實(shí)現(xiàn)全局接口日志

雖然 MDC 幫我們串聯(lián)了日志,但我們還需要知道每個(gè)接口的入?yún)?、出參和耗時(shí)。手動(dòng)打印太麻煩,使用 AOP (Aspect Oriented Programming) 是最好的選擇。

1. 引入依賴與啟用 AOP

確保引入了 spring-boot-starter-aop。

2. 編寫(xiě)日志切面

我們定義一個(gè)切面,攔截所有 Controller 層的 public 方法。

@Slf4j
@Aspect
@Component
public class ControllerLogAspect {

    // 定義切點(diǎn):攔截 Controller 包下的所有方法
    @Pointcut("execution(public * com.example..controller..*.*(..))")
    public void controllerLog() {}

    @Around("controllerLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        // 獲取請(qǐng)求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        
        // 構(gòu)建日志對(duì)象
        LogInfo logInfo = new LogInfo();
        logInfo.setTraceId(TraceIdUtil.getTraceId());
        logInfo.setMethod(request.getMethod());
        logInfo.setUrl(request.getRequestURI());
        logInfo.setIp(request.getRemoteAddr());
        logInfo.setClassMethod(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        
        // 獲取入?yún)?
        Object[] args = joinPoint.getArgs();
        // 注意:過(guò)濾掉 HttpServletRequest, HttpServletResponse 等無(wú)法序列化的參數(shù)
        Object[] logArgs = Arrays.stream(args)
                .filter(arg -> !(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse))
                .toArray();
        logInfo.setArgs(logArgs);
        
        // 打印入?yún)⑷罩?
        log.info("Request Info: {}", JSON.toJSONString(logInfo));

        Object result;
        try {
            result = joinPoint.proceed();
        } catch (Exception e) {
            // 記錄異常日志(通常由全局異常處理器處理,這里也可以記錄一份用于審計(jì))
            log.error("Controller Exception: ", e);
            throw e;
        }
        
        // 計(jì)算耗時(shí)
        long costTime = System.currentTimeMillis() - startTime;
        logInfo.setCostTime(costTime);
        logInfo.setResult(result);
        
        // 打印出參日志
        log.info("Response Info ({}ms): {}", costTime, JSON.toJSONString(logInfo));
        
        return result;
    }

    @Data
    public static class LogInfo {
        private String traceId;
        private String method;
        private String url;
        private String ip;
        private String classMethod;
        private Object[] args;
        private Object result;
        private Long costTime;
    }
}

四、 敏感信息脫敏

在實(shí)際生產(chǎn)環(huán)境中,日志里絕對(duì)不能出現(xiàn)明文密碼、身份證號(hào)、手機(jī)號(hào)等敏感信息。我們可以在序列化(如 Jackson 或 Fastjson)輸出日志前進(jìn)行處理。

自定義脫敏序列化器 (以 Jackson 為例)

public class SensitiveDataSerializer extends JsonSerializer<String> {
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (StringUtils.isEmpty(value)) {
            gen.writeNull();
            return;
        }
        // 簡(jiǎn)單的脫敏邏輯:保留前 3 后 4,中間星號(hào)
        if (value.length() > 8) {
            gen.writeString(value.substring(0, 3) + "****" + value.substring(value.length() - 4));
        } else {
            gen.writeString("****");
        }
    }
}

使用方式:在 DTO 的敏感字段上添加注解

public class UserDTO {
    
    @JsonSerialize(using = SensitiveDataSerializer.class)
    private String phone;
    
    // ...
}

或者在 AOP 中通過(guò)正則替換整個(gè) JSON 字符串后再打印。

五、 最佳實(shí)踐

  1. 異步日志 (AsyncAppender) :生產(chǎn)環(huán)境日志量巨大,同步打印日志會(huì)阻塞業(yè)務(wù)線程。務(wù)必在 Logback 配置中使用 AsyncAppender 包裝 ConsoleFile Appender。
  2. 線程池透?jìng)?MDC :如果你的代碼使用了 @Async 或自定義線程池,MDC 的值不會(huì)自動(dòng)傳遞到子線程。你需要實(shí)現(xiàn) TaskDecoratorThreadPoolTaskExecutor 的包裝類,在提交任務(wù)時(shí)拷貝 MDC 上下文,執(zhí)行完畢后恢復(fù)。
  3. 避免打印大對(duì)象 :如果入?yún)笪募鳎∕ultipartFile)或巨大的列表,不要直接打印 JSON,會(huì)撐爆日志文件。在 AOP 中判斷參數(shù)類型進(jìn)行過(guò)濾。
  4. 日志級(jí)別控制 :不要把所有日志都設(shè)為 INFO。開(kāi)發(fā)環(huán)境可以是 DEBUG,生產(chǎn)環(huán)境建議 INFO 或 WARN,Error 必須報(bào)警。

六、 總結(jié)

通過(guò) MDC + Filter + AOP 的組合拳,我們實(shí)現(xiàn)了:

  1. 全鏈路追蹤:每個(gè)請(qǐng)求都有唯一的 TraceId。
  2. 自動(dòng)審計(jì) :無(wú)需修改業(yè)務(wù)代碼,自動(dòng)記錄接口出入?yún)⒑秃臅r(shí)。
  3. 數(shù)據(jù)安全 :敏感信息脫敏,避免合規(guī)風(fēng)險(xiǎn)。

這套方案是構(gòu)建生產(chǎn)級(jí) SpringBoot 應(yīng)用的標(biāo)配,掌握它將為你的系統(tǒng)可觀測(cè)性打下堅(jiān)實(shí)基礎(chǔ)。

到此這篇關(guān)于SpringBoot中全局日志記錄與請(qǐng)求鏈路追蹤實(shí)戰(zhàn)教學(xué)的文章就介紹到這了,更多相關(guān)SpringBoot全局日志系統(tǒng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實(shí)現(xiàn)十秒向MySQL插入百萬(wàn)條數(shù)據(jù)

    Java實(shí)現(xiàn)十秒向MySQL插入百萬(wàn)條數(shù)據(jù)

    這篇文章主要為大家詳細(xì)介紹了Java如何實(shí)現(xiàn)十秒向MySQL插入百萬(wàn)條數(shù)據(jù),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定借鑒價(jià)值,需要的可以參考一下
    2022-11-11
  • springboot對(duì)壓縮請(qǐng)求的處理方法

    springboot對(duì)壓縮請(qǐng)求的處理方法

    這篇文章主要介紹了springboot對(duì)壓縮請(qǐng)求的處理,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-05-05
  • Java使用DSA密鑰對(duì)生成XML簽名的方法

    Java使用DSA密鑰對(duì)生成XML簽名的方法

    這篇文章主要介紹了Java使用DSA密鑰對(duì)生成XML簽名的方法,實(shí)例分析了java使用DSA密鑰對(duì)生成XML簽名的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-03-03
  • 淺談Java(SpringBoot)基于zookeeper的分布式鎖實(shí)現(xiàn)

    淺談Java(SpringBoot)基于zookeeper的分布式鎖實(shí)現(xiàn)

    這篇文章主要介紹了Java(SpringBoot)基于zookeeper的分布式鎖實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • IDEA 格式化SQL代碼技巧分享

    IDEA 格式化SQL代碼技巧分享

    這篇文章主要介紹了IDEA 格式化SQL代碼技巧分享,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-02-02
  • IntelliJ IDEA 常用設(shè)置(配置)吐血整理(首次安裝必需)

    IntelliJ IDEA 常用設(shè)置(配置)吐血整理(首次安裝必需)

    這篇文章主要介紹了IntelliJ IDEA 常用設(shè)置(配置)吐血整理(首次安裝必需),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • Java Socket循環(huán)接收數(shù)據(jù)readLine()阻塞的解決方案

    Java Socket循環(huán)接收數(shù)據(jù)readLine()阻塞的解決方案

    這篇文章主要介紹了Java Socket循環(huán)接收數(shù)據(jù)readLine()阻塞的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • SpringBoot實(shí)現(xiàn)自定義指標(biāo)監(jiān)控功能

    SpringBoot實(shí)現(xiàn)自定義指標(biāo)監(jiān)控功能

    本文主要介紹了SpringBoot實(shí)現(xiàn)自定義指標(biāo)監(jiān)控功能的實(shí)現(xiàn),,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,感興趣的小伙伴跟著著小編來(lái)一起來(lái)學(xué)習(xí)吧
    2024-01-01
  • Java實(shí)現(xiàn)異步執(zhí)行的8種方式總結(jié)

    Java實(shí)現(xiàn)異步執(zhí)行的8種方式總結(jié)

    這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)異步執(zhí)行的8種方式,異步編程不會(huì)阻塞程序的執(zhí)行,它將耗時(shí)的操作提交給后臺(tái)線程或其他執(zhí)行環(huán)境,并立即返回,使得程序可以繼續(xù)執(zhí)行其他任務(wù),需要的朋友可以參考下
    2023-09-09
  • SpringBoot整合Swagger框架過(guò)程解析

    SpringBoot整合Swagger框架過(guò)程解析

    這篇文章主要介紹了SpringBoot整合Swagger框架過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05

最新評(píng)論

修文县| 栾城县| 临猗县| 绥江县| 郧西县| 罗山县| 阜城县| 嘉兴市| 阿拉善右旗| 太仆寺旗| 禄劝| 通榆县| 威远县| 灵丘县| 儋州市| 内江市| 靖州| 柳江县| 安义县| 扎囊县| 宁都县| 伊吾县| 新源县| 福建省| 黄山市| 瓮安县| 游戏| 平山县| 额敏县| 花莲市| 阳原县| 乐至县| 资阳市| 惠水县| 卢湾区| 靖州| 麻江县| 晋宁县| 五常市| 巴彦县| 连江县|