前语

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

正文

一. Opentracing供给的TracingFilter

其实最简略的增强方式,便是运用TracingFilter来替换咱们自己完成的HoneyTracingFilter,下面给出TracingFilter源码完成。

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
        throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
    HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
    // 基于正则来判别当时恳求URI是否不需要记载链路信息
    if (!isTraced(httpRequest, httpResponse)) {
        chain.doFilter(httpRequest, httpResponse);
        return;
    }
    if (servletRequest.getAttribute(SERVER_SPAN_CONTEXT) != null) {
        chain.doFilter(servletRequest, servletResponse);
    } else {
        // 运用Extractor从HTTP恳求头中提取出SpanContext
        SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS,
                new HttpServletRequestExtractAdapter(httpRequest));
        // 创立Span并将其作为提取出来的Span的子Span
        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());
        // 在恳求的一开始运用装修器来装修Span
        // 这儿的装修器是很重要的扩展点
        for (ServletFilterSpanDecorator spanDecorator: spanDecorators) {
            spanDecorator.onRequest(httpRequest, span);
        }
        // 将创立出来的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) {
            // 在恳求反常时运用装修器来装修Span
            for (ServletFilterSpanDecorator spanDecorator : spanDecorators) {
                spanDecorator.onError(httpRequest, httpResponse, ex, span);
            }
            throw ex;
        } finally {
            if (httpRequest.isAsyncStarted()) {
                // 异步Servlet场景下增加监听器
                httpRequest.getAsyncContext()
                        .addListener(new AsyncListener() {
                            @Override
                            public void onComplete(AsyncEvent event) throws IOException {
                                HttpServletRequest httpRequest = (HttpServletRequest) event.getSuppliedRequest();
                                HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse();
                                // 异步操作完结时运用装修器装修Span
                                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();
                                // 异步操作超时时运用装修器装修Span
                                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();
                                // 异步操作反常时运用装修器装修Span
                                for (ServletFilterSpanDecorator spanDecorator: spanDecorators) {
                                    spanDecorator.onError(httpRequest,
                                            httpResponse,
                                            event.getThrowable(),
                                            span);
                                }
                            }
                            @Override
                            public void onStartAsync(AsyncEvent event) throws IOException {
                            }
                        });
            } else {
                // 恳求完结时设置完结时间并记载Span
                span.finish();
            }
        }
    }
}

经过阅读TracingFilter源码,咱们能够得到如下几种扩展增强。

  1. Servlet本身的urlPatterns机制。能够经过装备urlPatterns,决定哪些恳求需要打印链路信息;
  2. TracingFilterskipPattern机制。能够经过装备skipPattern,决定哪些恳求不需要打印链路信息;
  3. 装修器ServletFilterSpanDecorator。能够供给ServletFilterSpanDecorator给到TracingFilter,这样在收到恳求,回来呼应和处理反常时均能够做一些扩展操作;

二. urlPatterns和skipPattern规划

在第一节中得到的TracingFilter的几种增强,其间第1和第2点的urlPatternsskipPattern,能够供给出来供用户装备,本节对这部分进行完成。

首先是装备属性类里边需要参加urlPatternsskipPattern的装备属性,如下所示。

/**
 * 分布式链路追寻装备属性类。
 */
@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需要做如下修正。

/**
 * 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<>();
        // 为TracingFilter增加注册时的urlPattern
        registrationBean.addUrlPatterns(urlPattern);
        // TracingFilter的优先级要尽或许的高
        registrationBean.setOrder(Integer.MIN_VALUE);
        // 创立TracingFilter时指定装修器调集和skipPattern
        // 这儿暂时传入空的装修器调集
        registrationBean.setFilter(new TracingFilter(tracer, new ArrayList<>(), skipPattern));
        return registrationBean;
    }
}

三. TracingFilter的装修器规划

经过为TracingFilter注册ServletFilterSpanDecorator装修器,能够让咱们在收到恳求,回来呼应和处理反常时做一些扩展操作,例如记载恳求urlapi和回来码等,下面完成一个装修器HoneyServletFilterSpanDecorator,其供给如下几个功用。

收到恳求时记载:

  1. 恳求的host
  2. 恳求的api

回来呼应时记载:

  1. 呼应码。

处理反常时记载:

  1. 呼应码。

完成如下。

/**
 * {@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) {
        // todo
    }
    private String getHostFromRequest(HttpServletRequest httpServletRequest) {
        return httpServletRequest.getScheme()
                + "://"
                + httpServletRequest.getServerName()
                + ":"
                + httpServletRequest.getServerPort();
    }
}

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

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修正如下。

/**
 * 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<>();
        // 为TracingFilter增加注册时的urlPattern
        registrationBean.addUrlPatterns(urlPattern);
        // TracingFilter的优先级要尽或许的高
        registrationBean.setOrder(Integer.MIN_VALUE);
        // 创立TracingFilter时指定装修器调集和skipPattern
        // 这儿暂时传入空的装修器调集
        registrationBean.setFilter(new TracingFilter(tracer, decorators, skipPattern));
        return registrationBean;
    }
    @Bean
    public ServletFilterSpanDecorator honeyServletFilterSpanDecorator() {
        return new HoneyServletFilterSpanDecorator();
    }
}

至此,咱们就运用装修器装修了TracingFilter,作用便是终究在TracingFilter调用到Spanfinish() 方法时,咱们能够从Spantags中拿到本次恳求的hostapihttpCode,这些数据能够终究在打印链路日志时运用。

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

5. 分布式链路追寻TracingFilter改造增强规划

总结

本文在4. 分布式链路追寻客户端工具包Starter规划的基础上,运用TracingFilter替换了咱们自己完成的HoneyTracingFilter,而且基于urlPatternsskipPattern和装修器进行了扩展增强。