0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

手動(dòng)實(shí)現(xiàn)SpringBoot日志鏈路追蹤

jf_ro2CN3Fa ? 來(lái)源:csdn ? 作者:CSDN ? 2022-12-15 15:04 ? 次閱讀

  • 前言
  • 正文
3abca07e-7c3d-11ed-8abf-dac502259ad0.jpg

前言

從文章標(biāo)題就知道,這篇文章是介紹些什么。

這是我一位朋友的問(wèn)題反饋:

3b060c64-7c3d-11ed-8abf-dac502259ad0.png

好像是的,確實(shí)這種現(xiàn)象是普遍存在的。

有時(shí)候一個(gè)業(yè)務(wù)調(diào)用鏈場(chǎng)景,很長(zhǎng),調(diào)了各種各樣的方法,看日志的時(shí)候,各個(gè)接口的日志穿插,確實(shí)讓人頭大。

模糊匹配搜索日志能解決嗎? 能解決一點(diǎn)點(diǎn)。 但是不能完全呈現(xiàn)出整個(gè)鏈路相關(guān)的日志。

那要做到方便,很顯然,我們需要的是把同一次的業(yè)務(wù)調(diào)用鏈上的日志串起來(lái)。

什么效果? 先看一個(gè)實(shí)現(xiàn)后的效果圖:

3b14d7d0-7c3d-11ed-8abf-dac502259ad0.png

這樣下來(lái),我們?cè)倥浜夏:ヅ洳檎胰罩?,效果不就剛剛的了?/p>

cat-ninfo.log|grep"a415ad50dbf84e99b1b56a31aacd209c"

或者

grep-10'a415ad50dbf84e99b1b56a31aacd209c'info.log(10是指上下10行)

不多說(shuō),開(kāi)整。

基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 視頻教程:https://doc.iocoder.cn/video/

正文

慣例,先看一眼這次實(shí)戰(zhàn)最終工程的結(jié)構(gòu):

3b58db1a-7c3d-11ed-8abf-dac502259ad0.png

①pom.xml 依賴

<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
dependency>

<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.10version>
dependency>
dependencies>

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


<configurationdebug="false">

<propertyname="log"value="D:/test/log"/>

<appendername="console"class="ch.qos.logback.core.ConsoleAppender">
<encoderclass="ch.qos.logback.classic.encoder.PatternLayoutEncoder">

<pattern>[%X{TRACE_ID}]%d{yyyy-MM-ddHHss.SSS}[%thread]%-5level%logger{50}-%msg%npattern>
encoder>
appender>

<appendername="file"class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

<FileNamePattern>${log}/%d{yyyy-MM-dd}.logFileNamePattern>

<MaxHistory>30MaxHistory>
rollingPolicy>
<encoderclass="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>[%X{TRACE_ID}]%d{yyyy-MM-ddHHss.SSS}[%thread]%-5level%logger{50}-%msg%npattern>
encoder>

<triggeringPolicyclass="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MBMaxFileSize>
triggeringPolicy>
appender>


<rootlevel="INFO">
<appender-refref="console"/>
<appender-refref="file"/>
root>
configuration>

application.yml

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

③自定義日志攔截器 LogInterceptor.java

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

importorg.slf4j.MDC;
importorg.springframework.lang.Nullable;
importorg.springframework.util.StringUtils;
importorg.springframework.web.servlet.HandlerInterceptor;

importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjava.util.UUID;

/**
*@Author:JCccc
*@Date:2022-5-3010:45
*@Description:
*/
publicclassLogInterceptorimplementsHandlerInterceptor{

privatestaticfinalStringTRACE_ID="TRACE_ID";

@Override
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){
Stringtid=UUID.randomUUID().toString().replace("-","");
//可以考慮讓客戶端傳入鏈路ID,但需保證一定的復(fù)雜度唯一性;如果沒(méi)使用默認(rèn)UUID自動(dòng)生成
if(!StringUtils.isEmpty(request.getHeader("TRACE_ID"))){
tid=request.getHeader("TRACE_ID");
}
MDC.put(TRACE_ID,tid);
returntrue;
}

@Override
publicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,
@NullableExceptionex){
MDC.remove(TRACE_ID);
}

}

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

WebConfigurerAdapter.java 添加攔截器

importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;
importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
*@Author:JCccc
*@Date:2022-5-3010:47
*@Description:
*/
@Configuration
publicclassWebConfigurerAdapterimplementsWebMvcConfigurer{
@Bean
publicLogInterceptorlogInterceptor(){
returnnewLogInterceptor();
}

@Override
publicvoidaddInterceptors(InterceptorRegistryregistry){
registry.addInterceptor(logInterceptor());
//可以具體制定哪些需要攔截,哪些不攔截,其實(shí)也可以使用自定義注解更靈活完成
//.addPathPatterns("/**")
//.excludePathPatterns("/testxx.html");
}
}

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

到這時(shí)候,其實(shí)已經(jīng)完成,就是這么簡(jiǎn)單。

我們寫個(gè)測(cè)試接口,看下效果:

@PostMapping("doTest")
publicStringdoTest(@RequestParam("name")Stringname)throwsInterruptedException{
log.info("入?yún)ame={}",name);
testTrace();
log.info("調(diào)用結(jié)束name={}",name);
return"Hello,"+name;
}
privatevoidtestTrace(){
log.info("這是一行info日志");
log.error("這是一行error日志");
testTrace2();
}
privatevoidtestTrace2(){
log.info("這也是一行info日志");

}

效果(OK的):

3b7c2340-7c3d-11ed-8abf-dac502259ad0.png

還沒(méi)完。

接下來(lái)看一個(gè)場(chǎng)景, 使用子線程的場(chǎng)景:

故意寫一個(gè)異步線程,加入這個(gè)調(diào)用里面:

3bb25adc-7c3d-11ed-8abf-dac502259ad0.png

再次執(zhí)行看開(kāi)效果,顯然子線程丟失了trackId:

3bc97a14-7c3d-11ed-8abf-dac502259ad0.png

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

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

importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.scheduling.annotation.EnableAsync;
importjava.util.concurrent.Executor;

/**
*@Author:JCccc
*@Date:2022-5-3011:07
*@Description:
*/
@Configuration
@EnableAsync
publicclassThreadPoolConfig{
/**
*聲明一個(gè)線程池
*
*@return執(zhí)行器
*/
@Bean("MyExecutor")
publicExecutorasyncExecutor(){
MyThreadPoolTaskExecutorexecutor=newMyThreadPoolTaskExecutor();
//核心線程數(shù)5:線程池創(chuàng)建時(shí)候初始化的線程數(shù)
executor.setCorePoolSize(5);
//最大線程數(shù)5:線程池最大的線程數(shù),只有在緩沖隊(duì)列滿了之后才會(huì)申請(qǐng)超過(guò)核心線程數(shù)的線程
executor.setMaxPoolSize(5);
//緩沖隊(duì)列500:用來(lái)緩沖執(zhí)行任務(wù)的隊(duì)列
executor.setQueueCapacity(500);
//允許線程的空閑時(shí)間60秒:當(dāng)超過(guò)了核心線程出之外的線程在空閑時(shí)間到達(dá)之后會(huì)被銷毀
executor.setKeepAliveSeconds(60);
//線程池名的前綴:設(shè)置好了之后可以方便我們定位處理任務(wù)所在的線程池
executor.setThreadNamePrefix("asyncJCccc");
executor.initialize();
returnexecutor;
}
}

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

importorg.slf4j.MDC;
importorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

importjava.util.concurrent.Callable;
importjava.util.concurrent.Future;

/**
*@Author:JCccc
*@Date:2022-5-3011:13
*@Description:
*/
publicfinalclassMyThreadPoolTaskExecutorextendsThreadPoolTaskExecutor{
publicMyThreadPoolTaskExecutor(){
super();
}

@Override
publicvoidexecute(Runnabletask){
super.execute(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));
}


@Override
publicFuturesubmit(Callabletask){
returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));
}

@Override
publicFuturesubmit(Runnabletask){
returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));
}
}

③ThreadMdcUtil.java

importorg.slf4j.MDC;

importjava.util.Map;
importjava.util.UUID;
importjava.util.concurrent.Callable;

/**
*@Author:JCccc
*@Date:2022-5-3011:14
*@Description:
*/
publicfinalclassThreadMdcUtil{
privatestaticfinalStringTRACE_ID="TRACE_ID";

//獲取唯一性標(biāo)識(shí)
publicstaticStringgenerateTraceId(){
returnUUID.randomUUID().toString();
}

publicstaticvoidsetTraceIdIfAbsent(){
if(MDC.get(TRACE_ID)==null){
MDC.put(TRACE_ID,generateTraceId());
}
}

/**
*用于父線程向線程池中提交任務(wù)時(shí),將自身MDC中的數(shù)據(jù)復(fù)制給子線程
*
*@paramcallable
*@paramcontext
*@param
*@return
*/
publicstaticCallablewrap(finalCallablecallable,finalMapcontext){
return()->{
if(context==null){
MDC.clear();
}else{
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try{
returncallable.call();
}finally{
MDC.clear();
}
};
}

/**
*用于父線程向線程池中提交任務(wù)時(shí),將自身MDC中的數(shù)據(jù)復(fù)制給子線程
*
*@paramrunnable
*@paramcontext
*@return
*/
publicstaticRunnablewrap(finalRunnablerunnable,finalMapcontext){
return()->{
if(context==null){
MDC.clear();
}else{
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try{
runnable.run();
}finally{
MDC.clear();
}
};
}
}

OK,重啟服務(wù),再看看效果:

3be73004-7c3d-11ed-8abf-dac502259ad0.png

可以看的,子線程的日志也被串起來(lái)了。



審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • spring
    +關(guān)注

    關(guān)注

    0

    文章

    335

    瀏覽量

    14277
  • 日志
    +關(guān)注

    關(guān)注

    0

    文章

    132

    瀏覽量

    10616
  • SpringBoot
    +關(guān)注

    關(guān)注

    0

    文章

    173

    瀏覽量

    153

原文標(biāo)題:手動(dòng)實(shí)現(xiàn) SpringBoot 日志鏈路追蹤,無(wú)需引入組件,日志定位更方便!

文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    IR615如何實(shí)現(xiàn)VPN備份?

    目的:IR615的備份(WAN為主、Wi-Fi做STA為從),當(dāng)VPN建好后,WAN
    發(fā)表于 07-25 08:27

    IG網(wǎng)關(guān)產(chǎn)品實(shí)現(xiàn)備份的方法

    的-接口備份模塊。 3、選擇主接口以及備份接口等數(shù)據(jù)參數(shù);設(shè)定完成后添加即生效。(Dot11radio 1代表WLAN接口) 通過(guò)這樣設(shè)定即可實(shí)現(xiàn)備份的基礎(chǔ)功能設(shè)定:
    發(fā)表于 07-24 08:25

    奇怪!應(yīng)用的日志呢??

    1. 問(wèn)題回顧 問(wèn)題背景 是在進(jìn)行中臺(tái)應(yīng)用中間件遷移過(guò)程中,發(fā)現(xiàn)存在 項(xiàng)目啟動(dòng)失敗 或者 項(xiàng)目正常啟動(dòng) (jsf正常掛載并正常運(yùn)行,mq正常發(fā)送和消費(fèi))但是 無(wú)任何日志打印 現(xiàn)象。 更奇怪 的是不打
    的頭像 發(fā)表于 06-11 10:48 ?264次閱讀
    奇怪!應(yīng)用的<b class='flag-5'>日志</b>呢??

    如何識(shí)別光纖問(wèn)題?

    光纖網(wǎng)絡(luò)專為連續(xù)運(yùn)行而設(shè)計(jì)。通常,光纖網(wǎng)絡(luò)以最佳效率運(yùn)行。然而,網(wǎng)絡(luò)中有時(shí)會(huì)遇到光纖問(wèn)題。由于光纖網(wǎng)絡(luò)的復(fù)雜性,這些光纖問(wèn)題很難識(shí)別。然而,為了確保光纖網(wǎng)絡(luò)的最佳性能,識(shí)別和解
    的頭像 發(fā)表于 06-11 10:12 ?377次閱讀

    加法進(jìn)位手動(dòng)約束

    在激光雷達(dá)中,使用FPGA實(shí)現(xiàn)TDC時(shí)需要手動(dòng)約束進(jìn)位的位置。這里簡(jiǎn)單記錄下。 在outflow下會(huì)生成一個(gè).qplace文件?。用于指示布線的各個(gè)原語(yǔ)資源的分布位置 。 它的內(nèi)容主是 是原語(yǔ)
    的頭像 發(fā)表于 05-20 11:38 ?1178次閱讀
    加法進(jìn)位<b class='flag-5'>鏈</b>的<b class='flag-5'>手動(dòng)</b>約束

    如何辨別光纖的好壞?

    辨別光纖的好壞,通常涉及一系列測(cè)試和檢查步驟。以下是一些主要的方法: 光學(xué)連通性測(cè)試:檢查光纖的光學(xué)連通性。當(dāng)輸出端測(cè)到的光功率與輸入端實(shí)際輸入的光功率的比值小于一定的數(shù)值時(shí),
    的頭像 發(fā)表于 04-11 11:48 ?809次閱讀

    永久、信道測(cè)試的區(qū)別

    永久測(cè)試和信道測(cè)試是網(wǎng)絡(luò)和通信領(lǐng)域中兩個(gè)不同的概念,它們通常用于確保網(wǎng)絡(luò)和通信系統(tǒng)的可靠性和性能。 永久測(cè)試(Permanent Link Testing): 永久
    的頭像 發(fā)表于 03-25 10:59 ?2039次閱讀

    【嵌入式SD NAND】基于FATFS/Littlefs文件系統(tǒng)的日志框架實(shí)現(xiàn)

    文章目錄【嵌入式】基于FATFS/Littlefs文件系統(tǒng)的日志框架實(shí)現(xiàn)1.概述2.設(shè)計(jì)概要3.設(shè)計(jì)實(shí)現(xiàn)3.1初始化`init`3.2日志寫入`write`3.3
    的頭像 發(fā)表于 03-14 18:12 ?1065次閱讀
    【嵌入式SD NAND】基于FATFS/Littlefs文件系統(tǒng)的<b class='flag-5'>日志</b>框架<b class='flag-5'>實(shí)現(xiàn)</b>

    介紹五款好用的日志管理工具

    日志管理是現(xiàn)代IT環(huán)境中不可或缺的一部分,它有助于監(jiān)視和維護(hù)應(yīng)用程序、系統(tǒng)和網(wǎng)絡(luò)的正常運(yùn)行,幫助診斷問(wèn)題,追蹤事件以及確保安全性。
    的頭像 發(fā)表于 12-21 14:24 ?1109次閱讀
    介紹五款好用的<b class='flag-5'>日志</b>管理工具

    使用go語(yǔ)言實(shí)現(xiàn)一個(gè)grpc攔截器

    在開(kāi)發(fā)grpc服務(wù)時(shí),我們經(jīng)常會(huì)遇到一些通用的需求,比如:日志、追蹤、鑒權(quán)等。這些需求可以通過(guò)grpc攔截器來(lái)實(shí)現(xiàn)。本文使用go語(yǔ)言來(lái)
    的頭像 發(fā)表于 12-18 10:13 ?574次閱讀
    使用go語(yǔ)言<b class='flag-5'>實(shí)現(xiàn)</b>一個(gè)grpc攔截器

    SpringBoot實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源

    最近在做業(yè)務(wù)需求時(shí),需要從不同的數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)然后寫入到當(dāng)前數(shù)據(jù)庫(kù)中,因此涉及到切換數(shù)據(jù)源問(wèn)題。本來(lái)想著使用Mybatis-plus中提供的動(dòng)態(tài)數(shù)據(jù)源SpringBoot的starter:dynamic-datasource-spring-boot-starter來(lái)實(shí)現(xiàn)
    的頭像 發(fā)表于 12-08 10:53 ?923次閱讀
    <b class='flag-5'>SpringBoot</b><b class='flag-5'>實(shí)現(xiàn)</b>動(dòng)態(tài)切換數(shù)據(jù)源

    狀態(tài)路由協(xié)議的基本概念和原理解析

    狀態(tài)路由選擇協(xié)議又被稱為最短路徑優(yōu)先協(xié)議,它基SPF(shortest path first )算法。他比距離矢量協(xié)議復(fù)雜的多。路由器的狀態(tài)的信息稱為
    的頭像 發(fā)表于 12-07 09:52 ?2452次閱讀
    <b class='flag-5'>鏈</b><b class='flag-5'>路</b>狀態(tài)路由協(xié)議的基本概念和原理解析

    一個(gè)注解搞定SpringBoot接口防刷

    技術(shù)要點(diǎn):springboot的基本知識(shí),redis基本操作,
    的頭像 發(fā)表于 11-28 10:46 ?369次閱讀

    什么是聚合?怎么配置聚合?聚合簡(jiǎn)介

    以太網(wǎng)聚合Eth-Trunk簡(jiǎn)稱聚合,它通過(guò)將多條以太網(wǎng)物理捆綁在一起成為一條邏輯
    的頭像 發(fā)表于 11-28 09:24 ?2968次閱讀
    什么是<b class='flag-5'>鏈</b><b class='flag-5'>路</b>聚合?怎么配置<b class='flag-5'>鏈</b><b class='flag-5'>路</b>聚合?<b class='flag-5'>鏈</b><b class='flag-5'>路</b>聚合簡(jiǎn)介

    C++異步日志實(shí)踐

    一個(gè)高效可拓展的異步C++日志庫(kù):RING LOG,本文分享了了其設(shè)計(jì)方案與技術(shù)原理等內(nèi)容 導(dǎo)論 同步日志與缺點(diǎn) 傳統(tǒng)的日志也叫同步日志,每次調(diào)用一次打印
    的頭像 發(fā)表于 11-09 10:29 ?592次閱讀
    C++異步<b class='flag-5'>日志</b>實(shí)踐