查询日志的痛点:项目中每当我们查询日志的时候都是看前端请求什么接口,根据一些关键字进入服务器查询日志中是否有这个关键字,然而这个关键字在日志里面并不是唯一的,所以要生成一个唯一的标识,每一次请求都是唯一的一串字符,查询会过滤掉很多无用的信息,快捷查找到这次请求。为了解决这个痛点,就使用了TraceId。
一、TraceId 定义
用于标识某一次具体的请求ID。当用户的请求进入系统后,会在RPC调用网络的第一层生成一个全局唯一的traceId,并且会随着每一层的RPC调用,不断往后传递,这样的话通过traceId就可以把一次用户请求在系统中调用的路径串联起来。
在分布式系统中,一个请求可能会涉及多个服务和组件的调用,而traceId可以帮助我们追踪和查看整个请求的流程和调用链
二、TraceId的请求流程
在最开始请求系统时候生成一个全局唯一的traceId,放在http 请求header中,系统接收到请求后,从header中取出这个traceId,放入MDC中,这个traceId伴随着这整个请求的调用周期,即当一个服务调用另外一个服务的时候,需要将traceId往下传递,从而形成一条链路。
注意:这次的traceId主要是用UUID来生成的,前端生成traceId,后端拦截器拦截到了做一层判断,如果前端有,就不需要后端生成,而且只需要将前端的这个traceId发给后端人员,后端人员就能直接能定位到日志,不需要再让前端给请求的是什么接口,所以建议是前端来生成traceId。
三、SpringBoot整合TraceId
(一)添加一个logback.xml
logback.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_PATH" value="/xxx/log"/> <!-- 项目的日志路径 -->
<property name="LOG_FILE" value="xxx"/>
<property name="DATE_PATH" value="${LOG_PATH}/%d{yyyy-MM-dd}"/>
<!-- 日志输出格式 -->
<property name="log.pattern.console"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) [traceId:%X{traceId}] %highlight(%-5level) %boldMagenta(%logger{10}) - %cyan(%msg%n)"/>
<property name="log.pattern.file"
value="%d{HH:mm:ss.SSS} [%thread] [traceId:%X{traceId}] %-5level %logger{20} - [%method,%line] - %msg%n"/>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern.console}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 日志文件fzwyapi-admin.log -->
<appender name="fileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE}.log</file>
<append>true</append>
<encoder>
<!-- <pattern>${CONSOLE_LOG_PATTERN}</pattern>-->
<pattern>${log.pattern.file}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>
${DATE_PATH}/%i.log
</fileNamePattern>
<maxHistory>15</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
<maxFileSize>50MB</maxFileSize>
</rollingPolicy>
</appender>
<appender name="fileError" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_PATH}/${LOG_FILE}.error.log</file>
<append>true</append>
<encoder>
<!-- <pattern>${CONSOLE_LOG_PATTERN}</pattern>-->
<pattern>${log.pattern.file}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>
${DATE_PATH}/%i.error.log
</fileNamePattern>
<maxHistory>15</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
<maxFileSize>50MB</maxFileSize>
</rollingPolicy>
</appender>
<root level="info">
<appender-ref ref="fileAppender"/>
<appender-ref ref="fileError"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>
(二)添加TraceId工具类
TraceIdUtil.java
package xxx;
import org.slf4j.MDC;
import java.util.UUID;
/**
* @author lyc
* @date 2024/10/17
* @funtion
*/
public class TraceIdUtil {
public static final String TRACE_ID = "traceId";
public static String getTraceId() {
String traceId = MDC.get(TRACE_ID);
return traceId == null ? "" : traceId;
}
public static void setTraceId(String traceId) {
MDC.put(TRACE_ID, traceId);
}
public static void remove() {
MDC.remove(TRACE_ID);
}
public static void clear() {
MDC.clear();
}
public static String generateTraceId() {
return UUID.randomUUID().toString().replace("-", "");
}
}
(三)添加TraceId拦截器
SystemTraceIdInterceptor.java
package xxx;
import xxx.TraceIdUtil;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author lyc
* @date 2024/10/17
* @funtion
*/
@Component
public class SystemTraceIdInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//如果有上层调用就用上层的ID
String traceId = request.getHeader(TraceIdUtil.TRACE_ID);
if (traceId == null) {
traceId = TraceIdUtil.generateTraceId();
}
MDC.put(TraceIdUtil.TRACE_ID, traceId);
//配置traceId到响应header中
response.setHeader(TraceIdUtil.TRACE_ID, MDC.get(TraceIdUtil.TRACE_ID));
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
//调用结束后删除
MDC.remove(TraceIdUtil.TRACE_ID);
}
}
(四)InterceptorConfig配置TraceId拦截器
InterceptorConfig.java
package xxx.config;
import xxx.SystemTraceIdInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
/**
* @author lyc
* @updated: 2024/10/17
* @funtion
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Resource
private SystemTraceIdInterceptor systemTraceIdInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置链路追踪traceId拦截器
registry.addInterceptor(systemTraceIdInterceptor).addPathPatterns("/**");
}
}
四、效果