diff --git a/src/main/java/cn/iocoder/dashboard/framework/tracer/core/util/TracerUtils.java b/src/main/java/cn/iocoder/dashboard/framework/tracer/core/util/TracerUtils.java index 033216260..a7cd841da 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/tracer/core/util/TracerUtils.java +++ b/src/main/java/cn/iocoder/dashboard/framework/tracer/core/util/TracerUtils.java @@ -5,7 +5,9 @@ import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.dashboard.framework.tracer.core.ITrace; import org.apache.skywalking.apm.toolkit.trace.TraceContext; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; /** * 链路追踪工具类 @@ -14,6 +16,36 @@ import java.util.UUID; */ public class TracerUtils { + /** + * 维护请求线程对应的TraceId + */ + private final static Map threadTraceIdMap = new ConcurrentHashMap<>(); + + /** + * 保存链路流水号 + * + * @param traceId 链路流水号 + */ + public static void saveThreadTraceId(String traceId) { + threadTraceIdMap.put(Thread.currentThread(), traceId); + } + + /** + * 根据线程获取链路流水号 + * + * @return 链路流水号 + */ + public static String getThreadTraceId() { + return threadTraceIdMap.get(Thread.currentThread()); + } + + /** + * 根据线程删除链路流水 + */ + public static void deleteThreadTraceId() { + threadTraceIdMap.remove(Thread.currentThread()); + } + /** * 私有化构造方法 */ @@ -26,10 +58,27 @@ public class TracerUtils { * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。 *

* 默认情况下,我们使用 Apache SkyWalking 的 traceId 作为链路追踪编号。当然,可能会存在并未引入 Skywalking 的情况,此时使用 UUID 。 + * 方法获取顺序: skywalking - > map -> 扩展接口 -> default + * 项目整体获取traceId的优先级 skywalking > 自定义实现接口 > 默认 + * map中的traceId 当且仅当其他情况为产生时才会出现. * * @return 链路追踪编号 */ public static String getTraceId() { + String traceId; + // 通过 SkyWalking 获取链路编号 + try { + traceId = TraceContext.traceId(); + if (StrUtil.isNotBlank(traceId)) { + return traceId; + } + } catch (Throwable ignore) { + } + // 尝试从map中获取 + traceId = threadTraceIdMap.get(Thread.currentThread()); + if (StrUtil.isNotBlank(traceId)) { + return traceId; + } // 通过自定义扩展的tracer产生traceId, 在Spring容器加载完成前会获取不到对应的Bean ITrace tracer = null; try { @@ -42,19 +91,19 @@ public class TracerUtils { } catch (Throwable ignored) { } } - // 通过 SkyWalking 获取链路编号 - try { - String traceId = TraceContext.traceId(); - if (StrUtil.isNotBlank(traceId)) { - return traceId; - } - } catch (Throwable ignore) { - } // TODO 芋艿 多次调用会问题 return defaultTraceId(); } + /** + * 仅仅获取Skywalking 中的traceId, 若无skywalking,则会返回"" + * + * @return skywalking 的traceId + */ + public static String getSkywalkingTraceId() { + return TraceContext.traceId(); + } /** * 从Spring 容器中获取 ITrace 类,返回可以为null * diff --git a/src/main/java/cn/iocoder/dashboard/framework/tracer/filter/SpringMvcTraceFilter.java b/src/main/java/cn/iocoder/dashboard/framework/tracer/filter/SpringMvcTraceFilter.java new file mode 100644 index 000000000..76fc93eb4 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/tracer/filter/SpringMvcTraceFilter.java @@ -0,0 +1,68 @@ +package cn.iocoder.dashboard.framework.tracer.filter; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.AsyncHandlerInterceptor; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 对Spring Mvc 的请求拦截, 添加traceId. + * + * @author mashu + */ + +@Slf4j +@Component +public class SpringMvcTraceFilter implements HandlerInterceptor { + + @Value("${cn.iocoder.tracer.name:global-trace-id}") + private String traceIdName; + + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // 请求中traceId + String reqTraceId = (String)request.getAttribute(traceIdName); + // skywalking中的traceId + String skywalkingTraceId = TracerUtils.getSkywalkingTraceId(); + String traceId ; + if (null == reqTraceId && StrUtil.isBlank(skywalkingTraceId)) { + // 两者皆空,添加默认的. + traceId = TracerUtils.getTraceId(); + request.setAttribute(traceIdName, traceId); + } else if (null == reqTraceId && StrUtil.isNotBlank(skywalkingTraceId)){ + // 若请求空,则添加,为没有skywalking的系统添加一个TraceId + traceId = skywalkingTraceId; + request.setAttribute(traceIdName, traceId); + } else if (null != reqTraceId && StrUtil.isBlank(skywalkingTraceId)) { + // 请求非空, skywalking为空 + traceId = reqTraceId; + } else { + // 两者皆非空,不动请求头 + traceId = skywalkingTraceId; + } + TracerUtils.saveThreadTraceId(traceId); + log.debug("请求进入,添加traceId[{}]", traceId); + return true; + } + + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { + // 请求结束,删除本地的链路流水号 + log.debug("请求结束,删除traceId[{}]", TracerUtils.getTraceId()); + TracerUtils.deleteThreadTraceId(); + + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/tracer/filter/WebConfig.java b/src/main/java/cn/iocoder/dashboard/framework/tracer/filter/WebConfig.java new file mode 100644 index 000000000..4dddc0803 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/tracer/filter/WebConfig.java @@ -0,0 +1,23 @@ +package cn.iocoder.dashboard.framework.tracer.filter; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import javax.annotation.Resource; + +@Configuration +@Component +public class WebConfig implements WebMvcConfigurer { + + @Resource + private SpringMvcTraceFilter springMvcTraceFilter; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(this.springMvcTraceFilter).addPathPatterns("/**"); + } + +} \ No newline at end of file