20260326:traceId链路追踪
This commit is contained in:
parent
ea1d179c41
commit
24c1120105
@ -5,79 +5,36 @@ import org.slf4j.MDC;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日志追踪ID工具类
|
* 日志追踪ID工具类
|
||||||
* 统一生成TraceId的逻辑,使用IdWorker和ThreadLocalRandom
|
* 统一生成 TraceId 并通过 MDC 传递,使同一请求/消息链路的所有日志可关联。
|
||||||
*
|
*
|
||||||
* @author Sumuxi
|
* @author Sumuxi
|
||||||
*/
|
*/
|
||||||
public class TraceIdUtils {
|
public class TraceIdUtils {
|
||||||
|
|
||||||
/**
|
|
||||||
* TraceId在MDC中的key
|
|
||||||
*/
|
|
||||||
public static final String TRACE_ID_KEY = "traceId";
|
public static final String TRACE_ID_KEY = "traceId";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IdWorker实例(单例模式)
|
* 生成一个新的 TraceId(纯生成,不写入 MDC)。
|
||||||
|
* 使用 32 位 UUID(去横线),全局唯一且无溢出风险。
|
||||||
*/
|
*/
|
||||||
private static volatile IdUtils idWorker;
|
public static String generateTraceId() {
|
||||||
|
return IdUtils.fastSimpleUUID();
|
||||||
/**
|
|
||||||
* 获取IdWorker实例(懒加载单例)
|
|
||||||
*
|
|
||||||
* @return IdWorker实例
|
|
||||||
*/
|
|
||||||
private static IdUtils getIdWorker() {
|
|
||||||
if (idWorker == null) {
|
|
||||||
synchronized (TraceIdUtils.class) {
|
|
||||||
if (idWorker == null) {
|
|
||||||
// 使用机器ID,可以根据实际情况配置
|
|
||||||
// 这里使用1作为默认workerId,可以通过配置文件读取
|
|
||||||
// IdUtils构造函数:workerId, datacenterId,-1表示随机生成
|
|
||||||
idWorker = new IdUtils(-1L, -1L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return idWorker;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成TraceId
|
* 生成 TraceId 并写入当前线程 MDC,返回生成的值。
|
||||||
* 格式:IdWorker.nextId() * 1000 + ThreadLocalRandom.nextInt(1000)
|
|
||||||
* 参考:generateClueId() 的实现方式
|
|
||||||
* 使用Math.abs确保TraceId为正数
|
|
||||||
*
|
|
||||||
* @return TraceId
|
|
||||||
*/
|
*/
|
||||||
public static long generateTraceId() {
|
public static String generateAndSetTraceId() {
|
||||||
long id = getIdWorker().nextId();
|
String traceId = generateTraceId();
|
||||||
// 使用绝对值确保TraceId为正数,避免负数显示
|
MDC.put(TRACE_ID_KEY, traceId);
|
||||||
// 注意:Math.abs(id) * 1000 可能会溢出,所以需要对最终结果再次使用Math.abs()
|
|
||||||
long traceId = Math.abs(id) * 1000 + ThreadLocalRandom.current().nextInt(1000);
|
|
||||||
// 确保最终结果为非负数(处理溢出情况)
|
|
||||||
traceId = Math.abs(traceId);
|
|
||||||
MDC.put(TRACE_ID_KEY, String.valueOf(traceId));
|
|
||||||
return traceId;
|
return traceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成TraceId并放入MDC
|
* 设置指定 TraceId 到 MDC
|
||||||
* 返回字符串格式的TraceId
|
|
||||||
*
|
|
||||||
* @return TraceId字符串
|
|
||||||
*/
|
|
||||||
public static String generateTraceIdString() {
|
|
||||||
long traceId = generateTraceId();
|
|
||||||
return String.valueOf(traceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置TraceId到MDC
|
|
||||||
*
|
|
||||||
* @param traceId TraceId
|
|
||||||
*/
|
*/
|
||||||
public static void setTraceId(String traceId) {
|
public static void setTraceId(String traceId) {
|
||||||
if (traceId != null && !traceId.isEmpty()) {
|
if (traceId != null && !traceId.isEmpty()) {
|
||||||
@ -86,18 +43,7 @@ public class TraceIdUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置TraceId到MDC(long类型)
|
* 从 MDC 获取当前线程的 TraceId
|
||||||
*
|
|
||||||
* @param traceId TraceId
|
|
||||||
*/
|
|
||||||
public static void setTraceId(long traceId) {
|
|
||||||
MDC.put(TRACE_ID_KEY, String.valueOf(traceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从MDC获取TraceId
|
|
||||||
*
|
|
||||||
* @return TraceId
|
|
||||||
*/
|
*/
|
||||||
public static String getTraceId() {
|
public static String getTraceId() {
|
||||||
return MDC.get(TRACE_ID_KEY);
|
return MDC.get(TRACE_ID_KEY);
|
||||||
@ -105,20 +51,19 @@ public class TraceIdUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 确保当前线程 MDC 中存在 TraceId。
|
* 确保当前线程 MDC 中存在 TraceId。
|
||||||
* - 已存在:直接返回
|
* 已存在则返回,不存在则生成并写入 MDC。
|
||||||
* - 不存在:生成并写入 MDC
|
|
||||||
*/
|
*/
|
||||||
public static String ensureTraceId() {
|
public static String ensureTraceId() {
|
||||||
String traceId = getTraceId();
|
String traceId = getTraceId();
|
||||||
if (traceId == null || traceId.isEmpty()) {
|
if (traceId == null || traceId.isEmpty()) {
|
||||||
traceId = generateTraceIdString();
|
traceId = generateAndSetTraceId();
|
||||||
}
|
}
|
||||||
return traceId;
|
return traceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用指定 traceId 覆盖当前线程 MDC(为空则生成)。
|
* 使用指定 traceId 覆盖当前线程 MDC(为空则生成)。
|
||||||
* 主要用于跨线程/跨进程透传后的落地。
|
* 用于跨线程/跨进程透传后的落地。
|
||||||
*/
|
*/
|
||||||
public static String ensureTraceId(String traceId) {
|
public static String ensureTraceId(String traceId) {
|
||||||
if (traceId == null || traceId.isEmpty()) {
|
if (traceId == null || traceId.isEmpty()) {
|
||||||
@ -129,7 +74,15 @@ public class TraceIdUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 包装 Runnable:透传提交线程的 MDC,上下文中无 traceId 则补齐;执行完自动清理/还原。
|
* 清除 MDC 中的 TraceId
|
||||||
|
*/
|
||||||
|
public static void clearTraceId() {
|
||||||
|
MDC.remove(TRACE_ID_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 包装 Runnable:透传提交线程的 MDC 上下文到执行线程,
|
||||||
|
* 无 traceId 则补齐;执行完自动还原,避免线程复用污染。
|
||||||
*/
|
*/
|
||||||
public static Runnable wrapWithMdc(Runnable task) {
|
public static Runnable wrapWithMdc(Runnable task) {
|
||||||
final Map<String, String> captured = MDC.getCopyOfContextMap();
|
final Map<String, String> captured = MDC.getCopyOfContextMap();
|
||||||
@ -154,7 +107,8 @@ public class TraceIdUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 包装 Callable:透传提交线程的 MDC,上下文中无 traceId 则补齐;执行完自动清理/还原。
|
* 包装 Callable:透传提交线程的 MDC 上下文到执行线程,
|
||||||
|
* 无 traceId 则补齐;执行完自动还原,避免线程复用污染。
|
||||||
*/
|
*/
|
||||||
public static <T> Callable<T> wrapWithMdc(Callable<T> task) {
|
public static <T> Callable<T> wrapWithMdc(Callable<T> task) {
|
||||||
final Map<String, String> captured = MDC.getCopyOfContextMap();
|
final Map<String, String> captured = MDC.getCopyOfContextMap();
|
||||||
@ -177,12 +131,4 @@ public class TraceIdUtils {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除MDC中的TraceId
|
|
||||||
*/
|
|
||||||
public static void clearTraceId() {
|
|
||||||
MDC.remove(TRACE_ID_KEY);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import com.fastbee.common.config.RuoYiConfig;
|
|||||||
import com.fastbee.common.constant.Constants;
|
import com.fastbee.common.constant.Constants;
|
||||||
import com.fastbee.framework.interceptor.LanguageInterceptor;
|
import com.fastbee.framework.interceptor.LanguageInterceptor;
|
||||||
import com.fastbee.framework.interceptor.RepeatSubmitInterceptor;
|
import com.fastbee.framework.interceptor.RepeatSubmitInterceptor;
|
||||||
import com.fastbee.framework.interceptor.TraceIdInterceptor;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@ -30,8 +29,6 @@ public class ResourcesConfig implements WebMvcConfigurer {
|
|||||||
private RepeatSubmitInterceptor repeatSubmitInterceptor;
|
private RepeatSubmitInterceptor repeatSubmitInterceptor;
|
||||||
@Resource
|
@Resource
|
||||||
private LanguageInterceptor languageInterceptor;
|
private LanguageInterceptor languageInterceptor;
|
||||||
@Autowired
|
|
||||||
private TraceIdInterceptor traceIdInterceptor;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
@ -51,8 +48,6 @@ public class ResourcesConfig implements WebMvcConfigurer {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
// TraceId拦截器放在最前面,确保所有请求都有TraceId
|
|
||||||
registry.addInterceptor(traceIdInterceptor).addPathPatterns("/**").order(0);
|
|
||||||
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
|
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
|
||||||
//这里配置国际化拦截器的白名单
|
//这里配置国际化拦截器的白名单
|
||||||
registry.addInterceptor(languageInterceptor).addPathPatterns("/**").excludePathPatterns("/v2/api-docs",
|
registry.addInterceptor(languageInterceptor).addPathPatterns("/**").excludePathPatterns("/v2/api-docs",
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.fastbee.framework.config;
|
|||||||
|
|
||||||
import com.fastbee.framework.config.properties.PermitAllUrlProperties;
|
import com.fastbee.framework.config.properties.PermitAllUrlProperties;
|
||||||
import com.fastbee.framework.security.filter.JwtAuthenticationTokenFilter;
|
import com.fastbee.framework.security.filter.JwtAuthenticationTokenFilter;
|
||||||
|
import com.fastbee.framework.security.filter.TraceIdFilter;
|
||||||
import com.fastbee.framework.security.handle.AuthenticationEntryPointImpl;
|
import com.fastbee.framework.security.handle.AuthenticationEntryPointImpl;
|
||||||
import com.fastbee.framework.security.handle.LogoutSuccessHandlerImpl;
|
import com.fastbee.framework.security.handle.LogoutSuccessHandlerImpl;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -18,6 +19,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
|
|||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.logout.LogoutFilter;
|
import org.springframework.security.web.authentication.logout.LogoutFilter;
|
||||||
|
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||||
import org.springframework.web.filter.CorsFilter;
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,6 +54,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private JwtAuthenticationTokenFilter authenticationTokenFilter;
|
private JwtAuthenticationTokenFilter authenticationTokenFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TraceId 过滤器(最高优先级,保证整个请求链路 TraceId 一致)
|
||||||
|
*/
|
||||||
|
@Autowired
|
||||||
|
private TraceIdFilter traceIdFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 跨域过滤器
|
* 跨域过滤器
|
||||||
*/
|
*/
|
||||||
@ -152,6 +160,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
// 添加CORS filter
|
// 添加CORS filter
|
||||||
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
|
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
|
||||||
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
|
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
|
||||||
|
// TraceId filter 放在 Security 过滤链最前面,确保所有日志都有同一个 TraceId
|
||||||
|
httpSecurity.addFilterBefore(traceIdFilter, SecurityContextPersistenceFilter.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
package com.fastbee.framework.interceptor;
|
|
||||||
|
|
||||||
import com.fastbee.common.utils.TraceIdUtils;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 日志追踪ID拦截器
|
|
||||||
* 用于生成和传递TraceId,方便同一个链路调用追查问题
|
|
||||||
*
|
|
||||||
* @author Sumuxi
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class TraceIdInterceptor implements HandlerInterceptor {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
|
||||||
// 获取请求URI
|
|
||||||
String requestURI = request.getRequestURI();
|
|
||||||
|
|
||||||
// 尝试从请求头中获取TraceId(支持分布式追踪)
|
|
||||||
String traceId = request.getHeader("X-Trace-Id");
|
|
||||||
|
|
||||||
// 如果请求头中没有TraceId,则生成一个新的
|
|
||||||
if (traceId == null || traceId.isEmpty()) {
|
|
||||||
traceId = TraceIdUtils.generateTraceIdString();
|
|
||||||
} else {
|
|
||||||
// 如果请求头中有TraceId,设置到MDC
|
|
||||||
TraceIdUtils.setTraceId(traceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将TraceId放入响应头,方便前端获取
|
|
||||||
response.setHeader("X-Trace-Id", traceId);
|
|
||||||
|
|
||||||
// 打印请求路径日志
|
|
||||||
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TraceIdInterceptor.class);
|
|
||||||
log.info("拦截请求路径: {}", requestURI);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
|
||||||
// 请求完成后清除MDC中的TraceId,避免内存泄漏
|
|
||||||
TraceIdUtils.clearTraceId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.fastbee.framework.security.filter;
|
||||||
|
|
||||||
|
import com.fastbee.common.utils.TraceIdUtils;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TraceId 过滤器 — 替代原 TraceIdInterceptor。
|
||||||
|
* <p>
|
||||||
|
* 作为 Servlet Filter 注册在 Spring Security 过滤链最前端,
|
||||||
|
* 保证整个请求生命周期(包括 Security 过滤器、JWT 验证等)的日志
|
||||||
|
* 都携带同一个 TraceId。
|
||||||
|
*
|
||||||
|
* @author Sumuxi
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class TraceIdFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private static final String TRACE_HEADER = "X-Trace-Id";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
FilterChain filterChain)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
String traceId = request.getHeader(TRACE_HEADER);
|
||||||
|
if (traceId == null || traceId.isEmpty()) {
|
||||||
|
traceId = TraceIdUtils.generateAndSetTraceId();
|
||||||
|
} else {
|
||||||
|
TraceIdUtils.setTraceId(traceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setHeader(TRACE_HEADER, traceId);
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
} finally {
|
||||||
|
TraceIdUtils.clearTraceId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -56,8 +56,7 @@ public abstract class DeviceAbstractQuartzJob implements Job {
|
|||||||
protected void before(JobExecutionContext context, DeviceJob deviceJob) {
|
protected void before(JobExecutionContext context, DeviceJob deviceJob) {
|
||||||
threadLocal.set(new Date());
|
threadLocal.set(new Date());
|
||||||
|
|
||||||
// 为设备定时任务生成TraceId,使用统一的生成逻辑
|
TraceIdUtils.generateAndSetTraceId();
|
||||||
TraceIdUtils.generateTraceId();
|
|
||||||
|
|
||||||
log.info("设备定时任务开始执行: {} - {}", deviceJob.getJobName(), deviceJob.getJobGroup());
|
log.info("设备定时任务开始执行: {} - {}", deviceJob.getJobName(), deviceJob.getJobGroup());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,8 +56,7 @@ public abstract class AbstractQuartzJob implements Job {
|
|||||||
protected void before(JobExecutionContext context, SysJob sysJob) {
|
protected void before(JobExecutionContext context, SysJob sysJob) {
|
||||||
threadLocal.set(new Date());
|
threadLocal.set(new Date());
|
||||||
|
|
||||||
// 为定时任务生成TraceId,使用统一的生成逻辑
|
TraceIdUtils.generateAndSetTraceId();
|
||||||
TraceIdUtils.generateTraceId();
|
|
||||||
|
|
||||||
log.info("定时任务开始执行: {} - {}", sysJob.getJobName(), sysJob.getJobGroup());
|
log.info("定时任务开始执行: {} - {}", sysJob.getJobName(), sysJob.getJobGroup());
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user