spring cloud + feign 和 dubbo 两种框架下的无业务代码侵入的日志trace方案

13 篇文章 0 订阅
2 篇文章 0 订阅

写了好多年了,求波点赞,收藏,关注,一键三连!!

 

新公司架构对外提供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

我觉得这不算优雅,暂时没找到更好的方案。

剩下的问题等有时间会继续研究补全。

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值