全局调用链路traceId网关到业务层、feign调用统一问题记录
项目里面使用的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调用显示也和网关的都一样的了