欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot 項目添加 MDC 日志鏈路追蹤的執(zhí)行流程

 更新時間:2021年06月20日 17:09:48   作者:一線大碼  
日志鏈路追蹤就是將一個標(biāo)志跨線程進行傳遞,在一般的小項目中也就是在你新起一個線程的時候,或者使用線程池執(zhí)行任務(wù)的時候會用到,比如追蹤一個用戶請求的完整執(zhí)行流程,本文給大家介紹SpringBoot MDC 日志鏈路追蹤的代碼,感興趣的朋友一起看看吧

日志鏈路追蹤的意思就是將一個標(biāo)志跨線程進行傳遞,在一般的小項目中也就是在你新起一個線程的時候,或者使用線程池執(zhí)行任務(wù)的時候會用到,比如追蹤一個用戶請求的完整執(zhí)行流程。

這里用到MDCThreadLocal,分別由下面的包提供:

java.lang.ThreadLocal
org.slf4j.MDC

直接上代碼:

1. 線程池配置

如果你直接通過手動新建線程來執(zhí)行異步任務(wù),想要實現(xiàn)標(biāo)志傳遞的話,需要自己去實現(xiàn),其實和線程池一樣,也是調(diào)用MDC的相關(guān)方法,如下所示:

//取出父線程的MDC
Map<String, String> context = MDC.getCopyOfContextMap();
//將父線程的MDC內(nèi)容傳給子線程
MDC.setContextMap(context);

首先提供一個常量:

package com.example.demo.common.constant;

/**
 * 常量
 *
 * @author wangbo
 * @date 2021/5/13
 */
public class Constants {
    public static final String LOG_MDC_ID = "trace_id";
}

接下來需要對ThreadPoolTaskExecutor的方法進行重寫:

package com.example.demo.common.threadpool;

import com.example.demo.common.constant.Constants;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;

/**
 * MDC線程池
 * 實現(xiàn)內(nèi)容傳遞
 *
 * @author wangbo
 * @date 2021/5/13
 */
@Slf4j
public class MdcTaskExecutor extends ThreadPoolTaskExecutor {

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        log.info("mdc thread pool task executor submit");
        Map<String, String> context = MDC.getCopyOfContextMap();
        return super.submit(() -> {
            T result;
            if (context != null) {
                //將父線程的MDC內(nèi)容傳給子線程
                MDC.setContextMap(context);
            } else {
                //直接給子線程設(shè)置MDC
                MDC.put(Constants.LOG_MDC_ID, UUID.randomUUID().toString().replace("-", ""));
            }
            try {
                //執(zhí)行任務(wù)
                result = task.call();
            } finally {
                try {
                    MDC.clear();
                } catch (Exception e) {
                    log.warn("MDC clear exception", e);
                }
            }
            return result;
        });
    }

    @Override
    public void execute(Runnable task) {
        log.info("mdc thread pool task executor execute");
        Map<String, String> context = MDC.getCopyOfContextMap();
        super.execute(() -> {
            if (context != null) {
                //將父線程的MDC內(nèi)容傳給子線程
                MDC.setContextMap(context);
            } else {
                //直接給子線程設(shè)置MDC
                MDC.put(Constants.LOG_MDC_ID, UUID.randomUUID().toString().replace("-", ""));
            }
            try {
                //執(zhí)行任務(wù)
                task.run();
            } finally {
                try {
                    MDC.clear();
                } catch (Exception e) {
                    log.warn("MDC clear exception", e);
                }
            }
        });
    }
}

然后使用自定義的重寫子類MdcTaskExecutor來實現(xiàn)線程池配置:

package com.example.demo.common.threadpool;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 線程池配置
 *
 * @author wangbo
 * @date 2021/5/13
 */
@Slf4j
@Configuration
public class ThreadPoolConfig {
    /**
     * 異步任務(wù)線程池
     * 用于執(zhí)行普通的異步請求,帶有請求鏈路的MDC標(biāo)志
     */
    @Bean
    public Executor commonThreadPool() {
        log.info("start init common thread pool");
        //ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        MdcTaskExecutor executor = new MdcTaskExecutor();
        //配置核心線程數(shù)
        executor.setCorePoolSize(10);
        //配置最大線程數(shù)
        executor.setMaxPoolSize(20);
        //配置隊列大小
        executor.setQueueCapacity(3000);
        //配置空閑線程存活時間
        executor.setKeepAliveSeconds(120);
        //配置線程池中的線程的名稱前綴
        executor.setThreadNamePrefix("common-thread-pool-");
        //當(dāng)達到最大線程池的時候丟棄最老的任務(wù)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        //執(zhí)行初始化
        executor.initialize();
        return executor;
    }

    /**
     * 定時任務(wù)線程池
     * 用于執(zhí)行自啟動的任務(wù)執(zhí)行,父線程不帶有MDC標(biāo)志,不需要傳遞,直接設(shè)置新的MDC
     * 和上面的線程池沒啥區(qū)別,只是名字不同
     */
    @Bean
    public Executor scheduleThreadPool() {
        log.info("start init schedule thread pool");
        MdcTaskExecutor executor = new MdcTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(3000);
        executor.setKeepAliveSeconds(120);
        executor.setThreadNamePrefix("schedule-thread-pool-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        executor.initialize();
        return executor;
    }
}

2. 攔截器配置

package com.example.demo.common.interceptor;

import com.example.demo.common.constant.Constants;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 * 日志攔截器
 *
 * @author wangbo
 * @date 2021/5/13
 */
@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //log.info("進入 LogInterceptor");
        //添加MDC值
        MDC.put(Constants.LOG_MDC_ID, UUID.randomUUID().toString().replace("-", ""));
        //打印接口請求信息
        String method = request.getMethod();
        String uri = request.getRequestURI();
        log.info("[請求接口] : {} : {}", method, uri);
        //打印請求參數(shù)
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //log.info("執(zhí)行 LogInterceptor");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //log.info("退出 LogInterceptor");
        //打印請求結(jié)果
        //刪除MDC值
        MDC.remove(Constants.LOG_MDC_ID);
    }
}

對攔截器進行注冊:

package com.example.demo.common.config;

import com.example.demo.common.interceptor.LogInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * MVC配置
 *
 * @author wangbo
 * @date 2021/5/13
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private LogInterceptor logInterceptor;
    
    /**
     * 攔截器注冊
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logInterceptor);
    }
}

3. 日志文件配置

需要在logback-spring.xml文件中的日志打印格式里添加%X{trace_id},如下所示:

<!-- 控制臺打印日志的相關(guān)配置 -->
<appender name="console_out" class="ch.qos.logback.core.ConsoleAppender">
     <!-- 日志格式 -->
     <encoder>
         <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{trace_id}] [%level] [%thread] [%class:%line] - %m%n</pattern>
         <charset>UTF-8</charset>
     </encoder>
 </appender>

4. 使用方法示例

4.1. 異步使用

這里注意,異步方法的調(diào)用不能直接調(diào)用當(dāng)前類的方法,也就是說調(diào)用方法和異步方法不能在同一個類里,否則會變?yōu)橥綀?zhí)行。

 /**
     * 異步方法
     */
    //@Async//這種寫法,當(dāng)只有一個線程池時,會使用該線程池執(zhí)行,有多個則會使用SimpleAsyncTaskExecutor
    @Async(value = "commonThreadPool")//指定執(zhí)行的線程池
    @Override
    public void async() {
        log.info("測試異步線程池");
    }

4.2. 定時任務(wù)

package com.example.demo.generator.crontab;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/**
 * 定時任務(wù)
 *
 * @author wangbo
 * @date 2021/5/14
 */
@Slf4j
@Component
public class TestTimeTask {
    //基于注解@Scheduled默認為單線程,開啟多個任務(wù)時,任務(wù)的執(zhí)行時機會受上一個任務(wù)執(zhí)行時間的影響。
    //使用的線程池是taskScheduler,線程ID為scheduling-x
    //添加@Async注解指定線程池,則可以多線程執(zhí)行定時任務(wù)(原本是單線程的)。

    /**
     * 兩次任務(wù)開始的時間間隔為2S
     * 不使用線程池,單線程間隔則為4S。單線程保證不了這個2S間隔,因為任務(wù)執(zhí)行耗時超過了定時間隔,就會影響下一次任務(wù)的執(zhí)行
     * 使用線程池,多線程執(zhí)行,時間間隔為2S
     */
    //@Async(value = "scheduleThreadPool")
    //@Scheduled(fixedRate = 2000)
    public void fixedRate() {
        log.info("定時間隔任務(wù) fixedRate = {}", LocalDateTime.now());
        try {
            Thread.sleep(4_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 下次任務(wù)的開始時間距離上次任務(wù)的結(jié)束時間間隔為2S
     * 這種適合使用單線程,不適合使用線程池,單線程間隔則為6S。
     * 用了線程池,和這個特性相背離了
     */
    //@Scheduled(fixedDelay = 2_000)
    public void fixedDelay() {
        log.info("延遲定時間隔任務(wù) fixedDelay = {}", LocalDateTime.now());
        try {
            Thread.sleep(4_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 首次延遲10S后執(zhí)行fixedDelay類型間隔任務(wù),也可以配置為fixedDelay類型間隔任務(wù)
     * 控件第一次執(zhí)行之前要延遲的毫秒數(shù)
     * {@link # fixeddrate} or {@link #fixedDelay}
     */
    //@Scheduled(initialDelay = 10_000, fixedDelay = 1_000)
    public void initialDelay() {
        log.info("首次延遲定時間隔任務(wù) initialDelay = {}", LocalDateTime.now());
    }

    /**
     * 這里使用線程池也是為了防止任務(wù)執(zhí)行耗時超過了定時間隔,就會影響下一次任務(wù)的執(zhí)行
     */
    //@Async(value = "scheduleThreadPool")
    //@Scheduled(cron = "0/2 * * * * *")
    public void testCron() {
        log.info("測試表達式定時任務(wù) testCron = {}", LocalDateTime.now());
        try {
            Thread.sleep(4_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

到此這篇關(guān)于SpringBoot 項目添加 MDC 日志鏈路追蹤的文章就介紹到這了,更多相關(guān)SpringBoot MDC 日志鏈路追蹤內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java輸入時如何通過回車(enter)來結(jié)束輸入

    java輸入時如何通過回車(enter)來結(jié)束輸入

    這篇文章主要介紹了java輸入時如何通過回車(enter)來結(jié)束輸入,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • 一篇文章徹底搞懂jdk8線程池

    一篇文章徹底搞懂jdk8線程池

    線程是稀缺資源,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控,這篇文章主要給大家介紹了jdk8線程池的相關(guān)資料,需要的朋友可以參考下
    2021-10-10
  • java 排序算法之歸并排序

    java 排序算法之歸并排序

    本文主要講解了排序算法中的歸并排序,文中運用大量的圖片和代碼講解的非常詳細,感興趣的朋友可以學(xué)習(xí)一下這篇文章,相信可以幫助到你
    2021-09-09
  • Java中6種單例模式寫法代碼實例

    Java中6種單例模式寫法代碼實例

    這篇文章主要介紹了Java中6種單例模式寫法代碼實例,某個類任何情況下只有一個實例,并提供一個全局訪問點來獲取該實例,Java6種單例模式有2種懶漢式,2種餓漢式,靜態(tài)內(nèi)部類 ,枚舉類,需要的朋友可以參考下
    2024-01-01
  • Java 實現(xiàn)倒計時功能(由秒計算天、小時、分鐘、秒)

    Java 實現(xiàn)倒計時功能(由秒計算天、小時、分鐘、秒)

    最近做項目遇到這樣的需求,天、小時、分鐘、秒的數(shù)值都是隔開的,服務(wù)器端只返回一個時間戳長度,怎么實現(xiàn)這樣的功能呢?下面小編給大家?guī)砹薐ava 實現(xiàn)倒計時功能的方案,需要的朋友參考下吧
    2018-01-01
  • Java數(shù)據(jù)類型實現(xiàn)自動與強制轉(zhuǎn)換的示例代碼

    Java數(shù)據(jù)類型實現(xiàn)自動與強制轉(zhuǎn)換的示例代碼

    Java數(shù)據(jù)類型之間的轉(zhuǎn)換有自動轉(zhuǎn)換和強制類型轉(zhuǎn)換,這篇文章主要給大家介紹Java數(shù)據(jù)類型如何實現(xiàn)自動轉(zhuǎn)換與強制轉(zhuǎn)換,需要的朋友可以參考下
    2023-05-05
  • SpringSecurity rememberme功能實現(xiàn)過程解析

    SpringSecurity rememberme功能實現(xiàn)過程解析

    這篇文章主要介紹了SpringSecurity rememberme功能實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-03-03
  • Java編碼算法與哈希算法深入分析使用方法

    Java編碼算法與哈希算法深入分析使用方法

    首先,我們一起來學(xué)習(xí)一下編碼算法,舉例說明,ASCII碼就是我們常見的一種編碼,字母a的編碼是十六進制的0x61,字母b是0x62,以此類推。哈希算法,可被稱為摘要算法。因此,哈希算法的加密是單向的,不可用密文解密得到明文
    2022-11-11
  • SpringBoot接口如何統(tǒng)一異常處理

    SpringBoot接口如何統(tǒng)一異常處理

    這篇文章主要介紹了SpringBoot接口如何統(tǒng)一異常處理,SpringBoot接口如何對異常進行統(tǒng)一封裝,并統(tǒng)一返回呢?以下文的參數(shù)校驗為例,如何優(yōu)雅的將參數(shù)校驗的錯誤信息統(tǒng)一處理并封裝返回呢,感興趣的下下伙伴可以一同參考一下
    2022-07-07
  • mybatisPlus配置邏輯字段不生效問題解決

    mybatisPlus配置邏輯字段不生效問題解決

    本文主要介紹了mybatisPlus配置邏輯字段不生效問題解決,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-05-05

最新評論