前言
在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源码,我们可以得到如下几种扩展增强。
- Servlet 自身的urlPatterns 机制。可以通过配置urlPatterns,决定哪些请求需要打印链路信息;
- TracingFilter 的skipPattern 机制。可以通过配置skipPattern,决定哪些请求不需要打印链路信息;
- 装饰器ServletFilterSpanDecorator 。可以提供ServletFilterSpanDecorator 给到TracingFilter,这样在收到请求,返回响应和处理异常时均可以做一些扩展操作;
二. urlPatterns和skipPattern设计
在第一节中得到的TracingFilter 的几种增强,其中第1和第2点的urlPatterns 和skipPattern,可以提供出来供用户配置,本节对这部分进行实现。
首先是配置属性类里面需要加入urlPatterns 和skipPattern的配置属性,如下所示。
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 装饰器,可以让我们在收到请求,返回响应和处理异常时做一些扩展操作,例如记录请求url ,api 和返回码等,下面实现一个装饰器HoneyServletFilterSpanDecorator,其提供如下几个功能。
收到请求时记录:
- 请求的host;
- 请求的api。
返回响应时记录:
- 响应码。
处理异常时记录:
- 响应码。
实现如下。
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 调用到Span 的finish() 方法时,我们可以从Span 的tags 中拿到本次请求的host ,api 和httpCode,这些数据可以最终在打印链路日志时使用。
最后给出工程目录结构图。
总结
本文在4. 分布式链路追踪客户端工具包Starter设计的基础上,使用TracingFilter 替换了我们自己实现的HoneyTracingFilter ,并且基于urlPatterns ,skipPattern和装饰器进行了扩展增强。