<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Boot + MDC 實現(xiàn)全鏈路調用日志跟蹤

          共 10003字,需瀏覽 21分鐘

           ·

          2022-03-02 22:38

          寫在前面

          通過本文將了解到什么是MDC、MDC應用中存在的問題、如何解決存在的問題

          MDC介紹

          簡介:

          MDC(Mapped Diagnostic Context,映射調試上下文)是 log4j 、logback及l(fā)og4j2 提供的一種方便在多線程條件下記錄日志的功能。MDC 可以看成是一個與當前線程綁定的哈希表,可以往其中添加鍵值對。MDC 中包含的內容可以被同一線程中執(zhí)行的代碼所訪問。當前線程的子線程會繼承其父線程中的 MDC 的內容。當需要記錄日志時,只需要從 MDC 中獲取所需的信息即可。MDC 的內容則由程序在適當?shù)臅r候保存進去。對于一個 Web 應用來說,通常是在請求被處理的最開始保存這些數(shù)據(jù)

          API說明:

          • clear() => 移除所有MDC
          • get (String key) => 獲取當前線程MDC中指定key的值
          • getContext() => 獲取當前線程MDC的MDC
          • put(String key, Object o) => 往當前線程的MDC中存入指定的鍵值對
          • remove(String key) => 刪除當前線程MDC中指定的鍵值對

          優(yōu)點:

          • 代碼簡潔,日志風格統(tǒng)一,不需要在log打印中手動拼寫traceId,即LOGGER.info("traceId:{} ", traceId)

          暫時只能想到這一點

          MDC使用

          • 添加攔截器
          ??public?class?LogInterceptor?implements?HandlerInterceptor?{
          ??????@Override
          ??????public?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler)?throws?Exception?{
          ??????????//如果有上層調用就用上層的ID
          ??????????String?traceId?=?request.getHeader(Constants.TRACE_ID);
          ??????????if?(traceId?==?null)?{
          ??????????????traceId?=?TraceIdUtil.getTraceId();
          ??????????}
          ??
          ??????????MDC.put(Constants.TRACE_ID,?traceId);
          ??????????return?true;
          ??????}
          ??
          ??????@Override
          ??????public?void?postHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,?ModelAndView?modelAndView)
          ??????????????throws?Exception?
          {
          ??????}
          ??
          ??????@Override
          ??????public?void?afterCompletion(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,?Exception?ex)
          ??????????????throws?Exception?
          {
          ??????????//調用結束后刪除
          ??????????MDC.remove(Constants.TRACE_ID);
          ??????}
          }
          • 修改日志格式
          <property?name="pattern">[TRACEID:%X{traceId}]?%d{HH:mm:ss.SSS}?%-5level?%class{-1}.%M()/%L?-?%msg%xEx%nproperty>

          重點是%X{traceId},traceId和MDC中的鍵名稱一致

          簡單使用就這么容易,但是在有些情況下traceId將獲取不到

          img

          MDC 存在的問題

          • 子線程中打印日志丟失traceId
          • HTTP調用丟失traceId

          丟失traceId的情況,來一個再解決一個,絕不提前優(yōu)化

          解決MDC存在的問題

          子線程日志打印丟失traceId

          子線程在打印日志的過程中traceId將丟失,解決方式為重寫線程池,對于直接new創(chuàng)建線程的情況不考略【實際應用中應該避免這種用法】,重寫線程池無非是對任務進行一次封裝

          • 線程池封裝類:ThreadPoolExecutorMdcWrapper.java
          public?class?ThreadPoolExecutorMdcWrapper?extends?ThreadPoolExecutor?{
          ??????public?ThreadPoolExecutorMdcWrapper(int?corePoolSize,?int?maximumPoolSize,?long?keepAliveTime,?TimeUnit?unit,
          ??????????????????????????????????????????BlockingQueue?workQueue)
          ?
          {
          ??????????super(corePoolSize,?maximumPoolSize,?keepAliveTime,?unit,?workQueue);
          ??????}
          ??
          ??????public?ThreadPoolExecutorMdcWrapper(int?corePoolSize,?int?maximumPoolSize,?long?keepAliveTime,?TimeUnit?unit,
          ??????????????????????????????????????????BlockingQueue?workQueue,?ThreadFactory?threadFactory)
          ?
          {
          ??????????super(corePoolSize,?maximumPoolSize,?keepAliveTime,?unit,?workQueue,?threadFactory);
          ??????}
          ??
          ??????public?ThreadPoolExecutorMdcWrapper(int?corePoolSize,?int?maximumPoolSize,?long?keepAliveTime,?TimeUnit?unit,
          ??????????????????????????????????????????BlockingQueue?workQueue,?RejectedExecutionHandler?handler)
          ?
          {
          ??????????super(corePoolSize,?maximumPoolSize,?keepAliveTime,?unit,?workQueue,?handler);
          ??????}
          ??
          ??????public?ThreadPoolExecutorMdcWrapper(int?corePoolSize,?int?maximumPoolSize,?long?keepAliveTime,?TimeUnit?unit,
          ??????????????????????????????????????????BlockingQueue?workQueue,?ThreadFactory?threadFactory,
          ??????????????????????????????????????????RejectedExecutionHandler?handler)
          ?
          {
          ??????????super(corePoolSize,?maximumPoolSize,?keepAliveTime,?unit,?workQueue,?threadFactory,?handler);
          ??????}
          ??
          ??????@Override
          ??????public?void?execute(Runnable?task)?{
          ??????????super.execute(ThreadMdcUtil.wrap(task,?MDC.getCopyOfContextMap()));
          ??????}
          ??
          ??????@Override
          ??????public??Future?submit(Runnable?task,?T?result)?{
          ??????????return?super.submit(ThreadMdcUtil.wrap(task,?MDC.getCopyOfContextMap()),?result);
          ??????}
          ??
          ??????@Override
          ??????public??Future?submit(Callable?task)?{
          ??????????return?super.submit(ThreadMdcUtil.wrap(task,?MDC.getCopyOfContextMap()));
          ??????}
          ??
          ??????@Override
          ??????public?Future?submit(Runnable?task)?{
          ??????????return?super.submit(ThreadMdcUtil.wrap(task,?MDC.getCopyOfContextMap()));
          ??????}
          }

          說明:

          • 繼承ThreadPoolExecutor類,重新執(zhí)行任務的方法
          • 通過ThreadMdcUtil對任務進行一次包裝
          • 線程traceId封裝工具類:ThreadMdcUtil.java
          public?class?ThreadMdcUtil?{
          ??????public?static?void?setTraceIdIfAbsent()?{
          ??????????if?(MDC.get(Constants.TRACE_ID)?==?null)?{
          ??????????????MDC.put(Constants.TRACE_ID,?TraceIdUtil.getTraceId());
          ??????????}
          ??????}
          ??
          ??????public?static??Callable?wrap(final?Callable?callable,?final?Map?context)?{
          ??????????return?()?->?{
          ??????????????if?(context?==?null)?{
          ??????????????????MDC.clear();
          ??????????????}?else?{
          ??????????????????MDC.setContextMap(context);
          ??????????????}
          ??????????????setTraceIdIfAbsent();
          ??????????????try?{
          ??????????????????return?callable.call();
          ??????????????}?finally?{
          ??????????????????MDC.clear();
          ??????????????}
          ??????????};
          ??????}
          ??
          ??????public?static?Runnable?wrap(final?Runnable?runnable,?final?Map?context)?{
          ??????????return?()?->?{
          ??????????????if?(context?==?null)?{
          ??????????????????MDC.clear();
          ??????????????}?else?{
          ??????????????????MDC.setContextMap(context);
          ??????????????}
          ??????????????setTraceIdIfAbsent();
          ??????????????try?{
          ??????????????????runnable.run();
          ??????????????}?finally?{
          ??????????????????MDC.clear();
          ??????????????}
          ??????????};
          ??????}
          }

          說明【以封裝Runnable為例】:

          • 判斷當前線程對應MDC的Map是否存在,存在則設置
          • 設置MDC中的traceId值,不存在則新生成,針對不是子線程的情況,如果是子線程,MDC中traceId不為null
          • 執(zhí)行run方法

          代碼等同于以下寫法,會更直觀

          public?static?Runnable?wrap(final?Runnable?runnable,?final?Map?context)?{
          ??????????return?new?Runnable()?{
          ??????????????@Override
          ??????????????public?void?run()?{
          ??????????????????if?(context?==?null)?{
          ??????????????????????MDC.clear();
          ??????????????????}?else?{
          ??????????????????????MDC.setContextMap(context);
          ??????????????????}
          ??????????????????setTraceIdIfAbsent();
          ??????????????????try?{
          ??????????????????????runnable.run();
          ??????????????????}?finally?{
          ??????????????????????MDC.clear();
          ??????????????????}
          ??????????????}
          ??????????};
          ??????}

          重新返回的是包裝后的Runnable,在該任務執(zhí)行之前【runnable.run()】先將主線程的Map設置到當前線程中【 即MDC.setContextMap(context)】,這樣子線程和主線程MDC對應的Map就是一樣的了

          HTTP調用丟失traceId

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

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

          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())

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

          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添加攔截器
          ????private?static?OkHttpClient?client?=?new?OkHttpClient.Builder()
          ??????????????.addNetworkInterceptor(new?OkHttpTraceIdInterceptor())
          ??????????????.build();

          調用addNetworkInterceptor方法添加攔截器

          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添加攔截器
          ??restTemplate.setInterceptors(Arrays.asList(new?RestTemplateTraceIdInterceptor()));

          調用setInterceptors方法添加攔截器

          第三方服務攔截器:

          HTTP調用第三方服務接口全流程traceId需要第三方服務配合,第三方服務需要添加攔截器拿到request header中的traceId并添加到MDC中

          public?class?LogInterceptor?implements?HandlerInterceptor?{
          ????@Override
          ????public?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler)?throws?Exception?{
          ????????//如果有上層調用就用上層的ID
          ????????String?traceId?=?request.getHeader(Constants.TRACE_ID);
          ????????if?(traceId?==?null)?{
          ????????????traceId?=?TraceIdUtils.getTraceId();
          ????????}
          ????????
          ????????MDC.put("traceId",?traceId);
          ????????return?true;
          ????}

          ????@Override
          ????public?void?postHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,?ModelAndView?modelAndView)
          ????????????throws?Exception?
          {
          ????}

          ????@Override
          ????public?void?afterCompletion(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,?Exception?ex)
          ????????????throws?Exception?
          {
          ????????MDC.remove(Constants.TRACE_ID);
          ????}
          }

          說明:

          • 先從request header中獲取traceId
          • 從request header中獲取不到traceId則說明不是第三方調用,直接生成一個新的traceId
          • 將生成的traceId存入MDC中

          除了需要添加攔截器之外,還需要在日志格式中添加traceId的打印,如下:

          <property?name="pattern">[TRACEID:%X{traceId}]?%d{HH:mm:ss.SSS}?%-5level?%class{-1}.%M()/%L?-?%msg%xEx%nproperty>
          復制代碼

          需要添加%X{traceId}


          我們創(chuàng)建了一個高質量的技術交流群,與優(yōu)秀的人在一起,自己也會優(yōu)秀起來,趕緊點擊加群,享受一起成長的快樂。另外,如果你最近想跳槽的話,年前我花了2周時間收集了一波大廠面經,節(jié)后準備跳槽的可以點擊這里領取!

          推薦閱讀

          ··································

          你好,我是程序猿DD,10年開發(fā)老司機、阿里云MVP、騰訊云TVP、出過書、創(chuàng)過業(yè)、國企4年互聯(lián)網(wǎng)6年10年前畢業(yè)加入宇宙行,工資不高、也不算太忙,業(yè)余堅持研究技術和做自己想做的東西。4年后離開國企,加入永輝互聯(lián)網(wǎng)板塊的創(chuàng)業(yè)團隊,從開發(fā)、到架構、到合伙人。一路過來,給我最深的感受就是一定要不斷學習并關注前沿。只要你能堅持下來,多思考、少抱怨、勤動手,就很容易實現(xiàn)彎道超車!所以,不要問我現(xiàn)在干什么是否來得及。如果你看好一個事情,一定是堅持了才能看到希望,而不是看到希望才去堅持。相信我,只要堅持下來,你一定比現(xiàn)在更好!如果你還沒什么方向,可以先關注我,這里會經常分享一些前沿資訊,幫你積累彎道超車的資本。

          點擊閱讀原文,送你免費Spring Boot教程
          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  爽 好紧 别夹 喷水欧美 | 国产美女自拍视频 | 午夜精品一区二区三区在线播放 | 婷婷噜噜在线 | A片黄色视频免费 |