亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

SpringBoot 項(xiàng)目添加 MDC 日志鏈路追蹤的執(zhí)行流程

瀏覽:54日期:2023-03-02 17:57:53
目錄1. 線程池配置2. 攔截器配置3. 日志文件配置4. 使用方法示例4.1. 異步使用4.2. 定時(shí)任務(wù)

日志鏈路追蹤的意思就是將一個(gè)標(biāo)志跨線程進(jìn)行傳遞,在一般的小項(xiàng)目中也就是在你新起一個(gè)線程的時(shí)候,或者使用線程池執(zhí)行任務(wù)的時(shí)候會(huì)用到,比如追蹤一個(gè)用戶請(qǐng)求的完整執(zhí)行流程。

這里用到MDC和ThreadLocal,分別由下面的包提供:

java.lang.ThreadLocalorg.slf4j.MDC

直接上代碼:

1. 線程池配置

如果你直接通過(guò)手動(dòng)新建線程來(lái)執(zhí)行異步任務(wù),想要實(shí)現(xiàn)標(biāo)志傳遞的話,需要自己去實(shí)現(xiàn),其實(shí)和線程池一樣,也是調(diào)用MDC的相關(guān)方法,如下所示:

//取出父線程的MDCMap<String, String> context = MDC.getCopyOfContextMap();//將父線程的MDC內(nèi)容傳給子線程MDC.setContextMap(context);

首先提供一個(gè)常量:

package com.example.demo.common.constant;/** * 常量 * * @author wangbo * @date 2021/5/13 */public class Constants { public static final String LOG_MDC_ID = 'trace_id';}

接下來(lái)需要對(duì)ThreadPoolTaskExecutor的方法進(jìn)行重寫:

package com.example.demo.common.threadpool;import com.example.demo.common.constant.Constants;import lombok.extern.slf4j.Slf4j;import org.slf4j.MDC;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.Map;import java.util.UUID;import java.util.concurrent.Callable;import java.util.concurrent.Future;/** * MDC線程池 * 實(shí)現(xiàn)內(nèi)容傳遞 * * @author wangbo * @date 2021/5/13 */@Slf4jpublic class MdcTaskExecutor extends ThreadPoolTaskExecutor { @Override public <T> Future<T> submit(Callable<T> task) {log.info('mdc thread pool task executor submit');Map<String, String> context = MDC.getCopyOfContextMap();return super.submit(() -> { T result; if (context != null) {//將父線程的MDC內(nèi)容傳給子線程MDC.setContextMap(context); } else {//直接給子線程設(shè)置MDCMDC.put(Constants.LOG_MDC_ID, UUID.randomUUID().toString().replace('-', '')); } try {//執(zhí)行任務(wù)result = task.call(); } finally {try { MDC.clear();} catch (Exception e) { log.warn('MDC clear exception', e);} } return result;}); } @Override public void execute(Runnable task) {log.info('mdc thread pool task executor execute');Map<String, String> context = MDC.getCopyOfContextMap();super.execute(() -> { if (context != null) {//將父線程的MDC內(nèi)容傳給子線程MDC.setContextMap(context); } else {//直接給子線程設(shè)置MDCMDC.put(Constants.LOG_MDC_ID, UUID.randomUUID().toString().replace('-', '')); } try {//執(zhí)行任務(wù)task.run(); } finally {try { MDC.clear();} catch (Exception e) { log.warn('MDC clear exception', e);} }}); }}

然后使用自定義的重寫子類MdcTaskExecutor來(lái)實(shí)現(xiàn)線程池配置:

package com.example.demo.common.threadpool;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.concurrent.Executor;import java.util.concurrent.ThreadPoolExecutor;/** * 線程池配置 * * @author wangbo * @date 2021/5/13 */@Slf4j@Configurationpublic class ThreadPoolConfig { /** * 異步任務(wù)線程池 * 用于執(zhí)行普通的異步請(qǐng)求,帶有請(qǐng)求鏈路的MDC標(biāo)志 */ @Bean public Executor commonThreadPool() {log.info('start init common thread pool');//ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();MdcTaskExecutor executor = new MdcTaskExecutor();//配置核心線程數(shù)executor.setCorePoolSize(10);//配置最大線程數(shù)executor.setMaxPoolSize(20);//配置隊(duì)列大小executor.setQueueCapacity(3000);//配置空閑線程存活時(shí)間executor.setKeepAliveSeconds(120);//配置線程池中的線程的名稱前綴executor.setThreadNamePrefix('common-thread-pool-');//當(dāng)達(dá)到最大線程池的時(shí)候丟棄最老的任務(wù)executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());//執(zhí)行初始化executor.initialize();return executor; } /** * 定時(shí)任務(wù)線程池 * 用于執(zhí)行自啟動(dòng)的任務(wù)執(zhí)行,父線程不帶有MDC標(biāo)志,不需要傳遞,直接設(shè)置新的MDC * 和上面的線程池沒(méi)啥區(qū)別,只是名字不同 */ @Bean public Executor scheduleThreadPool() {log.info('start init schedule thread pool');MdcTaskExecutor executor = new MdcTaskExecutor();executor.setCorePoolSize(10);executor.setMaxPoolSize(20);executor.setQueueCapacity(3000);executor.setKeepAliveSeconds(120);executor.setThreadNamePrefix('schedule-thread-pool-');executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());executor.initialize();return executor; }}2. 攔截器配置

package com.example.demo.common.interceptor;import com.example.demo.common.constant.Constants;import lombok.extern.slf4j.Slf4j;import org.slf4j.MDC;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.util.UUID;/** * 日志攔截器 * * @author wangbo * @date 2021/5/13 */@Slf4j@Componentpublic class LogInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//log.info('進(jìn)入 LogInterceptor');//添加MDC值MDC.put(Constants.LOG_MDC_ID, UUID.randomUUID().toString().replace('-', ''));//打印接口請(qǐng)求信息String method = request.getMethod();String uri = request.getRequestURI();log.info('[請(qǐng)求接口] : {} : {}', method, uri);//打印請(qǐng)求參數(shù)return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {//log.info('執(zhí)行 LogInterceptor'); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//log.info('退出 LogInterceptor');//打印請(qǐng)求結(jié)果//刪除MDC值MDC.remove(Constants.LOG_MDC_ID); }}

對(duì)攔截器進(jìn)行注冊(cè):

package com.example.demo.common.config;import com.example.demo.common.interceptor.LogInterceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/** * MVC配置 * * @author wangbo * @date 2021/5/13 */@Configurationpublic class WebMvcConfig implements WebMvcConfigurer { @Autowired private LogInterceptor logInterceptor;/** * 攔截器注冊(cè) */ @Override public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(logInterceptor); }}3. 日志文件配置

需要在logback-spring.xml文件中的日志打印格式里添加%X{trace_id},如下所示:

<!-- 控制臺(tái)打印日志的相關(guān)配置 --><appender name='console_out' class='ch.qos.logback.core.ConsoleAppender'> <!-- 日志格式 --> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{trace_id}] [%level] [%thread] [%class:%line] - %m%n</pattern> <charset>UTF-8</charset> </encoder> </appender>4. 使用方法示例4.1. 異步使用

這里注意,異步方法的調(diào)用不能直接調(diào)用當(dāng)前類的方法,也就是說(shuō)調(diào)用方法和異步方法不能在同一個(gè)類里,否則會(huì)變?yōu)橥綀?zhí)行。

/** * 異步方法 */ //@Async//這種寫法,當(dāng)只有一個(gè)線程池時(shí),會(huì)使用該線程池執(zhí)行,有多個(gè)則會(huì)使用SimpleAsyncTaskExecutor @Async(value = 'commonThreadPool')//指定執(zhí)行的線程池 @Override public void async() {log.info('測(cè)試異步線程池'); }4.2. 定時(shí)任務(wù)

package com.example.demo.generator.crontab;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import java.time.LocalDateTime;/** * 定時(shí)任務(wù) * * @author wangbo * @date 2021/5/14 */@Slf4j@Componentpublic class TestTimeTask { //基于注解@Scheduled默認(rèn)為單線程,開啟多個(gè)任務(wù)時(shí),任務(wù)的執(zhí)行時(shí)機(jī)會(huì)受上一個(gè)任務(wù)執(zhí)行時(shí)間的影響。 //使用的線程池是taskScheduler,線程ID為scheduling-x //添加@Async注解指定線程池,則可以多線程執(zhí)行定時(shí)任務(wù)(原本是單線程的)。 /** * 兩次任務(wù)開始的時(shí)間間隔為2S * 不使用線程池,單線程間隔則為4S。單線程保證不了這個(gè)2S間隔,因?yàn)槿蝿?wù)執(zhí)行耗時(shí)超過(guò)了定時(shí)間隔,就會(huì)影響下一次任務(wù)的執(zhí)行 * 使用線程池,多線程執(zhí)行,時(shí)間間隔為2S */ //@Async(value = 'scheduleThreadPool') //@Scheduled(fixedRate = 2000) public void fixedRate() {log.info('定時(shí)間隔任務(wù) fixedRate = {}', LocalDateTime.now());try { Thread.sleep(4_000);} catch (InterruptedException e) { e.printStackTrace();} } /** * 下次任務(wù)的開始時(shí)間距離上次任務(wù)的結(jié)束時(shí)間間隔為2S * 這種適合使用單線程,不適合使用線程池,單線程間隔則為6S。 * 用了線程池,和這個(gè)特性相背離了 */ //@Scheduled(fixedDelay = 2_000) public void fixedDelay() {log.info('延遲定時(shí)間隔任務(wù) fixedDelay = {}', LocalDateTime.now());try { Thread.sleep(4_000);} catch (InterruptedException e) { e.printStackTrace();} } /** * 首次延遲10S后執(zhí)行fixedDelay類型間隔任務(wù),也可以配置為fixedDelay類型間隔任務(wù) * 控件第一次執(zhí)行之前要延遲的毫秒數(shù) * {@link # fixeddrate} or {@link #fixedDelay} */ //@Scheduled(initialDelay = 10_000, fixedDelay = 1_000) public void initialDelay() {log.info('首次延遲定時(shí)間隔任務(wù) initialDelay = {}', LocalDateTime.now()); } /** * 這里使用線程池也是為了防止任務(wù)執(zhí)行耗時(shí)超過(guò)了定時(shí)間隔,就會(huì)影響下一次任務(wù)的執(zhí)行 */ //@Async(value = 'scheduleThreadPool') //@Scheduled(cron = '0/2 * * * * *') public void testCron() {log.info('測(cè)試表達(dá)式定時(shí)任務(wù) testCron = {}', LocalDateTime.now());try { Thread.sleep(4_000);} catch (InterruptedException e) { e.printStackTrace();} }}

到此這篇關(guān)于SpringBoot 項(xiàng)目添加 MDC 日志鏈路追蹤的文章就介紹到這了,更多相關(guān)SpringBoot MDC 日志鏈路追蹤內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Spring
相關(guān)文章:
主站蜘蛛池模板: 国产精品久久久久影院色老大 | 久久网页 | 欧美一级毛片一免费 | 成人免费黄色网址 | 中文字幕专区高清在线观看 | 手机国产日韩高清免费看片 | 欧美换爱交换乱理伦片不卡片 | 日韩三级毛片 | 黄网站色在线视频免费观看 | 妞干网最新视频 | 欧美国产人妖另类色视频 | 人妇出轨三级香港 | 精品国产午夜久久久久九九 | 久久网址 | 女人182毛片a级毛片 | 日韩视频一区二区 | 欧美精品第1页在线播放 | 香港午夜理理伦_级毛片 | 日韩精品久久一区二区三区 | 国产福利在线永久视频 | 国产精品视频一区二区三区经 | 国产伦码精品一区二区 | 免费国产高清视频 | 国产在线观看青草视频 | 久久久精品免费热线观看 | 色午夜视频 | 一级黄色毛片播放 | 亚洲精品1区 | 久久综合九色综合欧美播 | 五月一区二区久久综合天堂 | 国产成人高清亚洲一区久久 | 久久视频精品线视频在线网站 | 麻豆91精品91久久久 | 亚洲色图150p | 亚洲国产情侣偷自在线二页 | 91女神在线观看 | 51自拍视频 | 国产日本三级在线播放线观看 | 亚洲黄色影院 | 国产在线观看入口 | 亚洲精品入口一区二区乱 |