写了好多年了,求波点赞,收藏,关注,一键三连!!
新公司架构对外提供http接口服务,内部微服务间采用Dubbo进行通信。因为服务拆的非常散,HTTP到RPC服务之间的日志完全独立不利于排查问题。所以开发了一个小功能,trace日志追踪。
正好之前也开发过spring cloud系的相同功能,来分别记录一下。
通用内容:
日志框架logback
日志输出格式中加入traceId的输出
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(--){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr([%32.32X{traceId}]){faint} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
spring cloud + feign
服务全是http接口,消费者发送HTTP请求时通过RequestInterceptor把traceId放入header即可传输;服务提供者则在Controller做filter拦截将traceId保存至MDC即可。
版本:
springBootVersion = "2.0.4.RELEASE"
springCloudVersion = "Finchley.SR2"
@Component
public class FeignHeaderRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header(TraceConst.TRACE_ID, MDC.get(TraceConst.TRACE_ID))
.header(TenantConst.TENANT, HttpContextUtils.getHttpServletRequest().getHeader(TenantConst.TENANT));
}
}
@WebFilter(filterName = "mdcFilter", urlPatterns = "/*")
@Slf4j
public class MdcFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
HttpServletRequest request = (HttpServletRequest) servletRequest;
MDC.put(TraceConst.TRACE_ID, request.getHeader(TraceConst.TRACE_ID));
filterChain.doFilter(servletRequest, servletResponse);
} finally {
MDC.remove(TraceConst.TRACE_ID);
}
}
}
至于traceId生成时机,我选择在gateway接收到请求时即添加到header向下传输。这个时机需要根据业务系统分析。
Dubbo
版本 dubbo 2.7.0
在Dubbo下,实现类似的功能,需要用到Dubbo的RpcContext和扩展点Filter。
做providerFilter(服务提供者拦截器,在RPC接口接收到请求时拦截)和ConsumerFilter(服务消费者拦截器,消费某个RPC服务时拦截)两个自定义filter扩展
然后再filter中通过RpcContext的隐式传参传输traceId。
最开始做了一个方案,使用AOP切面切所有RPC入口方法,然后通过RpcContext传入traceId,发现只有第一个Rpc服务可以接收到我传输的traceId,后面的服务都接收不到。经查找发现是因为RpcContext的传参再每次Rpc调用都会被清除。
所以这个方案不可取。还是要采用dubbo提供的filter扩展点。再每次发送时都放入RpcContext中。
基本思路就是这样。下面上代码
public class TraceId {
private static final ThreadLocal<String> traceId = new ThreadLocal<>();
public static void set(String traceId) {
TraceId.traceId.set(traceId);
MDC.put(TraceConst.TRACE_ID, traceId);
}
public static String get() {
return TraceId.traceId.get();
}
public static void remove() {
traceId.remove();
MDC.remove(TraceConst.TRACE_ID);
}
}
@Activate(group = {Constants.CONSUMER}, order = Ordered.HIGHEST_PRECEDENCE)
public class ConsumerTraceRpcFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
final String traceId = TraceId.get();
RpcContext.getContext().setAttachment(TraceConst.TRACE_ID, traceId);
return invoker.invoke(invocation);
}
}
@Slf4j
@Activate(group = {Constants.PROVIDER})
public class ProviderTraceRpcFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
try {
final String traceId = RpcContext.getContext().getAttachment(TraceConst.TRACE_ID);
TraceId.set(traceId);
return invoker.invoke(invocation);
} finally {
TraceId.remove();
}
}
}
@Slf4j
public class HttpTraceFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
log.info("=======================httpTraceFilter init=======================");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String traceId = request.getHeader(TraceConst.TRACE_ID);
if (StringUtils.isEmpty(traceId)) {
traceId = UUID.randomUUID().toString().replaceAll("-", "");
}
TraceId.set(traceId);
filterChain.doFilter(servletRequest, servletResponse);
} finally {
TraceId.remove();
}
}
@Override
public void destroy() {
log.info("=======================httpTraceFilter destroy=======================");
}
}
然后做成spring的starter模式,通过AutoConfiguration模式注入即可。
基本实现了HTTP到RPC,RPC到RPC间的一个traceId透传。待解决的问题还有线程间的透传。线程池上的透传及线程结束后的一个线程池上线程的traceId消除,MQ发送时的透传等。
如果是手动开启一个新线程,可以在TraceId类中,加入如下代码的处理
private static final ThreadLocal<String> childTraceId = new InheritableThreadLocal<>();
然后在手动开启的新线程中即可获取到traceId,但是仍需要手动配置放入MDC
我觉得这不算优雅,暂时没找到更好的方案。
剩下的问题等有时间会继续研究补全。