欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot如何使用TraceId日志鏈路追蹤

 更新時(shí)間:2025年01月19日 10:25:36   作者:m0_74824592  
文章介紹了如何使用TraceId進(jìn)行日志鏈路追蹤,通過在日志中添加TraceId關(guān)鍵字,可以將同一次業(yè)務(wù)調(diào)用鏈上的日志串起來,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧

項(xiàng)目場(chǎng)景:

??有時(shí)候一個(gè)業(yè)務(wù)調(diào)用鏈場(chǎng)景,很長(zhǎng),調(diào)了各種各樣的方法,看日志的時(shí)候,各個(gè)接口的日志穿插,確實(shí)讓人頭大。為了解決這個(gè)痛點(diǎn),就使用了TraceId,根據(jù)TraceId關(guān)鍵字進(jìn)入服務(wù)器查詢?nèi)罩局惺欠裼羞@個(gè)TraceId,這樣就把同一次的業(yè)務(wù)調(diào)用鏈上的日志串起來了。

實(shí)現(xiàn)步驟

1、pom.xml 依賴

<dependencies>
????<dependency>
????????<groupId>org.springframework.boot</groupId>
????????<artifactId>spring-boot-starter-web</artifactId>
????</dependency>
????<dependency>
????????<groupId>org.springframework.boot</groupId>
????????<artifactId>spring-boot-starter-test</artifactId>
????????<scope>test</scope>
????</dependency>
????<dependency>
????????<groupId>org.springframework.boot</groupId>
????????<artifactId>spring-boot-starter-logging</artifactId>
????</dependency>
????<!--lombok配置-->
????<dependency>
????????<groupId>org.projectlombok</groupId>
????????<artifactId>lombok</artifactId>
????????<version>1.16.10</version>
????</dependency>
</dependencies>

2、整合logback,打印日志,logback-spring.xml (簡(jiǎn)單配置下)

關(guān)鍵代碼:[traceId:%X{traceId}],traceId是通過攔截器里MDC.put(traceId, tid)添加

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--日志存儲(chǔ)路徑-->
    <property name="log" value="D:/test/log" />
    <!-- 控制臺(tái)輸出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--輸出格式化-->
            <pattern>[traceId:%X{traceId}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 按天生成日志文件 -->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件名-->
            <FileNamePattern>${log}/%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--保留天數(shù)-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>[traceId:%X{traceId}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>
    <!-- 日志輸出級(jí)別 -->
    <root level="INFO">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>
</configuration>

3、application.yml

server:
  port: 8826
logging:
  config: classpath:logback-spring.xml

4、自定義日志攔截器 LogInterceptor.java

用途:每一次鏈路,線程維度,添加最終的鏈路ID traceId。

MDC(Mapped Diagnostic Context)診斷上下文映射,是@Slf4j提供的一個(gè)支持動(dòng)態(tài)打印日志信息的工具。

import org.slf4j.MDC;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
/**
 * 日志攔截器
 */
public class LogInterceptor implements HandlerInterceptor {
    private static final String traceId = "traceId";
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String tid = UUID.randomUUID().toString().replace("-", "");
        //可以考慮讓客戶端傳入鏈路ID,但需保證一定的復(fù)雜度唯一性;如果沒使用默認(rèn)UUID自動(dòng)生成
        if (!StringUtils.isEmpty(request.getHeader("traceId"))){
            tid=request.getHeader("traceId");
        }
        MDC.put(traceId, tid);
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                @Nullable Exception ex) {
        // 請(qǐng)求處理完成后,清除MDC中的traceId,以免造成內(nèi)存泄漏
        MDC.remove(traceId);
    }
}

5、WebConfigurerAdapter.java 添加攔截器

ps: 其實(shí)這個(gè)攔截的部分改為使用自定義注解+aop也是很靈活的。

import javax.annotation.Resource;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class WebConfigurerAdapter extends WebMvcConfigurationSupport {
    @Resource
	private LogInterceptor logInterceptor;
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(logInterceptor);
		//可以具體制定哪些需要攔截,哪些不攔截,其實(shí)也可以使用自定義注解更靈活完成
//                .addPathPatterns("/**")
//                .excludePathPatterns("/testxx.html");
	}
}

6、測(cè)試接口

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Api(tags = "測(cè)試接口")
@RequestMapping("/test")
@Slf4j
public class TestController {
	@RequestMapping(value = "/log", method = RequestMethod.GET)
	@ApiOperation(value = "測(cè)試日志")
	public String sign() {
		log.info("這是一行info日志");
		log.error("這是一行error日志");
		return "success";
	}
}

結(jié)果:

異步場(chǎng)景:

使用線程的場(chǎng)景,寫一個(gè)異步線程,加入這個(gè)調(diào)用里面。再次執(zhí)行看開效果,我們會(huì)發(fā)現(xiàn)顯然子線程丟失了trackId。

所以我們需要針對(duì)子線程使用情形,做調(diào)整,思路:將父線程的trackId傳遞下去給子線程即可。

1、ThreadMdcUtil.java

import org.slf4j.MDC;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
/**
 * @Author: JCccc
 * @Date: 2022-5-30 11:14
 * @Description:
 */
public final class ThreadMdcUtil {
    private static final String traceId = "traceId";
    // 獲取唯一性標(biāo)識(shí)
    public static String generateTraceId() {
        return UUID.randomUUID().toString().replace("-", "");
    }
    public static void setTraceIdIfAbsent() {
        if (MDC.get(traceId) == null) {
            MDC.put(traceId, generateTraceId());
        }
    }
    /**
     * 用于父線程向線程池中提交任務(wù)時(shí),將自身MDC中的數(shù)據(jù)復(fù)制給子線程
     *
     * @param callable
     * @param context
     * @param <T>
     * @return
     */
    public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                return callable.call();
            } finally {
                MDC.clear();
            }
        };
    }
    /**
     * 用于父線程向線程池中提交任務(wù)時(shí),將自身MDC中的數(shù)據(jù)復(fù)制給子線程
     *
     * @param runnable
     * @param context
     * @return
     */
    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}

2、MyThreadPoolTaskExecutor.java 是我們自己寫的,重寫了一些方法

import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
public final class MyThreadPoolTaskExecutor  extends ThreadPoolTaskExecutor  {
    public MyThreadPoolTaskExecutor() {
        super();
    }
    @Override
    public void execute(Runnable task) {
        super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
}

3、ThreadPoolConfig.java 定義線程池,交給spring管理

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.concurrent.Executor;
@EnableAsync
@Configuration
public class ThreadPoolConfig {
    /**
     * 聲明一個(gè)線程池
     */
    @Bean("taskExecutor")
    public Executor taskExecutor() {
        MyThreadPoolTaskExecutor executor = new MyThreadPoolTaskExecutor();
        //核心線程數(shù)5:線程池創(chuàng)建時(shí)候初始化的線程數(shù)
        executor.setCorePoolSize(5);
        //最大線程數(shù)5:線程池最大的線程數(shù),只有在緩沖隊(duì)列滿了之后才會(huì)申請(qǐng)超過核心線程數(shù)的線程
        executor.setMaxPoolSize(5);
        //緩沖隊(duì)列500:用來緩沖執(zhí)行任務(wù)的隊(duì)列
        executor.setQueueCapacity(500);
        //允許線程的空閑時(shí)間60秒:當(dāng)超過了核心線程出之外的線程在空閑時(shí)間到達(dá)之后會(huì)被銷毀
        executor.setKeepAliveSeconds(60);
        //線程池名的前綴:設(shè)置好了之后可以方便我們定位處理任務(wù)所在的線程池
        executor.setThreadNamePrefix("taskExecutor-");
        executor.initialize();
        return executor;
    }
}

4、Service

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
 * 測(cè)試Service
 */
@Service("testService")
@Slf4j
public class TestService {
	/**
	 * 異步操作測(cè)試
	 */
	@Async("taskExecutor")
	public void asyncTest() {
		try {
			log.info("模擬異步開始......");
			Thread.sleep(3000);
			log.info("模擬異步結(jié)束......");
		} catch (InterruptedException e) {
			log.error("異步操作出錯(cuò):"+e);
		}
	}
}

5、測(cè)試接口

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Api(tags = "測(cè)試接口")
@RequestMapping("/test")
@Slf4j
public class TestController {
	@Resource
	private TestService testService;
	@RequestMapping(value = "/log", method = RequestMethod.GET)
	@ApiOperation(value = "測(cè)試日志")
	public String sign() {
		log.info("這是一行info日志");
		log.error("這是一行error日志");
		//異步操作測(cè)試
		testService.asyncTest();
		return "success";
	}
}

結(jié)果:

我們可以看到,子線程的日志也被串起來了。

定時(shí)任務(wù):

如果使用了定時(shí)任務(wù)@Scheduled,這時(shí)候執(zhí)行定時(shí)任務(wù),不會(huì)走上面的攔截器邏輯,所以定時(shí)任務(wù)需要單獨(dú)創(chuàng)建個(gè)AOP切面。

1、創(chuàng)建個(gè)定時(shí)任務(wù)線程池

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executors;
/**
 * 定時(shí)任務(wù)線程池
 */
@EnableScheduling
@Configuration
public class SeheduleConfig implements SchedulingConfigurer{
	@Override
	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
		taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
	}
}

2、創(chuàng)建個(gè)AOP切面

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import java.util.UUID;
@Aspect   //定義一個(gè)切面
@Configuration
public class SeheduleTaskAspect {
    // 定義定時(shí)任務(wù)切點(diǎn)Pointcut
    @Pointcut("@annotation(org.springframework.scheduling.annotation.Scheduled)")
    public void seheduleTask() {
    }
    @Around("seheduleTask()")
    public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
		try {
			String traceId = UUID.randomUUID().toString().replace("-", "");
			//用于日志鏈路追蹤,logback配置:%X{traceId}
			MDC.put("traceId", traceId);
			//執(zhí)行定時(shí)任務(wù)方法
	        joinPoint.proceed();
		} finally {
			//請(qǐng)求處理完成后,清除MDC中的traceId,以免造成內(nèi)存泄漏
			MDC.remove("traceId");
		}
    }
}

3、創(chuàng)建定時(shí)任務(wù)測(cè)試

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class SeheduleTasks {
	private Logger logger = LoggerFactory.getLogger(SeheduleTasks.class);
	/**
	 * 1分鐘執(zhí)行一次
	 */
	@Scheduled(cron = "0 0/1 * * * ?")
	public void testTask() {
		logger.info("執(zhí)行定時(shí)任務(wù)>"+new Date());
	}
}

總結(jié):

服務(wù)啟動(dòng)的時(shí)候traceId是空的,這是正常的,因?yàn)檫€沒到攔截器這一層。

源碼點(diǎn)擊此處下載:

http://xiazai.jb51.net/202501/yuanma/springbootlog_jb51.rar

API 說明

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

到此這篇關(guān)于SpringBoot如何使用TraceId日志鏈路追蹤的文章就介紹到這了,更多相關(guān)SpringBoot TraceId日志鏈路追蹤內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • LongAdder原理及創(chuàng)建使用示例詳解

    LongAdder原理及創(chuàng)建使用示例詳解

    這篇文章主要為大家介紹了LongAdder原理及創(chuàng)建使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Java線程池ThreadPoolExecutor源碼深入分析

    Java線程池ThreadPoolExecutor源碼深入分析

    ThreadPoolExecutor作為java.util.concurrent包對(duì)外提供基礎(chǔ)實(shí)現(xiàn),以內(nèi)部線程池的形式對(duì)外提供管理任務(wù)執(zhí)行,線程調(diào)度,線程池管理等等服務(wù)
    2022-08-08
  • MyBatis分頁查詢與特殊字符處理方式

    MyBatis分頁查詢與特殊字符處理方式

    這篇文章主要介紹了MyBatis分頁查詢與特殊字符處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • java語言求解兔子問題代碼分析

    java語言求解兔子問題代碼分析

    這篇文章主要介紹了Java語言求解兔子問題代碼分析,具有一定借鑒價(jià)值,需要的朋友可以了解下。
    2017-12-12
  • Java中的AQS框架原理詳解

    Java中的AQS框架原理詳解

    這篇文章主要介紹了Java中的AQS框架原理詳解,AQS核心思想是,如果被請(qǐng)求的共享資源(state)空閑,則將當(dāng)前請(qǐng)求資源的線程設(shè)置為有效的工作線程,并且將共享資源設(shè)置為鎖定狀態(tài),需要的朋友可以參考下
    2023-12-12
  • java啟動(dòng)如何設(shè)置JAR包內(nèi)存大小

    java啟動(dòng)如何設(shè)置JAR包內(nèi)存大小

    這篇文章主要介紹了java啟動(dòng)如何設(shè)置JAR包內(nèi)存大小問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • Redis使用RedisTemplate模板類的常用操作方式

    Redis使用RedisTemplate模板類的常用操作方式

    這篇文章主要介紹了Redis使用RedisTemplate模板類的常用操作方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 有關(guān)tomcat內(nèi)存溢出的完美解決方法

    有關(guān)tomcat內(nèi)存溢出的完美解決方法

    下面小編就為大家?guī)硪黄嘘P(guān)tomcat內(nèi)存溢出的完美解決方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-05-05
  • Spring Web MVC和Hibernate的集成配置詳解

    Spring Web MVC和Hibernate的集成配置詳解

    這篇文章主要介紹了Spring Web MVC和Hibernate的集成配置詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2017-12-12
  • Java編寫實(shí)現(xiàn)多人聊天室

    Java編寫實(shí)現(xiàn)多人聊天室

    這篇文章主要為大家詳細(xì)介紹了Java編寫實(shí)現(xiàn)多人聊天室,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-09-09

最新評(píng)論