SpringBoot項(xiàng)目注入?traceId?追蹤整個請求的日志鏈路(過程詳解)
SpringBoot項(xiàng)目注入 traceId 來追蹤整個請求的日志鏈路,有了 traceId, 我們在排查問題的時候,可以迅速根據(jù) traceId 查找到相關(guān)請求的日志,特別是在生產(chǎn)環(huán)境的時候,用戶可能只提供一個錯誤截圖,我們作為開發(fā)人員想要更詳細(xì)的日志,這時候我們就可以直接根據(jù)用戶提供的 traceId 來查找用戶這次請求的整個日志,會方便很多。
springboot 單體項(xiàng)目沒辦法做到有 traceId 一樣的效果嗎?
即使是在單體應(yīng)用中,為請求添加一個唯一的traceId以追蹤整個請求鏈路的日志也是完全可行的。雖然Spring Cloud Sleuth等工具主要用于分布式系統(tǒng)中的鏈路追蹤,但在單體Spring Boot應(yīng)用中你仍然可以通過一些方法來實(shí)現(xiàn)類似的功能。下面是一個基本的方法來手動實(shí)現(xiàn)這樣的功能:
手動實(shí)現(xiàn) traceId 注入
1. 創(chuàng)建一個過濾器或攔截器:你可以創(chuàng)建一個Servlet過濾器或者Spring的攔截器,用于在每個請求到達(dá)時生成一個唯一的traceId(如果不存在的話),并將其存儲到當(dāng)前線程的上下文中。
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>
我的項(xiàng)目使用的是 logback-spring.xml ,我的文件完整內(nèi)容如下:
logback-spring.xml:
<configuration debug="true"> <!-- 引用Spring Boot全局配置文件中的日期格式配置項(xiàng) --> <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)境專屬的詳細(xì)日志 --> <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> <!-- 錯誤日志:用于將錯誤日志輸出到獨(dú)立文件 --> <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項(xiàng)目中為每個請求生成一個唯一的traceId,并通過日志輸出它,從而實(shí)現(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(..)
當(dāng)然,我們不止要將 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項(xiàng)目注入 traceId 來追蹤整個請求的日志鏈路的文章就介紹到這了,更多相關(guān)SpringBoot traceId 日志鏈路內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java集合Iterator迭代的實(shí)現(xiàn)方法
這篇文章主要介紹了Java集合Iterator迭代接口的實(shí)現(xiàn)方法,非常不錯,具有參考借鑒家,對Java 結(jié)合iterator知識感興趣的朋友一起看看吧2016-08-08springboot實(shí)現(xiàn)jar運(yùn)行復(fù)制resources文件到指定的目錄(思路詳解)
這篇文章主要介紹了springboot實(shí)現(xiàn)jar運(yùn)行復(fù)制resources文件到指定的目錄,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04springboot整合cxf發(fā)布webservice以及調(diào)用的方法
這篇文章主要介紹了springboot整合cxf發(fā)布webservice以及調(diào)用的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08Java并發(fā)教程之volatile關(guān)鍵字詳解
這篇文章主要給大家介紹了關(guān)于Java并發(fā)教程之volatile關(guān)鍵字的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11通過Java實(shí)現(xiàn)中文分詞與文本關(guān)鍵詞提取
這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)中文分詞以及文本關(guān)鍵詞提取功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)學(xué)習(xí)2023-06-06mybatis中映射文件include標(biāo)簽的應(yīng)用
這篇文章主要介紹了mybatis中映射文件include標(biāo)簽的應(yīng)用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11SpringBoot集成MyBatis的分頁插件PageHelper實(shí)例代碼
這篇文章主要介紹了SpringBoot集成MyBatis的分頁插件PageHelper的相關(guān)操作,需要的朋友可以參考下2017-08-08java正則表達(dá)式校驗(yàn)日期格式實(shí)例代碼
如果使用得當(dāng),正則表達(dá)式是匹配各種模式的強(qiáng)大工具,下面這篇文章主要給大家介紹了關(guān)于java正則表達(dá)式校驗(yàn)日期格式的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05