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

          SpringBoot中如何實現(xiàn)全鏈路調(diào)用日志跟蹤?這方法才優(yōu)雅!

          共 9835字,需瀏覽 20分鐘

           ·

          2022-02-11 10:34

          作者:何甜甜在嗎
          來源:juejin.cn/post/6844904101483020295

          寫在前面

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

          MDC介紹

          簡介

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

          API說明

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

          優(yōu)點:

          • 代碼簡潔,日志風(fēng)格統(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?{
          ??????????//如果有上層調(diào)用就用上層的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?
          {
          ??????????//調(diào)用結(jié)束后刪除
          ??????????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將獲取不到

          MDC 存在的問題

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

          • HTTP調(diào)用丟失traceId

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

          解決MDC存在的問題

          子線程日志打印丟失traceId

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

          • 線程池封裝類: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í)行任務(wù)的方法

          • 通過ThreadMdcUtil對任務(wù)進行一次包裝

          • 線程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為例】:

          • 判斷當(dāng)前線程對應(yīng)MDC的Map是否存在,存在則設(shè)置
          • 設(shè)置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,在該任務(wù)執(zhí)行之前【runnable.run()】先將主線程的Map設(shè)置到當(dāng)前線程中【 即MDC.setContextMap(context)】,這樣子線程和主線程MDC對應(yīng)的Map就是一樣的了

          HTTP調(diào)用丟失traceId

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

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

          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);
          ????????//當(dāng)前線程調(diào)用中有traceId,則將該traceId進行透傳
          ????????if?(traceId?!=?null)?{
          ????????????//添加請求體
          ????????????httpRequest.addHeader(Constants.TRACE_ID,?traceId);
          ????????}
          ????}
          }

          實現(xiàn)HttpRequestInterceptor接口并重寫process方法

          如果調(diào)用線程中含有traceId,則需要將獲取到的traceId通過request中的header向下透傳下去

          • 為HttpClient添加攔截器
          ??private?static?CloseableHttpClient?httpClient?=?HttpClientBuilder.create()
          ??????????????.addInterceptorFirst(new?HttpClientTraceIdInterceptor())
          ??????????????.build();

          通過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差不多,如果能夠獲取到當(dāng)前線程的traceId則向下透傳

          • 為OkHttp添加攔截器
          ????private?static?OkHttpClient?client?=?new?OkHttpClient.Builder()
          ??????????????.addNetworkInterceptor(new?OkHttpTraceIdInterceptor())
          ??????????????.build();

          調(diào)用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方法,其余邏輯都是一樣的不重復(fù)說明

          • 為RestTemplate添加攔截器
          ??restTemplate.setInterceptors(Arrays.asList(new?RestTemplateTraceIdInterceptor()));

          調(diào)用setInterceptors方法添加攔截器

          第三方服務(wù)攔截器:

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

          public?class?LogInterceptor?implements?HandlerInterceptor?{
          ????@Override
          ????public?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler)?throws?Exception?{
          ????????//如果有上層調(diào)用就用上層的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則說明不是第三方調(diào)用,直接生成一個新的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}

          項目源碼地址

          https://github.com/TiantianUpup/springboot-log/tree/master/springboot-trace

          程序汪資料鏈接

          程序汪接的7個私活都在這里,經(jīng)驗整理

          Java項目分享 最新整理全集,找項目不累啦 06版

          堪稱神級的Spring Boot手冊,從基礎(chǔ)入門到實戰(zhàn)進階

          臥槽!字節(jié)跳動《算法中文手冊》火了,完整版 PDF 開放下載!

          臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

          字節(jié)跳動總結(jié)的設(shè)計模式 PDF 火了,完整版開放下載!

          歡迎添加程序汪個人微信 itwang008? 進粉絲群或圍觀朋友圈

          瀏覽 51
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  欧美一区二区三区四区视频 | 国内毛片毛片毛片 | 云盘流出真实操逼免费视频国产 | 在线看片肏| 韩国的毛片|