5. 分布式链路追踪TracingFilter改造增强设计

前言

4. 分布式链路追踪客户端工具包Starter设计一文中,我们实现了基础的Starter 包,里面提供了我们自己定义的Servlet 过滤器和RestTemplate 拦截器,其中Servlet 过滤器叫做HoneyTracingFilter ,仅提供了提取SpanContext ,创建Span 和开启Span 的基础功能,所以本文将围绕如何增强Servlet过滤器展开讨论。

相关版本依赖如下。

opentracing-api 版本:0.33.0
opentracing-spring-web 版本:4.1.0
jaeger-client 版本:1.8.1
Springboot 版本:2.7.6

github 地址:honey-tracing

正文

一. Opentracing提供的TracingFilter

其实最简单的增强方式,就是使用TracingFilter 来替换我们自己实现的HoneyTracingFilter ,下面给出TracingFilter的源码实现。

java 复制代码
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
        throws IOException, ServletException {

    HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
    HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;

    
    if (!isTraced(httpRequest, httpResponse)) {
        chain.doFilter(httpRequest, httpResponse);
        return;
    }

    if (servletRequest.getAttribute(SERVER_SPAN_CONTEXT) != null) {
        chain.doFilter(servletRequest, servletResponse);
    } else {
        
        SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS,
                new HttpServletRequestExtractAdapter(httpRequest));

        
        final Span span = tracer.buildSpan(httpRequest.getMethod())
                .asChildOf(extractedContext)
                .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
                .start();

        httpRequest.setAttribute(SERVER_SPAN_CONTEXT, span.context());

        
        
        for (ServletFilterSpanDecorator spanDecorator: spanDecorators) {
            spanDecorator.onRequest(httpRequest, span);
        }

        
        try (Scope scope = tracer.activateSpan(span)) {
            chain.doFilter(servletRequest, servletResponse);
            if (!httpRequest.isAsyncStarted()) {
                for (ServletFilterSpanDecorator spanDecorator : spanDecorators) {
                    spanDecorator.onResponse(httpRequest, httpResponse, span);
                }
            }
        } catch (Throwable ex) {
            
            for (ServletFilterSpanDecorator spanDecorator : spanDecorators) {
                spanDecorator.onError(httpRequest, httpResponse, ex, span);
            }
            throw ex;
        } finally {
            if (httpRequest.isAsyncStarted()) {
                
                httpRequest.getAsyncContext()
                        .addListener(new AsyncListener() {
                            @Override
                            public void onComplete(AsyncEvent event) throws IOException {
                                HttpServletRequest httpRequest = (HttpServletRequest) event.getSuppliedRequest();
                                HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse();
                                
                                for (ServletFilterSpanDecorator spanDecorator: spanDecorators) {
                                    spanDecorator.onResponse(httpRequest,
                                            httpResponse,
                                            span);
                                }
                                span.finish();
                            }

                            @Override
                            public void onTimeout(AsyncEvent event) throws IOException {
                                HttpServletRequest httpRequest = (HttpServletRequest) event.getSuppliedRequest();
                                HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse();
                                
                                for (ServletFilterSpanDecorator spanDecorator : spanDecorators) {
                                    spanDecorator.onTimeout(httpRequest,
                                            httpResponse,
                                            event.getAsyncContext().getTimeout(),
                                            span);
                                }
                            }

                            @Override
                            public void onError(AsyncEvent event) throws IOException {
                                HttpServletRequest httpRequest = (HttpServletRequest) event.getSuppliedRequest();
                                HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse();
                                
                                for (ServletFilterSpanDecorator spanDecorator: spanDecorators) {
                                    spanDecorator.onError(httpRequest,
                                            httpResponse,
                                            event.getThrowable(),
                                            span);
                                }
                            }

                            @Override
                            public void onStartAsync(AsyncEvent event) throws IOException {
                            }
                        });
            } else {
                
                span.finish();
            }
        }
    }
}

通过阅读TracingFilter源码,我们可以得到如下几种扩展增强。

  1. Servlet 自身的urlPatterns 机制。可以通过配置urlPatterns,决定哪些请求需要打印链路信息;
  2. TracingFilterskipPattern 机制。可以通过配置skipPattern,决定哪些请求不需要打印链路信息;
  3. 装饰器ServletFilterSpanDecorator 。可以提供ServletFilterSpanDecorator 给到TracingFilter,这样在收到请求,返回响应和处理异常时均可以做一些扩展操作;

二. urlPatterns和skipPattern设计

在第一节中得到的TracingFilter 的几种增强,其中第1和第2点的urlPatternsskipPattern,可以提供出来供用户配置,本节对这部分进行实现。

首先是配置属性类里面需要加入urlPatternsskipPattern的配置属性,如下所示。

java 复制代码
 * 分布式链路追踪配置属性类。
 */
@ConfigurationProperties("honey.tracing")
public class HoneyTracingProperties {

    private boolean enabled;

    private HttpUrlProperties httpUrl = new HttpUrlProperties();

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public HttpUrlProperties getHttpUrl() {
        return httpUrl;
    }

    public void setHttpUrl(HttpUrlProperties httpUrl) {
        this.httpUrl = httpUrl;
    }

    public static class HttpUrlProperties {
        
         * 按照/url1,/url2这样配置。
         */
        private String urlPattern = "/*";
        
         * 按照/url1|/honey.*这样配置。
         */
        private String skipPattern = "";

        public String getUrlPattern() {
            return urlPattern;
        }

        public void setUrlPattern(String urlPattern) {
            this.urlPattern = urlPattern;
        }

        public String getSkipPattern() {
            return skipPattern;
        }

        public void setSkipPattern(String skipPattern) {
            this.skipPattern = skipPattern;
        }
    }

}

然后注册过滤器的配置类HoneyTracingFilterConfig需要做如下修改。

java 复制代码
 * Servlet过滤器配置类。
 */
@Configuration
@AutoConfigureAfter(HoneyTracingConfig.class)
public class HoneyTracingFilterConfig {

    @Autowired
    private HoneyTracingProperties honeyTracingProperties;

    @Bean
    public FilterRegistrationBean<TracingFilter> honeyTracingFilter(Tracer tracer) {
        String urlPattern = honeyTracingProperties.getHttpUrl().getUrlPattern();
        String skipPatternStr = honeyTracingProperties.getHttpUrl().getSkipPattern();
        Pattern skipPattern = Pattern.compile(skipPatternStr);

        FilterRegistrationBean<TracingFilter> registrationBean = new FilterRegistrationBean<>();
        
        registrationBean.addUrlPatterns(urlPattern);
        
        registrationBean.setOrder(Integer.MIN_VALUE);
        
        
        registrationBean.setFilter(new TracingFilter(tracer, new ArrayList<>(), skipPattern));

        return registrationBean;
    }

}

三. TracingFilter的装饰器设计

通过为TracingFilter 注册ServletFilterSpanDecorator 装饰器,可以让我们在收到请求,返回响应和处理异常时做一些扩展操作,例如记录请求urlapi 和返回码等,下面实现一个装饰器HoneyServletFilterSpanDecorator,其提供如下几个功能。

收到请求时记录:

  1. 请求的host
  2. 请求的api

返回响应时记录:

  1. 响应码。

处理异常时记录:

  1. 响应码。

实现如下。

java 复制代码
 * {@link TracingFilter}的装饰器。
 */
public class HoneyServletFilterSpanDecorator implements ServletFilterSpanDecorator {

    @Override
    public void onRequest(HttpServletRequest httpServletRequest, Span span) {
        span.setTag(FIELD_HOST, getHostFromRequest(httpServletRequest));
        span.setTag(FIELD_API, httpServletRequest.getRequestURI());
    }

    @Override
    public void onResponse(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Span span) {
        span.setTag(FIELD_HTTP_CODE, httpServletResponse.getStatus());
    }

    @Override
    public void onError(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Throwable exception, Span span) {
        span.setTag(FIELD_HTTP_CODE, httpServletResponse.getStatus());
    }

    @Override
    public void onTimeout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, long timeout, Span span) {
        
    }

    private String getHostFromRequest(HttpServletRequest httpServletRequest) {
        return httpServletRequest.getScheme()
                + "://"
                + httpServletRequest.getServerName()
                + ":"
                + httpServletRequest.getServerPort();
    }

}

相关的常量字段记录在CommonConstants中,如下所示。

java 复制代码
public class CommonConstants {

    public static final double DEFAULT_SAMPLE_RATE = 1.0;

    public static final String HONEY_TRACER_NAME = "HoneyTracer";
    public static final String HONEY_REST_TEMPLATE_NAME = "HoneyRestTemplate";

    public static final String FIELD_HOST = "host";
    public static final String FIELD_API = "api";
    public static final String FIELD_HTTP_CODE = "httpCode";

}

在注册TracingFilter 时需要将HoneyServletFilterSpanDecorator 设置给TracingFilter ,对应的配置类HoneyTracingFilterConfig修改如下。

java 复制代码
 * Servlet过滤器配置类。
 */
@Configuration
@AutoConfigureAfter(HoneyTracingConfig.class)
public class HoneyTracingFilterConfig {

    @Autowired
    private HoneyTracingProperties honeyTracingProperties;

    @Bean
    public FilterRegistrationBean<TracingFilter> honeyTracingFilter(Tracer tracer,
                                                                    List<ServletFilterSpanDecorator> decorators) {
        String urlPattern = honeyTracingProperties.getHttpUrl().getUrlPattern();
        String skipPatternStr = honeyTracingProperties.getHttpUrl().getSkipPattern();
        Pattern skipPattern = Pattern.compile(skipPatternStr);

        FilterRegistrationBean<TracingFilter> registrationBean = new FilterRegistrationBean<>();
        
        registrationBean.addUrlPatterns(urlPattern);
        
        registrationBean.setOrder(Integer.MIN_VALUE);
        
        
        registrationBean.setFilter(new TracingFilter(tracer, decorators, skipPattern));

        return registrationBean;
    }

    @Bean
    public ServletFilterSpanDecorator honeyServletFilterSpanDecorator() {
        return new HoneyServletFilterSpanDecorator();
    }

}

至此,我们就使用装饰器装饰了TracingFilter ,效果就是最终在TracingFilter 调用到Spanfinish() 方法时,我们可以从Spantags 中拿到本次请求的hostapihttpCode,这些数据可以最终在打印链路日志时使用。

最后给出工程目录结构图。

总结

本文在4. 分布式链路追踪客户端工具包Starter设计的基础上,使用TracingFilter 替换了我们自己实现的HoneyTracingFilter ,并且基于urlPatternsskipPattern和装饰器进行了扩展增强。

相关推荐
hochenchong1 个月前
自定义 SpringBoot starter
java·springboot·starter
G皮T4 个月前
【Spring Boot】Spring Boot 中的 Starter
java·spring boot·后端·spring·starter
锋神丶4 个月前
4. 分布式链路追踪客户端工具包Starter设计
starter
java_强哥4 个月前
SpringBoot自定义starter
spring boot·后端·starter
大飞哥~BigFei6 个月前
我手写的轮子开源了
java·开源·starter
用针戳左手中指指头7 个月前
spring aop实现接口超时处理组件
java·spring·自动配置·starter
小沈同学呀8 个月前
【实战】SpringBoot自定义 starter及使用
java·spring boot·后端·spring·starter
京东云技术团队1 年前
Spring Boot Starter 剖析与实践 | 京东云技术团队
java·spring boot·后端·starter·京东云
Ez4Sterben1 年前
【SpringBoot】从零开始封装自己的starter并且引入到其他项目中使用
java·spring boot·后端·starter