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

SpringBoot使用TraceId進行日志鏈路追蹤的實現(xiàn)步驟

 更新時間:2024年11月12日 09:43:23   作者:濤哥是個大帥比  
有時候一個業(yè)務(wù)調(diào)用鏈場景,很長,調(diào)了各種各樣的方法,看日志的時候,各個接口的日志穿插,確實讓人頭大,所以為了解決這個問題,本文給大家介紹了SpringBoot使用TraceId進行日志鏈路追蹤的實現(xiàn)步驟,需要的朋友可以參考下

項目場景:

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

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

關(guān)鍵代碼:[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>
            <!--保留天數(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>
 
    <!-- 日志輸出級別 -->
    <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,但需保證一定的復(fù)雜度唯一性;如果沒使用默認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,以免造成內(nèi)存泄漏
        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";
	}
}

結(jié)果:

異步場景:

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

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

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

總結(jié):

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

以上就是SpringBoot使用TraceId進行日志鏈路追蹤的實現(xiàn)步驟的詳細內(nèi)容,更多關(guān)于SpringBoot TraceId日志鏈路追蹤的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 認識Java中的Stub與StubQueue

    認識Java中的Stub與StubQueue

    StubQueue是用來保存生成的本地代碼的Stub隊列,隊列每一個元素對應(yīng)一個InterpreterCodelet對象,InterpreterCodelet對象繼承自抽象基類Stub,下面我們介紹一下StubQueue類及相關(guān)類Stub、InterpreterCodelet類和CodeletMark類。需要的的下伙伴可以參考下面文字內(nèi)容
    2021-09-09
  • Mybatis 高級用法和tk.mybatis使用示例詳解

    Mybatis 高級用法和tk.mybatis使用示例詳解

    tkmybatis 是對底層 sql 進行了抽象封裝,不需要考慮 sql 怎么寫,只需要按照邏輯思維,遵循 tkmybatis 的語法即可實現(xiàn)數(shù)據(jù)庫操作,這篇文章主要介紹了Mybatis 高級用法和tk.mybatis使用,需要的朋友可以參考下
    2024-05-05
  • 深入理解Java設(shè)計模式之訪問者模式

    深入理解Java設(shè)計模式之訪問者模式

    這篇文章主要介紹了JAVA設(shè)計模式之訪問者模式的的相關(guān)資料,文中示例代碼非常詳細,供大家參考和學(xué)習(xí),感興趣的朋友可以了解
    2021-11-11
  • Java排序的那些事之sort方法的使用詳解

    Java排序的那些事之sort方法的使用詳解

    sort方法用于對數(shù)組的元素進行排序。排序順序可以是字母或數(shù)字,并按升序或降序。默認排序順序為按字母升序,當數(shù)字是按字母順序排列時"40"將排在"5"前面。使用數(shù)字排序,你必須通過一個函數(shù)作為參數(shù)來調(diào)用。這些說起來可能很難理解,你可以通過本篇文章進一步了解它
    2021-09-09
  • SpringBoot JPA實現(xiàn)查詢多值

    SpringBoot JPA實現(xiàn)查詢多值

    這篇文章主要為大家詳細介紹了SpringBoot JPA實現(xiàn)查詢多值,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • Java踩坑記錄之Arrays.AsList

    Java踩坑記錄之Arrays.AsList

    這篇文章主要給大家介紹了關(guān)于Java踩坑記錄之Arrays.AsList的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • 關(guān)于springboot打包目錄全解析

    關(guān)于springboot打包目錄全解析

    這篇文章主要介紹了springboot打包目錄解析,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • Java 將PPT幻燈片轉(zhuǎn)為HTML文件的實現(xiàn)思路

    Java 將PPT幻燈片轉(zhuǎn)為HTML文件的實現(xiàn)思路

    本文以Java程序代碼為例展示如何通過格式轉(zhuǎn)換的方式將PPT幻燈片文檔轉(zhuǎn)為HTML文件,本文通過實例代碼圖文相結(jié)合給大家分享實現(xiàn)思路,需要的朋友參考下吧
    2021-06-06
  • MyBatis獲取自動生成的(主)鍵值的方法

    MyBatis獲取自動生成的(主)鍵值的方法

    本文主要介紹了MyBatis獲取自動生成的(主)鍵值的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Java常量池知識點總結(jié)

    Java常量池知識點總結(jié)

    本篇文章給大家通過理論原理等方便徹底分析了Java常量池的相關(guān)知識,有興趣的朋友閱讀學(xué)習(xí)下吧。
    2017-12-12

最新評論