问题描述
当前运维监控系统会将超时接口的告警信息发送至企业邮箱,但邮件中未包含Gateway的TraceID,导致在排查分布式链路问题时难以快速定位和追踪。
排查思路
1. 检查插件依赖
首先确认在APM插件(plugin)中是否已正确引入Gateway相关依赖包。若缺少Gateway组件包,会导致无法捕获和传递TraceID。
2.项目工程是否确实相关依赖
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-webflux</artifactId>
<version>9.4.0</version>
</dependency>
以上验证完成,但是日志中还是无TID。偶然debug发现TraceId可以再ServerWebExchange获取到。
于是就考虑到自己将traceId 拿到到,利用MDC工具拼接到日志中。
简单普及一下MDC的做作用
MDC(Mapped Diagnostic Context)是日志框架提供的用于跨方法传递上下文信息的工具,特别适合在分布式系统中传递 TraceID、用户信息等公共参数。
代码实现
-
1.通过反射将tid值获取到
public class SkywalkingUtil {
/**
* tid放入MDC
*
* @param exchange
*/
public static void putTidIntoMdc(ServerWebExchange exchange) {
try {
Object entrySpanInstance = exchange.getAttributes().get("SKYWALING_SPAN");
if (ObjectUtil.isEmpty(entrySpanInstance)) {
return;
}
Class<?> entrySpanClazz = entrySpanInstance.getClass().getSuperclass().getSuperclass();
Field field = entrySpanClazz.getDeclaredField("owner");
field.setAccessible(true);
Object ownerInstance = field.get(entrySpanInstance);
Class<?> ownerClazz = ownerInstance.getClass();
Method getTraceId = ownerClazz.getMethod("getReadablePrimaryTraceId");
String traceId = (String) getTraceId.invoke(ownerInstance);
MDC.put("qinqing", traceId);
} catch (Exception e) {
log.error("未加载加到skywalking信息", e);
}
}
}
-
2.添加过滤器再请求被订阅时将traceId放入
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.doOnSubscribe(s -> { // 当订阅发生时(请求开始时)执行
String traceId = WebFluxSkyWalkingTraceContext.traceId(exchange);
if (traceId.isEmpty()) { // 如果SkyWalking未生成TraceID
SkywalkingUtil.putTidIntoMdc(exchange); // 自定义工具类生成并存入MDC
}
})
.doFinally(s -> MDC.remove("qinqing")); // 请求结束时清理MDC
}
关键点说明
-
执行时机:
-
doOnSubscribe
:在请求处理管道被订阅时触发(相当于Servlet过滤器的doFilter
) -
doFinally
:无论成功/异常都会执行(类似finally
块)
-
-
TraceID处理逻辑:
-
优先尝试从
WebFluxSkyWalkingTraceContext
获取SkyWalking的TraceID -
如果获取失败(空值),则通过
SkywalkingUtil
生成并存入MDC -
使用的MDC Key为
"CBIMTID"
(推测是项目自定义的键名)
-
-
资源清理:
-
通过
MDC.remove()
确保线程局部变量不会内存泄漏 -
在响应完成后立即清理,避免污染后续请求
-
3. 日志中组装为TID输出格式
<property name="CONSOLE_LOG_PATTERN"
value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [${servername}] [%thread] [%logger{50}:%L] [TID: %X{qinqing}] %msg%n"/>
测试验证
- gateway 中打印的日志
- A服务中调用的日志
- skywalking中日志追踪链