问题

在继承了SkyWalking日志工具的情况下,Spring Cloud Gateway的日志中TID为N/A,其他服务的日志均能够正常记录TID。
SkyWalking UI可以正常记录包括Spring Cloud Gateway在内的各个服务的TID。

原因

SkyWalking agent plugin只保证将TID传递给OAP,而不保证一定把TID输出到日志。
如果需要支持,则需要使用方增加此特性。

Agent plugin源码分析

Spring Cloud Gateway对应的plugin源码如下:
Agent plugin:spring-webflux-5.x-plugin
类:DispatcherHandlerHandleMethodInterceptor

@Override  
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,  
                         MethodInterceptResult result) throws Throwable {  
    EnhancedInstance instance = getInstance(allArguments[0]);  
  
    ServerWebExchange exchange = (ServerWebExchange) allArguments[0];  
  
    ContextCarrier carrier = new ContextCarrier();  
    CarrierItem next = carrier.items();  
    HttpHeaders headers = exchange.getRequest().getHeaders();  
    while (next.hasNext()) {  
        next = next.next();  
        List<String> header = headers.get(next.getHeadKey());  
        if (header != null && header.size() > 0) {  
            next.setHeadValue(header.get(0));  
        }  
    }  
  
    AbstractSpan span = ContextManager.createEntrySpan(exchange.getRequest().getURI().getPath(), carrier);  
  
    if (instance != null && instance.getSkyWalkingDynamicField() != null) {  
        ContextManager.continued((ContextSnapshot) instance.getSkyWalkingDynamicField());  
    }  
    span.setComponent(ComponentsDefine.SPRING_WEBFLUX);  
    SpanLayer.asHttp(span);  
    Tags.URL.set(span, exchange.getRequest().getURI().toString());  
    HTTP.METHOD.set(span, exchange.getRequest().getMethodValue());  
    instance.setSkyWalkingDynamicField(ContextManager.capture());  
    span.prepareForAsync();  
    ContextManager.stopSpan(span);  
	//SKYWALING_SPAN保存到exchange
    exchange.getAttributes().put("SKYWALING_SPAN", span);  
}

Solution

Solution 1

  • 只需要在filter中记录TID,便于排查问题,而不保存到日志框架的MDC种。
  • 根据源码分析部分可以得知plugin会把SKYWALING_SPAN保存到exchange
    中,所以我们可以在exchange中获取SKYWALING_SPAN。
  • 从exchange中获取到Span对象后,需要通过反射的方式一层层获取到traceid,因为无法直接依赖包含这些类的agent-core包,会跟agent本身发生冲突。
String traceId = "N/A";  
Object skywalingSpanObject = exchange.getAttributes().get("SKYWALING_SPAN");  
if (ObjectUtils.isNotEmpty(skywalingSpanObject)) {  
    try {  
        Field owner = FieldUtils.getField(skywalingSpanObject.getClass(), "owner", true);  
        Object tracingContext = owner.get(skywalingSpanObject);  
        Field segmentField = FieldUtils.getField(tracingContext.getClass(), "segment", true);  
        Object segment = segmentField.get(tracingContext);  
        Field relatedGlobalTraceIdField = FieldUtils.getField(segment.getClass(), "relatedGlobalTraceId", true);  
        Object relatedGlobalTraceId = relatedGlobalTraceIdField.get(segment);  
        String traceIdObject = relatedGlobalTraceId.toString();  
        traceId = Stringutils.substrΩingBetween(traceIdObject, "=", ")");  
    } catch (Exception e) {  
        log.warn("get TID failed", e);  
    }

Reference

SpringCloudGateway3.1.3, With apm-log4j2-2.x:8.10.0, does not display the real traceId, always display 'TID: N/A' · Discussion #9232 · apache/skywalking (github.com)
My log can't get traceId when I use Spring Cloud Gateway in my project ,all traceId in my log is "[traceId:TID:N/A]" · Issue #5268 · apache/skywalking (github.com)
SpringCloudGateway使用Skywalking时日志打印traceId - 简书 (jianshu.com)

#SkyWalking
#SpringCloudGateway
#MDC
#TracdID