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和装饰器进行了扩展增强。

相关推荐
coding侠客8 天前
一分钟上手:如何创建你的第一个 Spring Boot Starter
java·spring boot·后端·starter
hochenchong5 个月前
自定义 SpringBoot starter
java·springboot·starter
G皮T7 个月前
【Spring Boot】Spring Boot 中的 Starter
java·spring boot·后端·spring·starter
锋神丶7 个月前
4. 分布式链路追踪客户端工具包Starter设计
starter
java_强哥8 个月前
SpringBoot自定义starter
spring boot·后端·starter
大飞哥~BigFei9 个月前
我手写的轮子开源了
java·开源·starter
用针戳左手中指指头1 年前
spring aop实现接口超时处理组件
java·spring·自动配置·starter
小沈同学呀1 年前
【实战】SpringBoot自定义 starter及使用
java·spring boot·后端·spring·starter
京东云技术团队1 年前
Spring Boot Starter 剖析与实践 | 京东云技术团队
java·spring boot·后端·starter·京东云