日志之MDC和異步多線程間傳遞線程ID

日志追蹤對于接口故障排查非常重要,可以有效置媳、快捷的定位故障點于樟,但在多線程環(huán)境中,若沒有相關框架的支持拇囊,想要實現(xiàn)日志追蹤迂曲,就需要編碼實現(xiàn)將主線程的日志參數(shù)傳遞給子線程,本文就在線程池場景下借助MDC實現(xiàn)了traceId參數(shù)的透傳

1 MDC

1.1 簡介

MDCMapped Diagnostic Context寥袭,映射調試上下文)是 log4jlogback 提供的一種方便在多線程條件下記錄日志的功能路捧。某些應用程序采用多線程的方式來處理多個用戶的請求。在一個用戶的使用過程中传黄,可能有多個不同的線程來進行處理杰扫。典型的例子是Web 應用服務器。當用戶訪問某個頁面時膘掰,應用服務器可能會創(chuàng)建一個新的線程來處理該請求章姓,也可能從線程池中復用已有的線程。在一個用戶的會話存續(xù)期間识埋,可能有多個線程處理過該用戶的請求凡伊。這使得比較難以區(qū)分不同用戶所對應的日志。當需要追蹤某個用戶在系統(tǒng)中的相關日志記錄時窒舟,就會變得很麻煩系忙。

一種解決的辦法是采用自定義的日志格式,把用戶的信息采用某種方式編碼在日志記錄中惠豺。這種方式的問題在于要求在每個使用日志記錄器的類中银还,都可以訪問到用戶相關的信息风宁。這樣才可能在記錄日志時使用。這樣的條件通常是比較難以滿足的蛹疯。MDC 的作用是解決這個問題杀糯。MDC 可以看成是一個與當前線程綁定的哈希表,可以往其中添加鍵值對苍苞。MDC 中包含的內容可以被同一線程中執(zhí)行的代碼所訪問。當前線程的子線程會繼承其父線程中的 MDC 的內容狼纬。當需要記錄日志時羹呵,只需要從 MDC 中獲取所需的信息即可。MDC 的內容則由程序在適當?shù)臅r候保存進去疗琉。對于一個 Web 應用來說冈欢,通常是在請求被處理的最開始保存這些數(shù)據(jù)

1.2 MDC坐標和使用

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
  </dependency>
  <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.21</version>
  </dependency>

log4j.xml配置樣例,追蹤日志自定義格式主要在name="traceId"layout里面進行設置盈简,我們使用%X{traceId}來定義此處會打印MDC里面keytraceIdvalue凑耻,如果所定義的字段在MDC不存在對應的key,那么將不會打印柠贤,會留一個占位符
點擊了解Loback.xml文件解釋

1.3 主要方法

API 說明:

  • clear():移除所有MDC
  • get (String key):獲取當前線程 MDC 中指定 key 的值
  • getCopyOfContextMap():將MDC從內存獲取出來香浩,再傳給線程
  • put(String key, Object o):往當前線程的 MDC 中存入指定的鍵值對
  • remove(String key):刪除當前線程 MDC 中指定的鍵值對
  • setContextMap():將父線程的MDC內容傳給子線程

MDC異步線程間傳遞:
MDC的put時,子線程在創(chuàng)建的時候會把父線程中的inheritableThreadLocals變量設置到子線程的inheritableThreadLocals中臼勉,而MDC內部是用InheritableThreadLocal實現(xiàn)的邻吭,所以會把父線程中的上下文帶到子線程中
但在線程池中,由于線程會被重用宴霸,但是線程本身只會初始化一次囱晴,所以之后重用線程的時候,就不會進行初始化操作了瓢谢,也就不會有父線程inheritableThreadLocals拷貝到子線程中的過程了畸写,這個時候如果還想傳遞父線程的上下文的話,就要使用getCopyOfContextMap方法

2 多線程間使用

2.1 MDC工具類

定義MDC工具類氓扛,支持RunnableCallable兩種枯芬,目的就是為了把父線程的traceId設置給子線程

import org.slf4j.MDC;
import org.springframework.util.CollectionUtils;

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

/**
 * @Description 封裝MDC用于向線程池傳遞
 */
public class MDCUtil {
    // 設置MDC中的traceId值,不存在則新生成采郎,針對不是子線程的情況破停,
    // 如果是子線程,MDC中traceId不為null
    public static void setTraceIdIfAbsent() {
        if (MDC.get(Constants.TRACE_ID) == null) {
            MDC.put(Constants.TRACE_ID, TraceIdUtil.getTraceId());
        }
    }
    public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
        return () -> {
            if (CollectionUtils.isEmpty(context)) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                return callable.call();
            } finally {//清除子線程的尉剩,避免內存溢出真慢,就和ThreadLocal.remove()一個原因
                MDC.clear();
            }
        };
    }

 public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }

    public static void setMDCContextMap(final Map<String, String> context) {
        if (CollectionUtils.isEmpty(context)) {
            MDC.clear();
        } else {
            MDC.setContextMap(context);
        }
    }

}

2.2 攔截器定義和配置

package demo;

import org.slf4j.MDC;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;

public class RequestInterceptor extends HandlerInterceptorAdapter {

    private static final List<String> paramSet = Arrays.asList("traceId");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        this.setParam(request);
        return super.preHandle(request, response, handler);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        MDC.clear();
    }

    private void setParam(HttpServletRequest request) {
        // 設置要放到MDC中的參數(shù)
        for (String key : paramSet) {
            String val = request.getHeader(key);
            if (!StringUtils.isEmpty(val)) {
                MDC.put(key, val);
            }
        }
    }

}

攔截器配置

import demo.RequestInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 攔截WEB請求
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RequestInterceptor());
    }

}

2.3 Java線程池中使用

2.3.1 配置線程池

@Configuration
public class ThreadPoolService {
    @Bean
    public ThreadPoolExecutor threadPoolExecutor() {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
                (4, 8, 10,
                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(5536),
                new ScheduledThreadFactory("demo-"), new ThreadPoolExecutor.CallerRunsPolicy());
        return threadPoolExecutor;
    }   
}

點擊了解線程池相關信息

2.3.2 使用ExecutorCompletionService方式

使用ExecutorCompletionService實現(xiàn)多線程調用
點擊了解更多關于ExecutorCompletionService信息

/**
 * 使用MDC傳遞traceId
 */
public class Demo {

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    public void demo() {
        ExecutorCompletionService ecs = new ExecutorCompletionService(threadPoolExecutor);
        ecs.submit(MDCUtil.wrap(new TestMDC(), MDC.getCopyOfContextMap()));
    }
    
    class TestMDC implements Callable {
        @Override
        public Object call() throws Exception {
            // TODO 代碼邏輯
            return null;
        }
    }
}

2.3.3 使用CompletableFuture方式

使用CompletableFuture實現(xiàn)多線程調用,其中收集CompletableFuture運行結果理茎,
點擊了解更多關于CompletableFuture信息

public class Result {}
/**
 * 使用MDC傳遞traceId
 */
public class Demo {

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    private CompletableFuture<Result> test() {
    
        Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
        
        return CompletableFuture.supplyAsync(() -> {
        
            // 必須在打印日志前設置
            MDCUtil.setMDCContextMap(copyOfContextMap);
            //MDC.put("subTraceId",''); //如果需要對子線程進行加線程跟蹤號黑界,可在此處設定
            // TODO 業(yè)務邏輯
            return new Result();
            
        }, threadPoolExecutor).exceptionally(new Function<Throwable, Result>() {
            /**捕捉異常,不會導致整個流程中斷**/
            @Override
            public Result apply(Throwable throwable) {
                log.error("線程[{}]發(fā)生了異常[{}], 繼續(xù)執(zhí)行其他線程", Thread.currentThread().getName(), throwable.getMessage());
                return null;
            }
        });
    }
}

2.4 Spring線程池中使用

2.4.1 繼承ThreadPoolTaskExecutor

public class ThreadPoolMdcWrapper extends ThreadPoolTaskExecutor {

    public ThreadPoolMdcWrapper() {

    }

    @Override
    public void execute(Runnable task) {
        super.execute(MDCUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public void execute(Runnable task, long startTimeout) {
        super.execute(MDCUtil.wrap(task, MDC.getCopyOfContextMap()), startTimeout);
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(MDCUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(MDCUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        return super.submitListenable(MDCUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        return super.submitListenable(MDCUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
}

2.4.2 配置線程池

繼承ThreadPoolTaskExecutor 管嬉,重寫線程執(zhí)行的方法。到這我們就做完了大部分的準備工作朗鸠,還剩下最關鍵的就是讓程序用到我們封裝后的線程池蚯撩。我們可以在聲明線程池的時候,直接使用我們封裝好的線程池(因為繼承了ThreadPoolTaskExecutor)
點擊了解Spring線程池配置

@Bean
public ThreadPoolTaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolMdcWrapper();
    //核心線程數(shù)烛占,默認為1
    taskExecutor.setCorePoolSize(1);
    //最大線程數(shù)胎挎,默認為Integer.MAX_VALUE
    taskExecutor.setMaxPoolSize(200);
    //隊列最大長度,一般需要設置值>=notifyScheduledMainExecutor.maxNum忆家;默認為Integer.MAX_VALUE
    taskExecutor.setQueueCapacity(2000);
    //線程池維護線程所允許的空閑時間犹菇,默認為60s
    taskExecutor.setKeepAliveSeconds(60);
    //線程池對拒絕任務(無線程可用)的處理策略
    taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    // 初始化線程池
    taskExecutor.initialize();
    return  taskExecutor;
}

到這我們所做的準備工作,改造工作也就結束了芽卿,剩下的就是使用了揭芍。只要在程序異步調用時,利用聲明好的taskExecutor線程池進行調用卸例,就可以在線程上下文正確傳遞Traceid了

2.5 異步線程 AsyncConfigurer

繼承了SpringAsyncConfigurer称杨,并重寫了getAsyncExecutor方法,這樣在Spring中使用@Async注解開啟異步線程,會自動傳遞MDC信息給子線程,

另外關于異步線程的異常捕獲,先列舉一下一般開啟異步的方式:

  • 使用Spring@Async注解開啟異步
  • 通過executor.execute開啟異步
  • 通過executor.submit開啟異步
  • 通過CompletableFuture開啟異步

下面針對異步子線程的異常捕獲提供幾種解決方案:

  • 重寫AsyncConfigurer的getAsyncUncaughtExceptionHandler方法,這種方式只能捕獲方式A開啟的異步
  • 使用Future.get(),可以捕獲方式C開啟的異步
  • 使用Completable.join()或者Completable.get(),可以捕獲方式D開啟的異步
  • 重寫getAsyncExecutor方法時,在runnable.run()代碼塊上使用try/catch,可以捕獲方式A,B,C開啟的異步
  • 使用try/catch包裹整個runnable函數(shù)式接口,這樣可以捕獲A,B,C,D開啟的異步
    executor.execute(() -> {
            try {
                //需要開啟異步的業(yè)務邏輯方法或者代碼塊
                xxx();
            } catch (Throwable e) {
                log.error("異常", e);
            }
        });

下面給出完整的代碼

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
 
@Slf4j
@EnableAsync
@Configuration
@RequiredArgsConstructor
public class ThreadPoolTaskConfig implements AsyncConfigurer {
 
    @Bean("AsyncExecutor")
    @Override
    public ThreadPoolTaskExecutor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor() {
            /**
             * 所有線程都會委托給這個execute方法,在這個方法中我們把父線程的MDC內容賦值給子線程
             * https://logback.qos.ch/manual/mdc.html#managedThreads
             *
             * @param runnable runnable
             */
            @Override
            public void execute(Runnable runnable) {
                // 獲取父線程MDC中的內容筷转,必須在run方法之前姑原,否則等異步線程執(zhí)行的時候有可能MDC里面的值已經(jīng)被清空了,這個時候就會返回null
                Map<String, String> context = MDC.getCopyOfContextMap();
                super.execute(() -> {
                    // 將父線程的MDC內容傳給子線程
                    if (context != null) {
                        MDC.setContextMap(context);
                    }
                    try {
                        // 執(zhí)行異步操作
                        runnable.run();
                    } catch (Throwable e) {
                        log.info("異步線程執(zhí)行異常:{}", e.getMessage(), e);
                        //替換成業(yè)務異常
                        throw new RuntimeException("異步線程執(zhí)行異常");
                    } finally {
                        // 清空MDC內容
                        MDC.clear();
                    }
                });
            }
 
            @Override
            public <T> Future<T> submit(Callable<T> task) {
                // 獲取父線程MDC中的內容呜舒,必須在run方法之前页衙,否則等異步線程執(zhí)行的時候有可能MDC里面的值已經(jīng)被清空了,這個時候就會返回null
                Map<String, String> context = MDC.getCopyOfContextMap();
                return super.submit(() -> {
                    // 將父線程的MDC內容傳給子線程
                    if (context != null) {
                        MDC.setContextMap(context);
                    }
                    try {
                        // 執(zhí)行異步操作
                        return task.call();
                    } catch (Throwable e) {
                        log.info("異步線程執(zhí)行異常:{}", e.getMessage(), e);
                        //替換成業(yè)務異常
                        throw new RuntimeException("異步線程執(zhí)行異常");
                    } finally {
                        // 清空MDC內容
                        MDC.clear();
                    }
                });
            }
        };
        ;
        // 設置核心線程數(shù)
        threadPoolTaskExecutor.setCorePoolSize(30);
        // 設置最大線程數(shù)
        threadPoolTaskExecutor.setMaxPoolSize(50);
        // 設置隊列容量
        threadPoolTaskExecutor.setQueueCapacity(1000);
        // 設置線程活躍時間(秒)
        threadPoolTaskExecutor.setKeepAliveSeconds(60);
        // 設置拒絕策略
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 設置線程池終止等待時間
        threadPoolTaskExecutor.setAwaitTerminationSeconds(10);
 
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
 
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (Throwable throwable, Method method, Object... objects) -> {
            log.error("AsyncUncaughtExceptionHandler: ", throwable);
            log.info("method: {}", method.getName());
            log.info("objects: {}", objects);
        };
    }
}

調用測試

@Slf4j
@RestController
public class UserController {
    @Autowired
    @Qualifier("asyncExe")
    private Executor executor;
    @Autowired
    private AsyncServiceImpl ayncService;
    @GetMapping("/t1")
    public void test1(){
        log.info("開始....");
        CompletableFuture.runAsync(() ->{
            log.info("異步中....");
        });
        executor.execute(() ->{
            log.info("線程池中....");
        });
        ayncService.test();
        log.info("結束....");
    }
}   

@Slf4j
@Service
public class AsyncServiceImpl {
    @Async("asyncExe")
    public void test(){
        //...具體業(yè)務邏輯
        log.info("異步async中....");
    }
}

2.6 多線程間傳遞 TransmittableThreadLocal

2.6.1 引言

假如使用logback/log4j官網(wǎng)推薦的方案阴绢,顯示調用 MDC.getCopyOfContextMap()MDC.setContextMap() 店乐,在向線程池提交任務的時候需要顯示的去調用。這種方式很繁瑣呻袭,而且侵入性很高眨八,可維護性也很低。

如果使用阿里的TransmittableThreadLocal方案左电,是使用TransmittableThreadLocal的實現(xiàn)去增強ThreadPoolExecutor廉侧,不需要在任務提交運行的時候去顯示的調用MDC,但是TransmittableThreadLocal的官網(wǎng)上沒有明確的結合MDC的教程篓足。
主要有2種段誊,一種是自己實現(xiàn)一個MDCAdapter替換logback/log4j的MDCAdapter,內部將其ThreadLocal替換為TransmittableThreadLocal的實現(xiàn)栈拖,在通過其他方式注入到日志框架中连舍。
另外一種方式是使用 logback-mdc-ttl 來更換項目中的logback框架,內部的思路和上面類似涩哟,也是替換了MDCAdapter的實現(xiàn)索赏。
但是這2種方式都有很大的問題盼玄,第一種需要修改日志框架的注入實現(xiàn),在后續(xù)升級日志框架有很大的風險潜腻。第二種方式是引入了一個三方的日志框架埃儿,不可維護。

2.6.2 解決方案

總結來看上述幾種解決方案都不太理解融涣,第二種方式雖然使用了TransmittableThreadLocal解決了包裝類的問題童番,但是沒有很好的適配MDC,修改了大量的實現(xiàn)代碼威鹿,而且不利于后續(xù)的升級維護剃斧。
在搜索的相關的資料、源碼以及TransmittableThreadLocal的issue里专普,發(fā)現(xiàn)了一種比較簡潔的實現(xiàn)方式。

添加 HandlerInterceptor 攔截器弹沽,核心的實現(xiàn)思路是實現(xiàn) TransmittableThreadLocalinitialValue,beforeExecute,afterExecute接口檀夹,在多線程數(shù)據(jù)傳遞的時候,將數(shù)據(jù)復制一份給MDC策橘。

@Component
public class TraceIdInterceptor implements HandlerInterceptor {

    /**
     * 實現(xiàn) TransmittableThreadLocal 的 initialValue,beforeExecute,afterExecute接口
     */
    static TransmittableThreadLocal<Map<String, String>> ttlMDC = new TransmittableThreadLocal<>() {
        /**
         * 在多線程數(shù)據(jù)傳遞的時候炸渡,將數(shù)據(jù)復制一份給MDC
         */
        @Override
        protected void beforeExecute() {
            final Map<String, String> mdc = get();
            mdc.forEach(MDC::put);
        }

        @Override
        protected void afterExecute() {
            MDC.clear();
        }

        @Override
        protected Map<String, String> initialValue() {
            return Maps.newHashMap();
        }
    };


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        //MDC記錄traceId
        String traceId = IdUtil.fastUUID();
        MDC.put("traceId", traceId);

        //同時給TransmittableThreadLocal記錄traceId
        ttlMDC.get().put("traceId", traceId);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
        @Nullable Exception ex) {

        //清除數(shù)據(jù)
        MDC.clear();
        ttlMDC.get().clear();
        ttlMDC.remove();
    }
}

使用 TransmittableThreadLocal 提供的包裝池,

@Bean
public Executor asyncExecutor() {
    log.info("start asyncExecutor");
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    //配置核心線程數(shù)
    executor.setCorePoolSize(10);
    //配置最大線程數(shù)
    executor.setMaxPoolSize(50);
    //配置隊列大小
    executor.setQueueCapacity(0);
    //配置線程池中的線程的名稱前綴
    executor.setThreadNamePrefix("async-service-");

    // rejection-policy:當pool已經(jīng)達到max size的時候丽已,如何處理新任務
    // CALLER_RUNS:不在新線程中執(zhí)行任務蚌堵,而是有調用者所在的線程來執(zhí)行
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    //執(zhí)行初始化
    executor.initialize();
    //使用TransmittableThreadLocal提供的包裝池
    return TtlExecutors.getTtlExecutor(executor);
}

2.7 HTTP調用丟失traceId

在使用 HTTP 調用第三方服務接口時traceId將丟失,需要對HTTP調用工具進行改造沛婴,在發(fā)送時在request header中添加traceId吼畏,在下層被調用方添加攔截器獲取header中的traceId添加到MDC中

HTTP調用有多種方式,比較常見的有HttpClient嘁灯、OKHttp泻蚊、RestTemplate,所以只給出這幾種HTTP調用的解決方式

2.7.1 HttpClient

實現(xiàn)HttpClient攔截器

public class HttpClientTraceIdInterceptor implements HttpRequestInterceptor {
    @Override
    public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
        String traceId = MDC.get(Constants.TRACE_ID);
        //當前線程調用中有traceId丑婿,則將該traceId進行透傳
        if (traceId != null) {
            //添加請求體
            httpRequest.addHeader(Constants.TRACE_ID, traceId);
        }
    }
}

實現(xiàn) HttpRequestInterceptor接口并重寫process方法
如果調用線程中含有traceId性雄,則需要將獲取到的traceId通過request中的header向下透傳下去

為HttpClient添加攔截器

private static CloseableHttpClient httpClient = HttpClientBuilder.create()
            .addInterceptorFirst(new HttpClientTraceIdInterceptor())
            .build();

通過addInterceptorFirst方法為HttpClient添加攔截器

2.7.2 OKHttp

實現(xiàn)OKHttp攔截器

public class OkHttpTraceIdInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        String traceId = MDC.get(Constants.TRACE_ID);
        Request request = null;
        if (traceId != null) {
            //添加請求體
            request = chain.request().newBuilder().addHeader(Constants.TRACE_ID, traceId).build();
        }
        Response originResponse = chain.proceed(request);

        return originResponse;
    }
}

實現(xiàn)Interceptor攔截器,重寫interceptor方法羹奉,實現(xiàn)邏輯和HttpClient差不多秒旋,如果能夠獲取到當前線程的traceId則向下透傳

為OkHttp添加攔截器,調用addNetworkInterceptor方法添加攔截器

private static OkHttpClient client = new OkHttpClient.Builder()
            .addNetworkInterceptor(new OkHttpTraceIdInterceptor())
            .build();

2.7.3 RestTemplate

實現(xiàn)RestTemplate攔截器

public class RestTemplateTraceIdInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
        String traceId = MDC.get(Constants.TRACE_ID);
        if (traceId != null) {
            httpRequest.getHeaders().add(Constants.TRACE_ID, traceId);
        }

        return clientHttpRequestExecution.execute(httpRequest, bytes);
    }
}

實現(xiàn)ClientHttpRequestInterceptor接口诀拭,并重寫intercept方法迁筛,其余邏輯都是一樣的不重復說明

為RestTemplate添加攔截器,調用setInterceptors方法添加攔截器

restTemplate.setInterceptors(Arrays.asList(new RestTemplateTraceIdInterceptor()));
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末耕挨,一起剝皮案震驚了整個濱河市瑰煎,隨后出現(xiàn)的幾起案子铺然,更是在濱河造成了極大的恐慌,老刑警劉巖酒甸,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件魄健,死亡現(xiàn)場離奇詭異,居然都是意外死亡插勤,警方通過查閱死者的電腦和手機沽瘦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來农尖,“玉大人析恋,你說我怎么就攤上這事∈⒖ǎ” “怎么了助隧?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長滑沧。 經(jīng)常有香客問我并村,道長,這世上最難降的妖魔是什么滓技? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任哩牍,我火速辦了婚禮,結果婚禮上令漂,老公的妹妹穿的比我還像新娘膝昆。我一直安慰自己,他們只是感情好叠必,可當我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布荚孵。 她就那樣靜靜地躺著,像睡著了一般纬朝。 火紅的嫁衣襯著肌膚如雪处窥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天玄组,我揣著相機與錄音滔驾,去河邊找鬼。 笑死俄讹,一個胖子當著我的面吹牛哆致,可吹牛的內容都是我干的。 我是一名探鬼主播患膛,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼摊阀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起胞此,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤臣咖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后漱牵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體夺蛇,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年酣胀,在試婚紗的時候發(fā)現(xiàn)自己被綠了刁赦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡闻镶,死狀恐怖甚脉,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情铆农,我是刑警寧澤牺氨,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站墩剖,受9級特大地震影響猴凹,放射性物質發(fā)生泄漏。R本人自食惡果不足惜涛碑,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一精堕、第九天 我趴在偏房一處隱蔽的房頂上張望孵淘。 院中可真熱鬧蒲障,春花似錦、人聲如沸瘫证。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽背捌。三九已至毙籽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間毡庆,已是汗流浹背坑赡。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留么抗,地道東北人毅否。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像蝇刀,于是被迫代替她去往敵國和親螟加。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內容