SpringBoot項目注入?traceId?追蹤整個請求的日志鏈路(過程詳解)
SpringBoot項目注入 traceId 來追蹤整個請求的日志鏈路,有了 traceId, 我們在排查問題的時候,可以迅速根據(jù) traceId 查找到相關(guān)請求的日志,特別是在生產(chǎn)環(huán)境的時候,用戶可能只提供一個錯誤截圖,我們作為開發(fā)人員想要更詳細的日志,這時候我們就可以直接根據(jù)用戶提供的 traceId 來查找用戶這次請求的整個日志,會方便很多。
springboot 單體項目沒辦法做到有 traceId 一樣的效果嗎?
即使是在單體應(yīng)用中,為請求添加一個唯一的traceId以追蹤整個請求鏈路的日志也是完全可行的。雖然Spring Cloud Sleuth等工具主要用于分布式系統(tǒng)中的鏈路追蹤,但在單體Spring Boot應(yīng)用中你仍然可以通過一些方法來實現(xiàn)類似的功能。下面是一個基本的方法來手動實現(xiàn)這樣的功能:
手動實現(xiàn) traceId 注入
1. 創(chuàng)建一個過濾器或攔截器:你可以創(chuàng)建一個Servlet過濾器或者Spring的攔截器,用于在每個請求到達時生成一個唯一的traceId(如果不存在的話),并將其存儲到當前線程的上下文中。
2. 使用MDC(Mapped Diagnostic Context):SLF4J提供了MDC工具,可以用來保存日志的上下文信息。你可以在過濾器或攔截器中將traceId放入MDC中,這樣所有的日志記錄器都可以自動包含這個ID。 示例代碼:
TraceIdFilter :
# 我這里用的是 jdk17, 所以 servlet 工具類被移動到了 jakarta 包下,如果你用的是比較老的 jdk, 可以刪除后重新導(dǎo)入 servlet 的類。
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.UUID;
@Compone
nt
public class TraceIdFilter extends OncePerRequestFilter {
private static final String TRACE_ID = "traceId";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String traceId = request.getHeader(TRACE_ID);
if (traceId == null) {
traceId = UUID.randomUUID().toString();
}
MDC.put(TRACE_ID, traceId);
try {
filterChain.doFilter(request, response);
} finally {
MDC.remove(TRACE_ID);
}
}
}3. 配置日志格式:確保你的日志配置文件(如logback.xml、log4j2.xml等)中包含了輸出traceId的配置。例如,在logback中,你可以這樣做:
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} - [%thread] %-5level %logger{36} - [%X{traceId}] - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>我的項目使用的是 logback-spring.xml ,我的文件完整內(nèi)容如下:
logback-spring.xml:
<configuration debug="true">
<!-- 引用Spring Boot全局配置文件中的日期格式配置項 -->
<springProperty scope="context" name="dateformat" source="logging.pattern.dateformat" defaultValue="yyyy-MM-dd HH:mm:ss.SSS"/>
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
<springProperty scope="context" name="LOG_FILE" source="logging.file.path"/>
<!-- 定義顏色編碼 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<!-- 定義日志輸出到控制臺的appender -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%clr(%d{${dateformat}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([%15.15t]){faint} [%-40.40logger{39}] [%X{traceId}] %clr(%msg%n){faint}%wEx</pattern>
</encoder>
</appender>
<!-- 開發(fā)環(huán)境配置 -->
<springProfile name="local | test">
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
<!-- 開發(fā)環(huán)境專屬的詳細日志 -->
<logger name="com.tylerzhong" level="TRACE" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
</springProfile>
<springProfile name="dev | prod">
<!-- 文件日志:輸出全部日志到文件 -->
<appender name="FILE_INFO_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}/${APP_NAME}.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>180</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{${dateformat}} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 錯誤日志:用于將錯誤日志輸出到獨立文件 -->
<appender name="FILE_ERROR_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}/error-${APP_NAME}.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}/error-${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>180</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{${dateformat}} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE_INFO_LOG" />
<appender-ref ref="FILE_ERROR_LOG" />
</root>
</springProfile>
</configuration>關(guān)于更具體的日志輸出配置,可以參考我的這篇文章《SpringBoot配置log4j輸出日志的案例講解》。
通過這種方式,你就可以在單體Spring Boot項目中為每個請求生成一個唯一的traceId,并通過日志輸出它,從而實現(xiàn)在處理請求期間的所有日志條目都能關(guān)聯(lián)起來的效果。這種方法不僅適用于單體應(yīng)用,也可以作為理解更復(fù)雜的分布式追蹤概念的基礎(chǔ)。
這樣配置之后,你在代碼中打印日志的時候就會攜帶上 traceId 了。
log.info("請求參數(shù): {}", arg);輸出示例:
2025-02-26 19:25:02.187 INFO [nio-8070-exec-2] [com.tylerzhong.web.config.LoggingAspect ] [ab85097a-6d31-44c3-bcfb-2bcc97b87ab9] 方法: TransferController.xxx(..)
當然,我們不止要將 traceId 輸出到控制臺,還需要將 traceId 返回給前端用戶,這樣用戶找我們排查問題的時候,只需要給一個 traceId 給我們即可。
所以在返回的包裝類中注入 traceId 返回給前端。
我的代碼如下:
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.slf4j.MDC;
@Getter
@NoArgsConstructor
public class Result<T> {
private int code;
private boolean flag;
private String desc;
private String cause;
private String traceId;
private T data;
public Result(int code, boolean flag, String desc) {
this.code = code;
this.flag = flag;
this.desc = desc;
}
public Result(int code, boolean flag, String desc, String traceId) {
this.code = code;
this.flag = flag;
this.desc = desc;
this.traceId = traceId;
}
public Result(int code, boolean flag, String desc, String cause, String traceId) {
this.code = code;
this.flag = flag;
this.desc = desc;
this.cause = cause;
this.traceId = traceId;
}
public Result(int code, boolean flag, String desc, T data, String traceId) {
this.code = code;
this.flag = flag;
this.desc = desc;
this.data = data;
this.traceId = traceId;
}
public static <T> Result<T> success() {
return new Result<>(0, true, "成功", MDC.get("traceId"));
}
public static <T> Result<T> success(T data) {
return new Result<>(0, true,"成功", data, MDC.get("traceId"));
}
public static <T> Result<T> fail(int code, String desc, String cause) {
return new Result<>(code, false, desc, cause, MDC.get("traceId"));
}
}返回給前端的失敗示例:
{
"code": 405,
"flag": false,
"desc": "請求方式不支持.",
"cause": "Request method 'POST' is not supported",
"traceId": "ebd77e99-361c-47b3-8569-2d321d011418",
"data": null
}返回給前端的成功示例:
{
"code": 0,
"flag": true,
"desc": "成功",
"cause": null,
"traceId": "07a3b99c-8cf8-45dc-a758-6c7636472cab",
"data": {
"count": 100,
"name": "張三"
}
}到此這篇關(guān)于SpringBoot項目注入 traceId 來追蹤整個請求的日志鏈路的文章就介紹到這了,更多相關(guān)SpringBoot traceId 日志鏈路內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot實現(xiàn)jar運行復(fù)制resources文件到指定的目錄(思路詳解)
這篇文章主要介紹了springboot實現(xiàn)jar運行復(fù)制resources文件到指定的目錄,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04
springboot整合cxf發(fā)布webservice以及調(diào)用的方法
這篇文章主要介紹了springboot整合cxf發(fā)布webservice以及調(diào)用的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08
Java并發(fā)教程之volatile關(guān)鍵字詳解
這篇文章主要給大家介紹了關(guān)于Java并發(fā)教程之volatile關(guān)鍵字的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Java具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-11-11
通過Java實現(xiàn)中文分詞與文本關(guān)鍵詞提取
這篇文章主要為大家詳細介紹了如何利用Java實現(xiàn)中文分詞以及文本關(guān)鍵詞提取功能,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習學習2023-06-06
mybatis中映射文件include標簽的應(yīng)用
這篇文章主要介紹了mybatis中映射文件include標簽的應(yīng)用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
SpringBoot集成MyBatis的分頁插件PageHelper實例代碼
這篇文章主要介紹了SpringBoot集成MyBatis的分頁插件PageHelper的相關(guān)操作,需要的朋友可以參考下2017-08-08

