全局调用链路traceId网关到业务层、feign调用统一问题记录

发布时间 2023-08-28 14:58:31作者: Doyourself!

              项目里面使用的traceId是基于skywalking进行打印的,但是实际使用的过程中发现网关处的traceId为空,而且feign调用其他服务时候的traceId 都不一样。 显示如下:

              网关traceId为空:

            

  

    基于此,想要把项目里面的以及feign调用的traceId统一成一样的,且在网关显示一样。

 

 首先是需要在日志设置打印traceId的格式:以logback-spring.xml为例

 

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
                <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} %tid [%-5level] [%thread] [%logger{15}:%line] %X{traceId} --- %msg%n</pattern>
            </layout>
        </encoder>
    </appender>

  %X{traceId} 即为显示的traceId

 

 其次是在网关出需要优先把traceId进行打印显示:

  

            String traceId = StringUtils.isEmpty(exchange.getRequest().getHeaders().getFirst(TraceUtils.TRACE_ID))?TraceUtils.createTraceId():
                    exchange.getRequest().getHeaders().getFirst(TraceUtils.TRACE_ID);
            logger.info("当前request traceId:" + traceId);
public class TraceUtils {
    public static final String TRACE_ID = "traceId";

    public static synchronized String createTraceId() {
        String traceId = UUID.randomUUID().toString().replaceAll("-", "").toLowerCase();
        MDC.put(TRACE_ID, traceId);
        return traceId;
    }

    public static void destroyTraceId() {
        MDC.remove(TRACE_ID);
        MDC.clear();
    }


}

注意的是:如果在网关处有其他的业务日志,需要优先把traceId创建出来。否则使用的是默认的16位traceId.我自己在开发和测试环境测试发现,如果把traceId写在业务日志后面,显示的是16位的且和我本地不同的traceId.

 

feign时候需要通过request.getHeader("traceId")获取,且需要在网关处把生成的traceId放入header里面。大概的代码如下:

 

ServerHttpRequest request = exchange.getRequest().mutate()
                    .header(Constant.H_KEY_DEVICE_ID, deviceId)
                    .header(Constant.H_KEY_APP_ID, appId)
                    .header(Constant.H_KEY_BRAND, sourceApp)
                    .header(Constant.H_KEY_BRAND, sourceApp)
                    .header(Constant.H_KEY_IP, ip)
                    .header(Constant.H_URL, exchange.getRequest().getURI().toString())
                    .header(Constant.H_KEY_REQ_FROM,"outside")
                    .header(TraceUtils.TRACE_ID,traceId)
                    .build();
package com.gwm.marketing.feign.autoconfigure;

import com.gwm.marketing.common.constants.BaseCommon;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.Optional;

/**
 * FeignClient调用服务时添加广告主信息请求头
 *
 */
public class FeignAddHeadersRequestInterceptor implements RequestInterceptor {
    /**
     * 日志定义
     */
    private static final Logger LOG = LoggerFactory.getLogger(FeignAddHeadersRequestInterceptor.class);
    /**
     * 请求头名称
     */
    private static final String X_CLIENT_USER = "x-client-user";
    private static final String AUTHORIZATION = "Authorization";

    private static final String SOURCEAPP = "SourceApp";

    private static final String SOURCEAPPEGNORE= "sourceApp";
    private static final String TRACE_ID = "traceId";

    private static final String SOURCE_APP_VER = "SourceAppVer";

    private static final String SOURCE_APP_VER_IGNORE = "sourceappver";


    @Override
    public void apply(RequestTemplate template) {
        LOG.info("feign add header begin");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return;
        }
        HttpServletRequest request = attributes.getRequest();
        String header = request.getHeader(AUTHORIZATION);
        String traceId = request.getHeader(TRACE_ID);
        String sourceApp = request.getHeader(SOURCEAPP);
        String sourceAppVer = request.getHeader(SOURCE_APP_VER);
        if (header != null) {
            // 添加请求头
            template.header(AUTHORIZATION, header);
            LOG.info("feign add header done");
        }
        if(Objects.nonNull(sourceApp)){
            template.header(SOURCEAPP, sourceApp);
            LOG.info("feign souceApp done");
        }else {
            String sourceAppIgnore = request.getHeader(SOURCEAPPEGNORE);
            if(Objects.nonNull(sourceAppIgnore)){
                template.header(SOURCEAPPEGNORE,sourceAppIgnore);
                LOG.info("fein SOURCEAPPEGNORE done");
            }
        }
        if(Objects.isNull(traceId)){
            traceId = Optional.ofNullable(request.getAttribute(TRACE_ID)).map(t->t.toString()).orElse(null);
            LOG.info("current traceId:" + traceId);
        }
        if(Objects.nonNull(traceId)){
            //添加traceId
            template.header(TRACE_ID,traceId);
            MDC.put(TRACE_ID,traceId);
            LOG.info("request traceId:" + traceId);
        }
        if(Objects.nonNull(sourceAppVer)){
            template.header(BaseCommon.SOURCE_APP_VER,sourceAppVer);
        }else{
            String sourceAppVerIgnore = request.getHeader(SOURCE_APP_VER_IGNORE);
            if(Objects.nonNull(sourceAppVerIgnore)){
                template.header(BaseCommon.SOURCE_APP_VER,sourceAppVerIgnore);
                LOG.info("feign add sourceAppVerIgnore ");
            }
        }

    }
}

 

通过实现RequestInterceptor接口的apply方法,把feign调用的traceId给打通。大概思路是这样。这样打通后,一个完整的请求的traceId即可打通。

网关:

 

业务层:feign调用显示也和网关的都一样的了