精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

SpringBoot+MDC實現全鏈路調用日志跟蹤

jf_ro2CN3Fa ? 來源:稀土掘金技術社區 ? 2023-01-07 13:47 ? 次閱讀

寫在前面

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

基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

MDC介紹

簡介:

MDC(Mapped Diagnostic Context,映射調試上下文)是 log4j 、logback及log4j2 提供的一種方便在多線程條件下記錄日志的功能。MDC 可以看成是一個與當前線程綁定的哈希表 ,可以往其中添加鍵值對。MDC 中包含的內容可以被同一線程中執行的代碼所訪問

當前線程的子線程會繼承其父線程中的 MDC 的內容。當需要記錄日志時,只需要從 MDC 中獲取所需的信息即可。MDC 的內容則由程序在適當的時候保存進去。對于一個 Web 應用來說,通常是在請求被處理的最開始保存這些數據

API說明:

clear() => 移除所有MDC

get (String key) => 獲取當前線程MDC中指定key的值

getContext() => 獲取當前線程MDC的MDC

put(String key, Object o) => 往當前線程的MDC中存入指定的鍵值對

remove(String key) => 刪除當前線程MDC中指定的鍵值對

優點:

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

暫時只能想到這一點

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

MDC使用

添加攔截器

publicclassLogInterceptorimplementsHandlerInterceptor{
@Override
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{
//如果有上層調用就用上層的ID
StringtraceId=request.getHeader(Constants.TRACE_ID);
if(traceId==null){
traceId=TraceIdUtil.getTraceId();
}

MDC.put(Constants.TRACE_ID,traceId);
returntrue;
}

@Override
publicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)
throwsException{
}

@Override
publicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)
throwsException{
//調用結束后刪除
MDC.remove(Constants.TRACE_ID);
}
}

修改日志格式

[TRACEID:%X{traceId}]%d{HHss.SSS}%-5level%class{-1}.%M()/%L-%msg%xEx%n

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

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

MDC 存在的問題

子線程中打印日志丟失traceId

HTTP調用丟失traceId

......丟失traceId的情況,來一個再解決一個,絕不提前優化

解決MDC存在的問題

子線程日志打印丟失traceId

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

線程池封裝類:ThreadPoolExecutorMdcWrapper.java

publicclassThreadPoolExecutorMdcWrapperextendsThreadPoolExecutor{
publicThreadPoolExecutorMdcWrapper(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,
BlockingQueueworkQueue){
super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue);
}

publicThreadPoolExecutorMdcWrapper(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,
BlockingQueueworkQueue,ThreadFactorythreadFactory){
super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory);
}

publicThreadPoolExecutorMdcWrapper(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,
BlockingQueueworkQueue,RejectedExecutionHandlerhandler){
super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,handler);
}

publicThreadPoolExecutorMdcWrapper(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,
BlockingQueueworkQueue,ThreadFactorythreadFactory,
RejectedExecutionHandlerhandler){
super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);
}

@Override
publicvoidexecute(Runnabletask){
super.execute(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));
}

@Override
publicFuturesubmit(Runnabletask,Tresult){
returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()),result);
}

@Override
publicFuturesubmit(Callabletask){
returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));
}

@Override
publicFuturesubmit(Runnabletask){
returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));
}
}

說明:

繼承ThreadPoolExecutor類,重新執行任務的方法

通過ThreadMdcUtil對任務進行一次包裝

線程traceId封裝工具類:ThreadMdcUtil.java

publicclassThreadMdcUtil{
publicstaticvoidsetTraceIdIfAbsent(){
if(MDC.get(Constants.TRACE_ID)==null){
MDC.put(Constants.TRACE_ID,TraceIdUtil.getTraceId());
}
}

publicstaticCallablewrap(finalCallablecallable,finalMapcontext){
return()->{
if(context==null){
MDC.clear();
}else{
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try{
returncallable.call();
}finally{
MDC.clear();
}
};
}

publicstaticRunnablewrap(finalRunnablerunnable,finalMapcontext){
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

執行run方法

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

publicstaticRunnablewrap(finalRunnablerunnable,finalMapcontext){
returnnewRunnable(){
@Override
publicvoidrun(){
if(context==null){
MDC.clear();
}else{
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try{
runnable.run();
}finally{
MDC.clear();
}
}
};
}

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

判斷當前線程對應MDC的Map是否存在,存在則設置

設置MDC中的traceId值,不存在則新生成,針對不是子線程的情況,如果是子線程,MDC中traceId不為null

執行run方法

HTTP調用丟失traceId

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

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

HttpClient:

實現HttpClient攔截器

publicclassHttpClientTraceIdInterceptorimplementsHttpRequestInterceptor{
@Override
publicvoidprocess(HttpRequesthttpRequest,HttpContexthttpContext)throwsHttpException,IOException{
StringtraceId=MDC.get(Constants.TRACE_ID);
//當前線程調用中有traceId,則將該traceId進行透傳
if(traceId!=null){
//添加請求體
httpRequest.addHeader(Constants.TRACE_ID,traceId);
}
}
}

實現HttpRequestInterceptor接口并重寫process方法

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

為HttpClient添加攔截器

privatestaticCloseableHttpClienthttpClient=HttpClientBuilder.create()
.addInterceptorFirst(newHttpClientTraceIdInterceptor())
.build();

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

OKHttp:

實現OKHttp攔截器

publicclassOkHttpTraceIdInterceptorimplementsInterceptor{
@Override
publicResponseintercept(Chainchain)throwsIOException{
StringtraceId=MDC.get(Constants.TRACE_ID);
Requestrequest=null;
if(traceId!=null){
//添加請求體
request=chain.request().newBuilder().addHeader(Constants.TRACE_ID,traceId).build();
}
ResponseoriginResponse=chain.proceed(request);

returnoriginResponse;
}
}

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

為OkHttp添加攔截器

privatestaticOkHttpClientclient=newOkHttpClient.Builder()
.addNetworkInterceptor(newOkHttpTraceIdInterceptor())
.build();

調用addNetworkInterceptor方法添加攔截器

RestTemplate:

實現RestTemplate攔截器

publicclassRestTemplateTraceIdInterceptorimplementsClientHttpRequestInterceptor{
@Override
publicClientHttpResponseintercept(HttpRequesthttpRequest,byte[]bytes,ClientHttpRequestExecutionclientHttpRequestExecution)throwsIOException{
StringtraceId=MDC.get(Constants.TRACE_ID);
if(traceId!=null){
httpRequest.getHeaders().add(Constants.TRACE_ID,traceId);
}

returnclientHttpRequestExecution.execute(httpRequest,bytes);
}
}

實現ClientHttpRequestInterceptor接口,并重寫intercept方法,其余邏輯都是一樣的不重復說明

為RestTemplate添加攔截器

restTemplate.setInterceptors(Arrays.asList(newRestTemplateTraceIdInterceptor()));

調用setInterceptors方法添加攔截器

第三方服務攔截器:

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

publicclassLogInterceptorimplementsHandlerInterceptor{
@Override
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{
//如果有上層調用就用上層的ID
StringtraceId=request.getHeader(Constants.TRACE_ID);
if(traceId==null){
traceId=TraceIdUtils.getTraceId();
}

MDC.put("traceId",traceId);
returntrue;
}

@Override
publicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)
throwsException{
}

@Override
publicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)
throwsException{
MDC.remove(Constants.TRACE_ID);
}
}

說明:

先從request header中獲取traceId

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

將生成的traceId存入MDC中

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

[TRACEID:%X{traceId}]%d{HHss.SSS}%-5level%class{-1}.%M()/%L-%msg%xEx%n

需要添加%X{traceId}

審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • spring
    +關注

    關注

    0

    文章

    338

    瀏覽量

    14312
  • 日志
    +關注

    關注

    0

    文章

    138

    瀏覽量

    10633
  • Boot
    +關注

    關注

    0

    文章

    149

    瀏覽量

    35787
  • 線程
    +關注

    關注

    0

    文章

    504

    瀏覽量

    19653
  • SpringBoot
    +關注

    關注

    0

    文章

    173

    瀏覽量

    169

原文標題:SpringBoot + MDC 實現全鏈路調用日志跟蹤

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    2017雙11技術揭秘—雙十一海量數據下EagleEye的使命和挑戰

    升級提高了系統的穩定性,實現了更好更快的輔助業務方定位及排查問題。圖3 系統架構圖計算能力下沉早期的EagleEye在跟蹤以及數據統計都是基于明細
    發表于 12-29 13:54

    壓測一招搞定,阿里云性能測試鉑金版發布

    ,PTS宣布推出了基于阿里雙11壓測平臺的鉑金版。點此查看原文:http://click.aliyun.com/m/41269/阿里云性能測試(Performance Testing Service
    發表于 01-30 14:13

    API信息掌控,方便你的日志管理——阿里云推出API網關打通日志服務

    ,API網關每月前100萬次免費。相當于用戶每月前100萬次API調用日志存儲均免費,優惠力度極大超越其他云廠商。該功能使用起來也十分簡單,只需四步就可以輕松實現: 阿里云API網關服務自2016年7
    發表于 02-06 15:24

    基于分布式調用監控技術的全息排查功能

    作為鷹眼的商業化產品,用于APM監控的阿里云業務實時監控服務 (ARMS) , 基于鷹眼的全息排查沉淀,近日推出了基于分布式調用監控
    發表于 08-07 17:02

    基于SpringBoot mybatis方式的增刪改查實現

    SpringBoot mybatis方式實現增刪改查
    發表于 06-18 16:56

    監控工具Skywalking使用指南

    國產監控工具Skywalking
    發表于 09-03 14:26

    2021 OPPO開發者大會:運營

    2021 OPPO開發者大會:運營 2021 OPPO開發者大會上介紹了運營,技術能
    的頭像 發表于 10-27 15:07 ?2275次閱讀
    2021 OPPO開發者大會:<b class='flag-5'>全</b><b class='flag-5'>鏈</b><b class='flag-5'>路</b>運營

    MDC300F UART 下發配置 日志調試

    文章目錄型號SOME/IP定義接收參數配置新建UART工程MDC發出的數據發送數據到MDC命令行運行程序調用關系日志調試型號MDC300F,
    發表于 12-14 18:43 ?9次下載
    <b class='flag-5'>MDC</b>300F UART 下發配置 <b class='flag-5'>日志</b>調試

    手動實現SpringBoot日志追蹤

    基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
    的頭像 發表于 12-15 15:04 ?1102次閱讀

    SpringBoot實現多線程

    SpringBoot實現多線程
    的頭像 發表于 01-12 16:59 ?1803次閱讀
    <b class='flag-5'>SpringBoot</b><b class='flag-5'>實現</b>多線程

    微服務循環依賴調用引發的血案

    順著測試匯報的出現問題的場景,跟蹤調用上相關服務的日志,發現出現了微服務之間循依賴調用。大致情況可以抽象如下所示(圖中所有
    的頭像 發表于 01-16 10:28 ?691次閱讀

    追蹤系統SkyWalking的原理

    內部多個模塊,多個中間件,多臺機器的相互調用才能完成。在這一系列的調用中,可能有些是串行的,而有些是并行的。在這種情況下,我們如何才能確定這整個請求調用了哪些應用?哪些模塊?哪些節點?以及它們的先后順序和各部分的性能如何呢? 這
    的頭像 發表于 01-17 11:00 ?4089次閱讀

    SpringBoot+Vue實現網頁版人臉登錄、人臉識別案例解析

    Springboot,Mysql,JWT,VUE 2.X 等等技術實現,主要功能點:人臉列表CRUD,日志列表CRUD,基于自建人臉庫通過base64編碼方式存儲人臉圖片,通過調用騰訊
    發表于 02-23 15:36 ?1048次閱讀

    SpringBoot+Vue實現網頁版人臉登錄、人臉識別

    技術點:Springboot,Mysql,JWT,VUE 2.X 等等技術實現,主要功能點:人臉列表CRUD,日志列表CRUD,基于自建人臉庫通過base64編碼方式存儲人臉圖片,通過調用
    的頭像 發表于 03-07 09:27 ?1032次閱讀

    Spring Boot如何實現日志追蹤

    ,各個接口的日志穿插,確實讓人頭大。 模糊匹配搜索日志能解決嗎? 能解決一點點。 但是不能完全呈現出整個相關的日志。 那要做到方便,很顯
    的頭像 發表于 05-16 11:33 ?2801次閱讀
    Spring Boot如何<b class='flag-5'>實現</b><b class='flag-5'>日志</b><b class='flag-5'>鏈</b><b class='flag-5'>路</b>追蹤