最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot項(xiàng)目實(shí)現(xiàn)分布式日志鏈路追蹤

 更新時(shí)間:2023年07月04日 10:39:25   作者:shepherd111  
這篇文章主要給大家介紹了Spring Boot項(xiàng)目如何實(shí)現(xiàn)分布式日志鏈路追蹤,文中通過代碼示例給大家介紹的非常詳細(xì),需要的朋友可以參考下

1.概述

作為一名后端開發(fā)工程師,排查系統(tǒng)問題用得最多的手段之一就是查看系統(tǒng)日志,在當(dāng)下主要的分布式集群環(huán)境中一般使用ELK(Elasticsearch , Logstash, Kibana)來統(tǒng)一收集日志,以便后續(xù)查看日志定位追蹤相關(guān)問題。但是在并發(fā)情況下,大量的系統(tǒng)用戶即多線程并發(fā)訪問后端服務(wù)導(dǎo)致同一個(gè)請(qǐng)求的日志記錄不再是連續(xù)相鄰的,此時(shí)多個(gè)請(qǐng)求的日志是一起串行輸出到文件中,所以我們篩選出指定請(qǐng)求的全部相關(guān)日志還是比較麻煩的,同時(shí)當(dāng)后端異步處理功能邏輯以及微服務(wù)的下游服務(wù)調(diào)用日志追蹤也有著相同的問題。

為了快速排查、定位、解決日常反饋的系統(tǒng)問題,我們就必須解決上面所說的查看請(qǐng)求日志的痛點(diǎn)。解決方案就是:每個(gè)請(qǐng)求都使用一個(gè)唯一標(biāo)識(shí)traceId來追蹤全部的鏈路顯示在日志中,并且不修改原有的打印方式(代碼無入侵),然后使用使用Logback的MDC機(jī)制日志模板中加入traceId標(biāo)識(shí),取值方式為%X{traceId} 。這樣在收集的日志文件中就可以看到每行日志有一個(gè)tracceId值,每個(gè)請(qǐng)求的值都不一樣,這樣我們就可以根據(jù)traceId查詢過濾出一次請(qǐng)求的所有上下文日志了。

2.實(shí)現(xiàn)方案

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

由于MDC內(nèi)部使用的是ThreadLocal所以只有本線程才有效,子線程和下游的服務(wù)MDC里的值會(huì)丟失;所以方案主要的難點(diǎn)是解決traceId值的傳遞問題,需要重點(diǎn)關(guān)注一下兩點(diǎn):

  • MDCtraceId數(shù)據(jù)如何傳遞給下游服務(wù),下游服務(wù)如何接收traceId并放入MDC
  • 異步的情況下(線程池)如何把traceId值傳給子線程。

2.1 設(shè)置日志模板

無論是我們的項(xiàng)目使用的是log4j還是logback框架,我們都需要先調(diào)整日志配置文件的日志格式如下:

<!-- 日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="[%X{traceId}] [%-5p] [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%t@${PID}]  %c %M : %m%n"/>

這樣才能有效地把traceId收集到日志文件中。

2.2 請(qǐng)求上下文設(shè)置traceId并有效傳遞下游服務(wù)

按照上面說的,每個(gè)請(qǐng)求使用一個(gè)唯一標(biāo)識(shí)traceId來追蹤一次請(qǐng)求的全部日志,這就要求我們的traceId必須保證唯一性,不然就會(huì)出現(xiàn)請(qǐng)求日志混亂問題,是絕對(duì)不允許的。這里我們利用hutool框架的生成id工具IdUtil來生成唯一值,可以生成uuid或者使用雪花算法Snowflake生成唯一id都可以,因?yàn)檫@里id是記錄在日志文件中做唯一標(biāo)識(shí)用的,所以對(duì)id字符類型,遞增性那些沒啥要求,只要唯一標(biāo)識(shí)即可,按照之前習(xí)慣,我就用雪花算法生成唯一id標(biāo)識(shí)了。

生成traceId并放入到MDC上下文中

public class WebTraceFilter extends OncePerRequestFilter {
?
?
 ? ?@Override
 ? ?protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?FilterChain filterChain) throws IOException, ServletException {
 ? ? ? ?try {
 ? ? ? ? ? ?String traceId = request.getHeader(MDCTraceUtils.TRACE_ID_HEADER);
 ? ? ? ? ? ?if (StrUtil.isEmpty(traceId)) {
 ? ? ? ? ? ? ? ?MDCTraceUtils.addTrace();
 ? ? ? ? ?  } else {
 ? ? ? ? ? ? ? ?MDCTraceUtils.putTrace(traceId);
 ? ? ? ? ?  }
 ? ? ? ? ? ?filterChain.doFilter(request, response);
 ? ? ?  } finally {
 ? ? ? ? ? ?MDCTraceUtils.removeTrace();
 ? ? ?  }
 ?  }
}

這里通過一個(gè)過濾器來設(shè)置traceId放入到MDC中,可以將該過濾器的執(zhí)行優(yōu)先級(jí)設(shè)置比較靠前,這樣就可以有效保證我們一次請(qǐng)求上下文的日志中都有traceId了。同時(shí)這個(gè)過濾器我們是集成在自定義實(shí)現(xiàn)的web starter中,公司的所有服務(wù)都會(huì)引用web starter集成該過濾器,意味著只要我們請(qǐng)求下游服務(wù)時(shí)添加了traceId這個(gè)header,下游服務(wù)執(zhí)行到該過濾器時(shí)就會(huì)拿到上游服務(wù)傳遞過來的traceId值放入到當(dāng)前服務(wù)的MDC中。MDCTraceUtils工具類代碼如下:

public class MDCTraceUtils {
 ? ?/**
 ? ? * 追蹤id的名稱
 ? ? */
 ? ?public static final String KEY_TRACE_ID = "traceId";
?
 ? ?/**
 ? ? * 日志鏈路追蹤id信息頭
 ? ? */
 ? ?public static final String TRACE_ID_HEADER = "x-traceId-header";
?
?
 ? ?/**
 ? ? * 創(chuàng)建traceId并賦值MDC
 ? ? */
 ? ?public static void addTrace() {
 ? ? ? ?String traceId = createTraceId();
 ? ? ? ?MDC.put(KEY_TRACE_ID, traceId);
 ?  }
?
 ? ?/**
 ? ? * 賦值MDC
 ? ? */
 ? ?public static void putTrace(String traceId) {
 ? ? ? ?MDC.put(KEY_TRACE_ID, traceId);
 ?  }
?
 ? ?/**
 ? ? * 獲取MDC中的traceId值
 ? ? */
 ? ?public static String getTraceId() {
 ? ? ? ?return MDC.get(KEY_TRACE_ID);
 ?  }
?
 ? ?/**
 ? ? * 清除MDC的值
 ? ? */
 ? ?public static void removeTrace() {
 ? ? ? ?MDC.remove(KEY_TRACE_ID);
 ?  }
?
 ? ?/**
 ? ? * 創(chuàng)建traceId
 ? ? */
 ? ?public static String createTraceId() {
 ? ? ? ?return IdUtil.getSnowflake().nextIdStr();
 ?  }
?
}

接下來我們就來演示下traceId如何在服務(wù)間有效傳遞。無論是微服務(wù)間的服務(wù)調(diào)用還是單體項(xiàng)目的調(diào)用下游服務(wù),我都建議使用Spring Cloud框架中的openfeign組件進(jìn)行服務(wù)間接口調(diào)用,如果對(duì)組件openfeign不太熟悉的,可以看看之前我總結(jié)的 openfeign實(shí)現(xiàn)原理進(jìn)行了解。這里就用openFeign進(jìn)行模擬服務(wù)間調(diào)用下游服務(wù)獲取車間列表的接口

@FeignClient(name = "workshopService", url = "http://127.0.0.1:16688/textile", path = "/workshop")
public interface WorkshopService {
 ? ?@GetMapping("/list/temp")
 ? ?ResponseVO<List<WorkshopDTO>> getList();
}

增加feign攔截器,繼續(xù)把當(dāng)前服務(wù)的traceId值傳遞給下游服務(wù)

public class FeignInterceptor implements RequestInterceptor {
 ? ?@Override
 ? ?public void apply(RequestTemplate requestTemplate) {
 ? ? ? ?ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
 ? ? ? ?// 傳遞請(qǐng)求相關(guān)header
 ? ? ? ?if (requestAttributes != null) {
 ? ? ? ? ? ?HttpServletRequest request = requestAttributes.getRequest();
 ? ? ? ? ? ?Enumeration<String> headerNames = request.getHeaderNames();
 ? ? ? ? ? ?if (headerNames != null) {
 ? ? ? ? ? ? ? ?while (headerNames.hasMoreElements()) {
 ? ? ? ? ? ? ? ? ? ?String name = headerNames.nextElement();
 ? ? ? ? ? ? ? ? ? ?// 跳過 content-length
 ? ? ? ? ? ? ? ? ? ?if (Objects.equals("content-length", name)){
 ? ? ? ? ? ? ? ? ? ? ? ?continue;
 ? ? ? ? ? ? ? ? ?  }
 ? ? ? ? ? ? ? ? ? ?String value = request.getHeader(name);
 ? ? ? ? ? ? ? ? ? ?requestTemplate.header(name, value);
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ?  }
 ? ? ? ?// 傳遞日志追蹤的traceId
 ? ? ? ?String traceId = MDCTraceUtils.getTraceId();
 ? ? ? ?if (StringUtils.isNotBlank(traceId)) {
 ? ? ? ? ? ?requestTemplate.header(MDCTraceUtils.TRACE_ID_HEADER, traceId);
 ? ? ?  }
 ?  }
}

可以看到這里主要完成傳遞請(qǐng)求的header,traceId這個(gè)header單獨(dú)處理,這是因?yàn)?code>webTraceFilter過濾器中只把traceId放入了MDC中,并沒有吧traceId放入到請(qǐng)求的header中,servlet層的filter過濾器Spring不建議修改請(qǐng)求的參數(shù),包括header,改起來也比較麻煩,所以這里需要單獨(dú)處理傳遞。當(dāng)然這里的攔截器FeignInterceptor和上面的過濾器WebTraceFilter都需要注入到Spring容器中。

編寫代碼進(jìn)行接口調(diào)用測(cè)試:

 ? ?@GetMapping("/trace")
 ? ?public void testTrace() {
 ? ? ? ?log.info("開始執(zhí)行咯");
 ? ? ? ?BaseQuery query = new BaseQuery();
 ? ? ? ?ResponseVO<List<WorkshopDTO>> responseVO = workshopService.getList();
 ? ? ? ?log.info("接口返回結(jié)果:{}", responseVO);
 ?  }

執(zhí)行日志打印如下,當(dāng)前服務(wù)的日志:

[1675794072381583360] [INFO ] [2023-07-03 17:10:16.289] [http-nio-18888-exec-5@24417]  com.plasticene.boot.web.core.aop.ApiLogPrintAspect timeAround : Request Info : {"ip":"127.0.0.1","url":"http://127.0.0.1:18888/fds/test/trace","httpMethod":"GET","classMethod":"com.plasticene.fast.controller.TestController.testTrace","requestParams":null}
[1675794072381583360] [INFO ] [2023-07-03 17:10:16.299] [http-nio-18888-exec-5@24417]  com.plasticene.fast.controller.TestController testTrace$original$mZGAheRd : 開始執(zhí)行咯
[1675794072381583360] [INFO ] [2023-07-03 17:10:17.087] [http-nio-18888-exec-5@24417]  com.plasticene.fast.controller.TestController testTrace$original$mZGAheRd : 接口返回結(jié)果:ResponseVO(code=200, msg=OK, data=[WorkshopDTO(id=3, orgId=4, name=檢驗(yàn)車間, location=杭州市西湖區(qū), remark=這里是最嚴(yán)格的, machineCount=null)])
[1675794072381583360] [INFO ] [2023-07-03 17:10:17.088] [http-nio-18888-exec-5@24417]  com.plasticene.boot.web.core.aop.ApiLogPrintAspect timeAround : Response result:  null
[1675794072381583360] [INFO ] [2023-07-03 17:10:17.089] [http-nio-18888-exec-5@24417]  com.plasticene.boot.web.core.aop.ApiLogPrintAspect timeAround : time cost:  805

traceId為:1675794072381583360,看看下游服務(wù)textile的日志如下:

[1675794072381583360] [INFO ] [2023-07-03 17:10:16.438] [http-nio-16688-exec-1@24461]  com.plasticene.boot.web.core.aop.ApiLogPrintAspect timeAround : Request Info : {"ip":"127.0.0.1","url":"http://127.0.0.1:16688/textile/workshop/list/temp","httpMethod":"GET","classMethod":"com.plasticene.textile.controller.WorkshopController.getAllList","requestParams":null}
[1675794072381583360] [DEBUG] [2023-07-03 17:10:16.939] [http-nio-16688-exec-1@24461]  com.plasticene.textile.dao.WorkshopDAO.selectList debug : ==>  Preparing: SELECT id, org_id, name, location, remark, create_time, update_time, creator, updater FROM workshop WHERE (org_id = ?) ORDER BY id DESC
[1675794072381583360] [DEBUG] [2023-07-03 17:10:16.972] [http-nio-16688-exec-1@24461]  com.plasticene.textile.dao.WorkshopDAO.selectList debug : ==> Parameters: 4(Integer)
[1675794072381583360] [DEBUG] [2023-07-03 17:10:17.008] [http-nio-16688-exec-1@24461]  com.plasticene.textile.dao.WorkshopDAO.selectList debug : <==      Total: 1
[1675794072381583360] [INFO ] [2023-07-03 17:10:17.029] [http-nio-16688-exec-1@24461]  com.plasticene.boot.web.core.aop.ApiLogPrintAspect timeAround : Response result:  [{"id":3,"orgId":4,"name":"檢驗(yàn)車間","location":"杭州市西湖區(qū)","remark":"這里是最嚴(yán)格的","machineCount":null}]
[1675794072381583360] [INFO ] [2023-07-03 17:10:17.040] [http-nio-16688-exec-1@24461]  com.plasticene.boot.web.core.aop.ApiLogPrintAspect timeAround : time cost:  621

可以看到兩個(gè)服務(wù)的traceId都是一樣的,這就說明我們的traceId有效傳遞了。

當(dāng)然我們也可以使用Spring自帶的RestTemplate、或者h(yuǎn)ttpClient、OkHttp3等框架進(jìn)行接口調(diào)用,只要請(qǐng)求接口時(shí)設(shè)置traceId這個(gè)header即可,使用restTemplate客戶端調(diào)接口時(shí),還可以通過擴(kuò)展點(diǎn)ClientHttpRequestInterceptor接口的實(shí)現(xiàn)類對(duì)請(qǐng)求進(jìn)行攔截處理進(jìn)行統(tǒng)一traceIdheader設(shè)置,這樣就不用每個(gè)接口請(qǐng)求都要設(shè)置一遍,盡量減少重復(fù)勞動(dòng)做到優(yōu)雅不過時(shí)。這里不在展示詳細(xì),請(qǐng)自我去實(shí)現(xiàn)。

2.3 異步父子線程traceId傳遞

上面說過MDC內(nèi)部使用的是ThreadLocal,所以只有本線程才有效,子線程和下游的服務(wù)MDC里的值會(huì)丟失。我們項(xiàng)目服務(wù)使用的logback日志框架,所以我們需要重寫logback的LogbackMDCAdapter,由于logback的MDC實(shí)現(xiàn)內(nèi)部使用的是ThreadLocal不能傳遞子線程,所以需要重寫替換為阿里的TransmittableThreadLocalTransmittableThreadLocal 是Alibaba開源的、用于解決在使用線程池等會(huì)池化復(fù)用線程的執(zhí)行組件情況下,提供ThreadLocal值的傳遞功能,解決異步執(zhí)行時(shí)上下文傳遞的問題。

重寫logback的LogbackMDCAdapter,自定義實(shí)現(xiàn)TtlMDCAdapter類,其實(shí)就是把LogbackMDCAdapterThreadLocal換成TransmittableThreadLocal即可,其他代碼都是一樣的。

/**
 *重構(gòu){@link LogbackMDCAdapter}類,搭配TransmittableThreadLocal實(shí)現(xiàn)父子線程之間的數(shù)據(jù)傳遞
 *
 * @author fjzheng
 * @version 1.0
 * @date 2022/7/14 13:50
 */
public class TtlMDCAdapter implements MDCAdapter {
 ? ?private final ThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new TransmittableThreadLocal<>();
?
 ? ?private static final int WRITE_OPERATION = 1;
 ? ?private static final int MAP_COPY_OPERATION = 2;
?
 ? ?private static TtlMDCAdapter mtcMDCAdapter;
?
 ? ?/**
 ? ? * keeps track of the last operation performed
 ? ? */
 ? ?private final ThreadLocal<Integer> lastOperation = new ThreadLocal<>();
?
 ? ?static {
 ? ? ? ?mtcMDCAdapter = new TtlMDCAdapter();
 ? ? ? ?MDC.mdcAdapter = mtcMDCAdapter;
 ?  }
?
 ? ?public static MDCAdapter getInstance() {
 ? ? ? ?return mtcMDCAdapter;
 ?  }
?
 ? ?private Integer getAndSetLastOperation(int op) {
 ? ? ? ?Integer lastOp = lastOperation.get();
 ? ? ? ?lastOperation.set(op);
 ? ? ? ?return lastOp;
 ?  }
?
 ? ?private static boolean wasLastOpReadOrNull(Integer lastOp) {
 ? ? ? ?return lastOp == null || lastOp == MAP_COPY_OPERATION;
 ?  }
?
 ? ?private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {
 ? ? ? ?Map<String, String> newMap = Collections.synchronizedMap(new HashMap<>());
 ? ? ? ?if (oldMap != null) {
 ? ? ? ? ? ?// we don't want the parent thread modifying oldMap while we are
 ? ? ? ? ? ?// iterating over it
 ? ? ? ? ? ?synchronized (oldMap) {
 ? ? ? ? ? ? ? ?newMap.putAll(oldMap);
 ? ? ? ? ?  }
 ? ? ?  }
?
 ? ? ? ?copyOnInheritThreadLocal.set(newMap);
 ? ? ? ?return newMap;
 ?  }
?
 ? ?/**
 ? ? * Put a context value (the <code>val</code> parameter) as identified with the
 ? ? * <code>key</code> parameter into the current thread's context map. Note that
 ? ? * contrary to log4j, the <code>val</code> parameter can be null.
 ? ? * <p/>
 ? ? * <p/>
 ? ? * If the current thread does not have a context map it is created as a side
 ? ? * effect of this call.
 ? ? *
 ? ? * @throws IllegalArgumentException in case the "key" parameter is null
 ? ? */
 ? ?@Override
 ? ?public void put(String key, String val) {
 ? ? ? ?if (key == null) {
 ? ? ? ? ? ?throw new IllegalArgumentException("key cannot be null");
 ? ? ?  }
?
 ? ? ? ?Map<String, String> oldMap = copyOnInheritThreadLocal.get();
 ? ? ? ?Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
?
 ? ? ? ?if (wasLastOpReadOrNull(lastOp) || oldMap == null) {
 ? ? ? ? ? ?Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
 ? ? ? ? ? ?newMap.put(key, val);
 ? ? ?  } else {
 ? ? ? ? ? ?oldMap.put(key, val);
 ? ? ?  }
 ?  }
?
 ? ?/**
 ? ? * Remove the the context identified by the <code>key</code> parameter.
 ? ? * <p/>
 ? ? */
 ? ?@Override
 ? ?public void remove(String key) {
 ? ? ? ?if (key == null) {
 ? ? ? ? ? ?return;
 ? ? ?  }
 ? ? ? ?Map<String, String> oldMap = copyOnInheritThreadLocal.get();
 ? ? ? ?if (oldMap == null) {
 ? ? ? ? ? ?return;
 ? ? ?  }
?
 ? ? ? ?Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
?
 ? ? ? ?if (wasLastOpReadOrNull(lastOp)) {
 ? ? ? ? ? ?Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
 ? ? ? ? ? ?newMap.remove(key);
 ? ? ?  } else {
 ? ? ? ? ? ?oldMap.remove(key);
 ? ? ?  }
?
 ?  }
?
?
 ? ?/**
 ? ? * Clear all entries in the MDC.
 ? ? */
 ? ?@Override
 ? ?public void clear() {
 ? ? ? ?lastOperation.set(WRITE_OPERATION);
 ? ? ? ?copyOnInheritThreadLocal.remove();
 ?  }
?
 ? ?/**
 ? ? * Get the context identified by the <code>key</code> parameter.
 ? ? * <p/>
 ? ? */
 ? ?@Override
 ? ?public String get(String key) {
 ? ? ? ?final Map<String, String> map = copyOnInheritThreadLocal.get();
 ? ? ? ?if ((map != null) && (key != null)) {
 ? ? ? ? ? ?return map.get(key);
 ? ? ?  } else {
 ? ? ? ? ? ?return null;
 ? ? ?  }
 ?  }
?
 ? ?/**
 ? ? * Get the current thread's MDC as a map. This method is intended to be used
 ? ? * internally.
 ? ? */
 ? ?public Map<String, String> getPropertyMap() {
 ? ? ? ?lastOperation.set(MAP_COPY_OPERATION);
 ? ? ? ?return copyOnInheritThreadLocal.get();
 ?  }
?
 ? ?/**
 ? ? * Returns the keys in the MDC as a {@link Set}. The returned value can be
 ? ? * null.
 ? ? */
 ? ?public Set<String> getKeys() {
 ? ? ? ?Map<String, String> map = getPropertyMap();
?
 ? ? ? ?if (map != null) {
 ? ? ? ? ? ?return map.keySet();
 ? ? ?  } else {
 ? ? ? ? ? ?return null;
 ? ? ?  }
 ?  }
?
 ? ?/**
 ? ? * Return a copy of the current thread's context map. Returned value may be
 ? ? * null.
 ? ? */
 ? ?@Override
 ? ?public Map<String, String> getCopyOfContextMap() {
 ? ? ? ?Map<String, String> hashMap = copyOnInheritThreadLocal.get();
 ? ? ? ?if (hashMap == null) {
 ? ? ? ? ? ?return null;
 ? ? ?  } else {
 ? ? ? ? ? ?return new HashMap<>(hashMap);
 ? ? ?  }
 ?  }
?
 ? ?@Override
 ? ?public void setContextMap(Map<String, String> contextMap) {
 ? ? ? ?lastOperation.set(WRITE_OPERATION);
?
 ? ? ? ?Map<String, String> newMap = Collections.synchronizedMap(new HashMap<>());
 ? ? ? ?newMap.putAll(contextMap);
?
 ? ? ? ?// the newMap replaces the old one for serialisation's sake
 ? ? ? ?copyOnInheritThreadLocal.set(newMap);
 ?  }
}

接下來只需要實(shí)現(xiàn)程序啟動(dòng)時(shí)加載上我們自己實(shí)現(xiàn)的TtlMDCAdapter:

/**
 *
 * 初始化TtlMDCAdapter實(shí)例,并替換MDC中的adapter對(duì)象
 *
 * @author fjzheng
 * @version 1.0
 * @date 2022/7/14 13:55
 */
public class TtlMDCAdapterInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
 ? ?@Override
 ? ?public void initialize(ConfigurableApplicationContext applicationContext) {
 ? ? ? ?//加載TtlMDCAdapter實(shí)例
 ? ? ? ?TtlMDCAdapter.getInstance();
 ?  }
}

這樣我們?cè)诋惒蕉嗑€程情況下MDCtraceId值就能正常傳遞,下面來看看測(cè)試示例:

 ? // 定義線程池
 ? private ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
 ? ? ? ? ?  .setNameFormat("letter-pool-%d").build();
 ? private ExecutorService fixedThreadPool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors()*2,
 ? ? ? ? ? ?Runtime.getRuntime().availableProcessors() * 40,
 ? ? ? ? ? ?0L,
 ? ? ? ? ? ?TimeUnit.MILLISECONDS,
 ? ? ? ? ? ?new LinkedBlockingQueue<Runnable>(Runtime.getRuntime().availableProcessors() * 20),
 ? ? ? ? ? ?namedThreadFactory);
 ? ?// 測(cè)試接口
 ? ?@GetMapping("/async")
 ? ?public void testAsync() {
 ? ? ? ?log.info("打印日志了");
 ? ? ? ?fixedThreadPool.execute(()->{
 ? ? ? ? ? ?log.info("異步執(zhí)行了");
 ? ? ? ? ? ?try {
 ? ? ? ? ? ? ? ?Student student = null;
 ? ? ? ? ? ? ? ?String name = student.getName();
 ? ? ? ? ?  } catch (Exception e) {
 ? ? ? ? ? ? ? ?log.error("異步報(bào)錯(cuò)了:", e);
 ? ? ? ? ?  }
?
 ? ? ?  });
 ?  }

執(zhí)行結(jié)果日志打印如下:、

[1675805796950241280] [INFO ] [2023-07-03 17:56:51.683] [http-nio-18888-exec-8@24417]  com.plasticene.boot.web.core.aop.ApiLogPrintAspect timeAround : Request Info : {"ip":"127.0.0.1","url":"http://127.0.0.1:18888/fds/test","httpMethod":"GET","classMethod":"com.plasticene.fast.controller.TestController.test","requestParams":null}
[1675805796950241280] [INFO ] [2023-07-03 17:56:51.698] [http-nio-18888-exec-8@24417]  com.plasticene.fast.controller.TestController test$original$mZGAheRd : 打印日志了
[1675805796950241280] [INFO ] [2023-07-03 17:56:51.700] [http-nio-18888-exec-8@24417]  com.plasticene.boot.web.core.aop.ApiLogPrintAspect timeAround : Response result:  null
[1675805796950241280] [INFO ] [2023-07-03 17:56:51.700] [http-nio-18888-exec-8@24417]  com.plasticene.boot.web.core.aop.ApiLogPrintAspect timeAround : time cost:  24
[1675805796950241280] [INFO ] [2023-07-03 17:56:51.700] [letter-pool-1@24417]  com.plasticene.fast.controller.TestController lambda$test$0 : 異步執(zhí)行了
[1675805796950241280] [ERROR] [2023-07-03 17:56:51.704] [letter-pool-1@24417]  com.plasticene.fast.controller.TestController lambda$test$0 : 異步報(bào)錯(cuò)了:
java.lang.NullPointerException: null
  at com.plasticene.fast.controller.TestController.lambda$test$0(TestController.java:93)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
  at java.lang.Thread.run(Thread.java:748)

3.總結(jié)

以上全部就是關(guān)于Spring Boot如何實(shí)現(xiàn)分布式日志鏈路追蹤的相關(guān)知識(shí)點(diǎn)。工欲善其事,必先利其器,我們要想快速通過日志定位系統(tǒng)問題,就必須通過traceId高效查找一次請(qǐng)求的全部上下文日志,包括異步執(zhí)行的邏輯。

以上就是SpringBoot項(xiàng)目實(shí)現(xiàn)分布式日志鏈路追蹤的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot 日志鏈路追蹤的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Springboot實(shí)現(xiàn)多文件上傳代碼解析

    Springboot實(shí)現(xiàn)多文件上傳代碼解析

    這篇文章主要介紹了Springboot實(shí)現(xiàn)多文件上傳代碼解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • Java日期類詳解(最新推薦)

    Java日期類詳解(最新推薦)

    早期版本主要使用?java.util.Date、java.util.Calendar?等類,Java?8及以后引入了新的日期和時(shí)間API(JSR?310),包含在java.time包中,本文給大家介紹Java日期類的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2025-06-06
  • 11個(gè)值得借鑒的Java?return寫法分享

    11個(gè)值得借鑒的Java?return寫法分享

    return?這個(gè)關(guān)鍵字,相信大家每天都在用,它就像一把錘子,敲打著我們代碼里的每一個(gè)出口,但捫心自問,我們真的把這把錘子用好了嗎,下面小編就來和大家分享11個(gè)值得借鑒的Java?return寫法吧
    2025-04-04
  • Spring?Boot在Web應(yīng)用中基于JdbcRealm安全驗(yàn)證過程

    Spring?Boot在Web應(yīng)用中基于JdbcRealm安全驗(yàn)證過程

    這篇文章主要為大家介紹了Spring?Boot在Web應(yīng)用中基于JdbcRealm安全驗(yàn)證過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>
    2023-02-02
  • idea插件之如何使用JarEditor編輯Java JAR文件

    idea插件之如何使用JarEditor編輯Java JAR文件

    JarEditor是一款用于在IntelliJIDEA中直接編輯JAR文件的插件,支持反編譯查看和編輯.class文件,并提供即時(shí)編譯與保存功能,通過JarEditor,用戶可以在IDE內(nèi)一站式完成JAR文件的編輯、管理和打包操作,提高開發(fā)效率,但在生產(chǎn)環(huán)境中使用前,請(qǐng)確保備份并測(cè)試修改
    2025-01-01
  • java啟動(dòng)線程的3種方式對(duì)比分析

    java啟動(dòng)線程的3種方式對(duì)比分析

    這篇文章主要為大家對(duì)比分析了java啟動(dòng)線程的3種方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • 解決json串和實(shí)體類字段不一致的問題

    解決json串和實(shí)體類字段不一致的問題

    這篇文章主要介紹了解決json串和實(shí)體類字段不一致的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03
  • Mybatis 一級(jí)緩存與二級(jí)緩存的實(shí)現(xiàn)

    Mybatis 一級(jí)緩存與二級(jí)緩存的實(shí)現(xiàn)

    mybatis作為一個(gè)流行的持久化工具,緩存必然是缺少不了的組件。通過這篇文章,就讓我們來了解一下一級(jí)緩存與二級(jí)緩存的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • Java docx4j實(shí)現(xiàn)Word到PDF轉(zhuǎn)換的完整指南

    Java docx4j實(shí)現(xiàn)Word到PDF轉(zhuǎn)換的完整指南

    本文介紹了如何使用docx4j這個(gè)開源Java庫來將Microsoft Word的.docx文檔轉(zhuǎn)換為PDF格式,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下
    2026-02-02
  • Mybatis Plus 增刪改查的實(shí)現(xiàn)(小白教程)

    Mybatis Plus 增刪改查的實(shí)現(xiàn)(小白教程)

    本文主要介紹了Mybatis Plus 增刪改查,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09

最新評(píng)論

庐江县| 行唐县| 崇义县| 纳雍县| 海安县| 洛川县| 紫云| 左云县| 大城县| 广东省| 临猗县| 辰溪县| 阿克陶县| 汉中市| 桃园市| 青河县| 梓潼县| 景泰县| 寿宁县| 百色市| 盐亭县| 基隆市| 郑州市| 万源市| 临清市| 枞阳县| 泰兴市| 江川县| 盐亭县| 阿拉善盟| 青浦区| 临清市| 田东县| 文昌市| 安国市| 桐庐县| 南靖县| 临汾市| 临高县| 蒙自县| 定安县|