SpringBoot项目注入 traceId 来追踪整个请求的日志链路,有了 traceId, 我们在排查问题的时候,可以迅速根据 traceId 查找到相关请求的日志,特别是在生产环境的时候,用户可能只提供一个错误截图,我们作为开发人员想要更详细的日志,这时候我们就可以直接根据用户提供的 traceId 来查找用户这次请求的整个日志,会方便很多。
springboot 单体项目没办法做到有 traceId 一样的效果吗?
即使是在单体应用中,为请求添加一个唯一的traceId以追踪整个请求链路的日志也是完全可行的。虽然Spring Cloud Sleuth等工具主要用于分布式系统中的链路追踪,但在单体Spring Boot应用中你仍然可以通过一些方法来实现类似的功能。下面是一个基本的方法来手动实现这样的功能:
手动实现 traceId 注入
1. 创建一个过滤器或拦截器:你可以创建一个Servlet过滤器或者Spring的拦截器,用于在每个请求到达时生成一个唯一的traceId(如果不存在的话),并将其存储到当前线程的上下文中。
2. 使用MDC(Mapped Diagnostic Context):SLF4J提供了MDC工具,可以用来保存日志的上下文信息。你可以在过滤器或拦截器中将traceId放入MDC中,这样所有的日志记录器都可以自动包含这个ID。 示例代码:
TraceIdFilter :
# 我这里用的是 jdk17, 所以 servlet 工具类被移动到了 jakarta 包下,如果你用的是比较老的 jdk, 可以删除后重新导入 servlet 的类。 import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.hphpttp.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 ,我的文件完整内容如下:
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> <!-- 开发环境配置 --> <springProfile name="local | test"> <root level="DEBUG"> <appender-ref ref="STDOUT" /> </root> <!-- 开发环境专属的详细日志 --> <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</onMismaphptch> </filter> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> js <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> python <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>
关于更具体的日志输出配置,可以参考我的这篇文章《SpringBoot配置log4j输出日志的案例讲解》。
通过这种方式,你就可以在单体Spring Boot项目中为每个请求生成一个唯一的traceId,并通过日志输出它,从而实现在处理请求期间的所有日志条目都能关联起来的效果。这种方法不仅适用于单体应用,也可以作为理解更复杂的分布式追踪概念的基础。
这样配置之后,你在代码中打印日志的时候就会携带上 traceId 了。
log.info("请求参数: {}", 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.NoArgsCohttp://www.devze.comnstructor; 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": "张三" } }
到此这篇关于SpringBoot项目注入 traceId 来追踪整个请求的日志链路的文章就介绍到这了,更多相关SpringBoot traceId 日志链路内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论