前言

在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的源码实现。

@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源码,我们可以得到如下几种扩展增强。

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

二. urlPatterns和skipPattern设计

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

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

* 分布式链路追踪配置属性类。

*/

@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 honeyTracingFilter(Tracer tracer) {

String urlPattern = honeyTracingProperties.getHttpUrl().getUrlPattern();

String skipPatternStr = honeyTracingProperties.getHttpUrl().getSkipPattern();

Pattern skipPattern = Pattern.compile(skipPatternStr);

FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();

registrationBean.addUrlPatterns(urlPattern);

registrationBean.setOrder(Integer.MIN_VALUE);

registrationBean.setFilter(new TracingFilter(tracer, new ArrayList<>(), skipPattern));

return registrationBean;

}

}

三. TracingFilter的装饰器设计

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

收到请求时记录:

请求的host;请求的api。

返回响应时记录:

响应码。

处理异常时记录:

响应码。

实现如下。

* {@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中,如下所示。

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 honeyTracingFilter(Tracer tracer,

List decorators) {

String urlPattern = honeyTracingProperties.getHttpUrl().getUrlPattern();

String skipPatternStr = honeyTracingProperties.getHttpUrl().getSkipPattern();

Pattern skipPattern = Pattern.compile(skipPatternStr);

FilterRegistrationBean 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调用到Span的finish() 方法时,我们可以从Span的tags中拿到本次请求的host,api和httpCode,这些数据可以最终在打印链路日志时使用。

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

总结

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

好文推荐

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。