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

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

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

項目場景:

??有時候一個業(yè)務調用鏈場景,很長,調了各種各樣的方法,看日志的時候,各個接口的日志穿插,確實讓人頭大。為了解決這個痛點,就使用了TraceId,根據TraceId關鍵字進入服務器查詢日志中是否有這個TraceId,這樣就把同一次的業(yè)務調用鏈上的日志串起來了。

實現步驟

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 (簡單配置下)

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

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--日志存儲路徑-->
    <property name="log" value="D:/test/log" />
    <!-- 控制臺輸出 -->
    <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>
            <!--保留天數-->
            <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>
    <!-- 日志輸出級別 -->
    <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提供的一個支持動態(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,但需保證一定的復雜度唯一性;如果沒使用默認UUID自動生成
        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) {
        // 請求處理完成后,清除MDC中的traceId,以免造成內存泄漏
        MDC.remove(traceId);
    }
}

5、WebConfigurerAdapter.java 添加攔截器

ps: 其實這個攔截的部分改為使用自定義注解+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);
		//可以具體制定哪些需要攔截,哪些不攔截,其實也可以使用自定義注解更靈活完成
//                .addPathPatterns("/**")
//                .excludePathPatterns("/testxx.html");
	}
}

6、測試接口

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 = "測試接口")
@RequestMapping("/test")
@Slf4j
public class TestController {
	@RequestMapping(value = "/log", method = RequestMethod.GET)
	@ApiOperation(value = "測試日志")
	public String sign() {
		log.info("這是一行info日志");
		log.error("這是一行error日志");
		return "success";
	}
}

結果:

異步場景:

使用線程的場景,寫一個異步線程,加入這個調用里面。再次執(zhí)行看開效果,我們會發(fā)現顯然子線程丟失了trackId。

所以我們需要針對子線程使用情形,做調整,思路:將父線程的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";
    // 獲取唯一性標識
    public static String generateTraceId() {
        return UUID.randomUUID().toString().replace("-", "");
    }
    public static void setTraceIdIfAbsent() {
        if (MDC.get(traceId) == null) {
            MDC.put(traceId, generateTraceId());
        }
    }
    /**
     * 用于父線程向線程池中提交任務時,將自身MDC中的數據復制給子線程
     *
     * @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();
            }
        };
    }
    /**
     * 用于父線程向線程池中提交任務時,將自身MDC中的數據復制給子線程
     *
     * @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 {
    /**
     * 聲明一個線程池
     */
    @Bean("taskExecutor")
    public Executor taskExecutor() {
        MyThreadPoolTaskExecutor executor = new MyThreadPoolTaskExecutor();
        //核心線程數5:線程池創(chuàng)建時候初始化的線程數
        executor.setCorePoolSize(5);
        //最大線程數5:線程池最大的線程數,只有在緩沖隊列滿了之后才會申請超過核心線程數的線程
        executor.setMaxPoolSize(5);
        //緩沖隊列500:用來緩沖執(zhí)行任務的隊列
        executor.setQueueCapacity(500);
        //允許線程的空閑時間60秒:當超過了核心線程出之外的線程在空閑時間到達之后會被銷毀
        executor.setKeepAliveSeconds(60);
        //線程池名的前綴:設置好了之后可以方便我們定位處理任務所在的線程池
        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;
/**
 * 測試Service
 */
@Service("testService")
@Slf4j
public class TestService {
	/**
	 * 異步操作測試
	 */
	@Async("taskExecutor")
	public void asyncTest() {
		try {
			log.info("模擬異步開始......");
			Thread.sleep(3000);
			log.info("模擬異步結束......");
		} catch (InterruptedException e) {
			log.error("異步操作出錯:"+e);
		}
	}
}

5、測試接口

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 = "測試接口")
@RequestMapping("/test")
@Slf4j
public class TestController {
	@Resource
	private TestService testService;
	@RequestMapping(value = "/log", method = RequestMethod.GET)
	@ApiOperation(value = "測試日志")
	public String sign() {
		log.info("這是一行info日志");
		log.error("這是一行error日志");
		//異步操作測試
		testService.asyncTest();
		return "success";
	}
}

結果:

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

定時任務:

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

1、創(chuàng)建個定時任務線程池

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;
/**
 * 定時任務線程池
 */
@EnableScheduling
@Configuration
public class SeheduleConfig implements SchedulingConfigurer{
	@Override
	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
		taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
	}
}

2、創(chuàng)建個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   //定義一個切面
@Configuration
public class SeheduleTaskAspect {
    // 定義定時任務切點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í)行定時任務方法
	        joinPoint.proceed();
		} finally {
			//請求處理完成后,清除MDC中的traceId,以免造成內存泄漏
			MDC.remove("traceId");
		}
    }
}

3、創(chuàng)建定時任務測試

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í)行定時任務>"+new Date());
	}
}

總結:

服務啟動的時候traceId是空的,這是正常的,因為還沒到攔截器這一層。

源碼點擊此處下載:

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

API 說明

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

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

相關文章

  • 如何使用JavaCV進行圖像灰度化處理

    如何使用JavaCV進行圖像灰度化處理

    在計算機視覺和圖像處理領域,圖像灰度化是一項基礎且重要的任務,它將彩色圖像轉換為灰度圖像,JavaCV 是一個強大的開源庫,它提供了對各種計算機視覺算法和圖像處理操作的支持,本文將詳細介紹如何使用 JavaCV 進行圖像灰度化處理,需要的朋友可以參考下
    2024-10-10
  • Spring的循環(huán)依賴、三級緩存解決方案源碼詳細解析

    Spring的循環(huán)依賴、三級緩存解決方案源碼詳細解析

    這篇文章主要介紹了Spring的循環(huán)依賴、三級緩存解決方案源碼詳細解析,在Spring中,由于IOC的控制反轉,創(chuàng)建對象不再是簡單的new出來,而是交給Spring去創(chuàng)建,會經歷一系列Bean的生命周期才創(chuàng)建出相應的對象,需要的朋友可以參考下
    2024-01-01
  • 淺析Java的Hibernate框架中的緩存和延遲加載機制

    淺析Java的Hibernate框架中的緩存和延遲加載機制

    這篇文章主要介紹了Java的Hibernate框架中的緩存和延遲加載機制,Hibernate是注明的Java下SSH三大web開發(fā)框架之一,需要的朋友可以參考下
    2015-11-11
  • IntelliJ IDEA進行遠程調試(Remote Debugging)的操作教程

    IntelliJ IDEA進行遠程調試(Remote Debugging)的操作教程

    遠程調試(Remote Debugging)是指在本地開發(fā)環(huán)境(如 IntelliJ IDEA)中,連接并調試運行在遠程機器(如測試服務器、預發(fā)環(huán)境、生產服務器、Docker 容器、Kubernetes Pod 等)上的 Java 應用程序,本文給大家介紹了IntelliJ IDEA進行遠程調試的操作教程
    2025-11-11
  • java如何通過Kerberos認證方式連接hive

    java如何通過Kerberos認證方式連接hive

    該文主要介紹了如何在數據源管理功能中適配不同數據源(如MySQL、PostgreSQL和Hive),特別是如何在SpringBoot3框架下通過Kerberos認證與Hive進行安全交互,文章詳細描述了Kerberos認證過程,包括配置krb5.conf和keytab文件、處理Hadoop和Hive版本兼容性問題
    2025-02-02
  • SpringBoot設置接口超時的方法小結

    SpringBoot設置接口超時的方法小結

    這篇文章主要介紹了SpringBoot設置接口超時的方法小結,包括配置文件,config配置類及相關示例代碼,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09
  • java實現圖書館管理系統(tǒng)

    java實現圖書館管理系統(tǒng)

    這篇文章主要為大家詳細介紹了java實現圖書館管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • 通過簡易例子講解Java回調機制

    通過簡易例子講解Java回調機制

    這篇文章主要介紹了通過簡易例子講解Java回調機制,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-11-11
  • Java 8系列之Stream中萬能的reduce用法說明

    Java 8系列之Stream中萬能的reduce用法說明

    這篇文章主要介紹了Java 8系列之Stream中萬能的reduce用法說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • Java利用SPI實現解耦的示例詳解

    Java利用SPI實現解耦的示例詳解

    SPI的全稱是服務提供接口,可以用其來啟動框架的擴展和替換組件。本文將利用SPI實現解耦,文中的示例代碼講解詳細,具有一定的借鑒價值,需要的可以參考一下
    2023-04-04

最新評論

宿迁市| 封开县| 分宜县| 贵南县| 扶余县| 安康市| 务川| 甘孜县| 太湖县| 进贤县| 谢通门县| 海城市| 永福县| 新竹县| 钦州市| 华坪县| 封丘县| 湖南省| 景宁| 斗六市| 林西县| 成安县| 吴堡县| 乌苏市| 宜兰县| 邯郸市| 西青区| 沙河市| 盐池县| 互助| 开平市| 丰城市| 灵丘县| 台东县| 东方市| 开封县| 靖宇县| 息烽县| 巴塘县| 青阳县| 志丹县|