Make TraceKeys configurable
User can now configure additional HTTP headers to tag via spring.sleuth.keys.http.headers. Fixes gh-118
This commit is contained in:
@@ -16,80 +16,103 @@
|
||||
|
||||
package org.springframework.cloud.sleuth.instrument;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Well-known {@link org.springframework.cloud.sleuth.Span#tag(String, String) span tag} keys.
|
||||
* Well-known {@link org.springframework.cloud.sleuth.Span#tag(String, String) span tag}
|
||||
* keys.
|
||||
*
|
||||
* <h3>Overhead of adding Trace Data</h3>
|
||||
*
|
||||
* Overhead is directly related to the size of trace data exported out of process. Accordingly, it
|
||||
* is better to tag what's important for latency troubleshooting, ie a whitelist, vs collecting
|
||||
* everything and filtering downstream. The keys listed here are very common in tracing tools, and
|
||||
* are considerate to the issue of overhead.
|
||||
* Overhead is directly related to the size of trace data exported out of process.
|
||||
* Accordingly, it is better to tag what's important for latency troubleshooting, i.e. a
|
||||
* whitelist vs. collecting everything and filtering downstream. The keys listed here are
|
||||
* very common in tracing tools, and are considerate to the issue of overhead.
|
||||
*
|
||||
* <p>When evaluating new keys, consider how much additional data it implies, and if that data is
|
||||
* critical to classifying, filtering or displaying traces. More data often means larger systems,
|
||||
* less retention, or a lower sample rate.
|
||||
* <p>
|
||||
* When evaluating new keys, consider how much additional data it implies, and if that
|
||||
* data is critical to classifying, filtering or displaying traces. More data often means
|
||||
* larger systems, less retention, or a lower sample rate.
|
||||
*
|
||||
* <p>For example, in zipkin, a thrift-encoded span with an "sr" annotation is 82 bytes plus the
|
||||
* size of its name and associated service. The maximum size of an HTTP cookie is 4096 bytes,
|
||||
* roughly 50x that. Even if compression helps, if you aren't analyzing based on cookies, storing
|
||||
* them displaces resources that could be used for more traces. Meanwhile, you have another system
|
||||
* storing private data! The takeaway isn't never store cookies, as there are valid cases for this.
|
||||
* The takeaway is to be conscious about what's you are storing.
|
||||
* <p>
|
||||
* For example, in zipkin, a thrift-encoded span with an "sr" annotation is 82 bytes plus
|
||||
* the size of its name and associated service. The maximum size of an HTTP cookie is 4096
|
||||
* bytes, roughly 50x that. Even if compression helps, if you aren't analyzing based on
|
||||
* cookies, storing them displaces resources that could be used for more traces.
|
||||
* Meanwhile, you have another system storing private data! The takeaway isn't never store
|
||||
* cookies, as there are valid cases for this. The takeaway is to be conscious about
|
||||
* what's you are storing.
|
||||
*/
|
||||
public final class TraceKeys {
|
||||
@ConfigurationProperties("spring.sleuth.keys")
|
||||
@Data
|
||||
public class TraceKeys {
|
||||
|
||||
/**
|
||||
* The domain portion of the URL or host header. Ex. "mybucket.s3.amazonaws.com"
|
||||
*
|
||||
* <p>Used to filter by host as opposed to ip address.
|
||||
*/
|
||||
public static final String HTTP_HOST = "http/host";
|
||||
private Http http = new Http();
|
||||
|
||||
/**
|
||||
* The HTTP method, or verb, such as "GET" or "POST".
|
||||
*
|
||||
* <p>Used to filter against an http route.
|
||||
*/
|
||||
public static final String HTTP_METHOD = "http/method";
|
||||
@Data
|
||||
public static class Http {
|
||||
|
||||
/**
|
||||
* The absolute http path, without any query parameters. Ex. "/objects/abcd-ff"
|
||||
*
|
||||
* <p>Used to filter against an http route, portably with zipkin v1.
|
||||
*
|
||||
* <p>In zipkin v1, only equals filters are supported. Dropping query parameters makes the number
|
||||
* of distinct URIs less. For example, one can query for the same resource, regardless of signing
|
||||
* parameters encoded in the query line. This does not reduce cardinality to a HTTP single route.
|
||||
* For example, it is common to express a route as an http URI template like
|
||||
* "/resource/{resource_id}". In systems where only equals queries are available, searching for
|
||||
* {@code http.uri=/resource} won't match if the actual request was "/resource/abcd-ff".
|
||||
*
|
||||
* <p>Historical note: This was commonly expressed as "http.uri" in zipkin, eventhough it was most
|
||||
* often just a path.
|
||||
*/
|
||||
public static final String HTTP_PATH = "http/path";
|
||||
/**
|
||||
* The domain portion of the URL or host header. Example:
|
||||
* "mybucket.s3.amazonaws.com". Used to filter by host as opposed to ip address.
|
||||
*/
|
||||
private String host = "http/host";
|
||||
|
||||
/**
|
||||
* The entire URL, including the scheme, host and query parameters if available. Ex.
|
||||
* "https://mybucket.s3.amazonaws.com/objects/abcd-ff?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Algorithm=AWS4-HMAC-SHA256..."
|
||||
*
|
||||
* <p>Combined with {@link #HTTP_METHOD}, you can understand the fully-qualified request line.
|
||||
*
|
||||
* <p>This is optional as it may include private data or be of considerable length.
|
||||
*/
|
||||
public static final String HTTP_URL = "http/url";
|
||||
/**
|
||||
* The HTTP method, or verb, such as "GET" or "POST". Used to filter against an
|
||||
* http route.
|
||||
*/
|
||||
private String method = "http/method";
|
||||
|
||||
/**
|
||||
* The HTTP response code, when not in 2xx range. Ex. "503"
|
||||
*
|
||||
* <p>Used to filter for error status.
|
||||
*
|
||||
* <p>2xx range are not logged as success codes are less interesting for latency troubleshooting.
|
||||
* Omitting saves at least 20 bytes per span.
|
||||
*/
|
||||
public static final String HTTP_STATUS_CODE = "http/status_code";
|
||||
/**
|
||||
* The absolute http path, without any query parameters. Example:
|
||||
* "/objects/abcd-ff". Used to filter against an http route, portably with zipkin
|
||||
* v1. In zipkin v1, only equals filters are supported. Dropping query parameters
|
||||
* makes the number of distinct URIs less. For example, one can query for the same
|
||||
* resource, regardless of signing parameters encoded in the query line. This does
|
||||
* not reduce cardinality to a HTTP single route. For example, it is common to
|
||||
* express a route as an http URI template like "/resource/{resource_id}". In
|
||||
* systems where only equals queries are available, searching for
|
||||
* {@code http.uri=/resource} won't match if the actual request was
|
||||
* "/resource/abcd-ff". Historical note: This was commonly expressed as "http.uri"
|
||||
* in zipkin, eventhough it was most often just a path.
|
||||
*/
|
||||
private String path = "http/path";
|
||||
|
||||
/**
|
||||
* The entire URL, including the scheme, host and query parameters if available.
|
||||
* Ex.
|
||||
* "https://mybucket.s3.amazonaws.com/objects/abcd-ff?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Algorithm=AWS4-HMAC-SHA256..."
|
||||
* Combined with {@link #HTTP_METHOD}, you can understand the fully-qualified
|
||||
* request line. This is optional as it may include private data or be of
|
||||
* considerable length.
|
||||
*/
|
||||
private String url = "http/url";
|
||||
|
||||
/**
|
||||
* The HTTP response code, when not in 2xx range. Ex. "503" Used to filter for
|
||||
* error status. 2xx range are not logged as success codes are less interesting
|
||||
* for latency troubleshooting. Omitting saves at least 20 bytes per span.
|
||||
*/
|
||||
private String statusCode = "http/status_code";
|
||||
|
||||
/**
|
||||
* Prefix for header names if they are added as tags.
|
||||
*/
|
||||
private String prefix = "http/";
|
||||
|
||||
/**
|
||||
* Additional headers that should be added as tags if they exist. If the header
|
||||
* value is multi-valued, the tag value will be a comma-separated, single-quoted
|
||||
* list.
|
||||
*/
|
||||
private Collection<String> headers = new LinkedHashSet<String>();
|
||||
|
||||
}
|
||||
|
||||
private TraceKeys() { // no instances
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,25 @@
|
||||
*/
|
||||
package org.springframework.cloud.sleuth.instrument.web;
|
||||
|
||||
import org.springframework.cloud.sleuth.*;
|
||||
import static org.springframework.util.StringUtils.hasText;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Random;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.cloud.sleuth.MilliSpan;
|
||||
import org.springframework.cloud.sleuth.MilliSpan.MilliSpanBuilder;
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.Trace;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.event.ServerReceivedEvent;
|
||||
import org.springframework.cloud.sleuth.event.ServerSentEvent;
|
||||
import org.springframework.cloud.sleuth.instrument.TraceKeys;
|
||||
@@ -31,16 +48,6 @@ import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.springframework.util.StringUtils.hasText;
|
||||
|
||||
/**
|
||||
* Filter that takes the value of the {@link Trace#SPAN_ID_NAME} and
|
||||
* {@link Trace#TRACE_ID_NAME} header from either request or response and uses them to
|
||||
@@ -71,6 +78,7 @@ public class TraceFilter extends OncePerRequestFilter
|
||||
"/api-docs.*|/autoconfig|/configprops|/dump|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico|/hystrix.stream");
|
||||
|
||||
private final Tracer tracer;
|
||||
private final TraceKeys traceKeys;
|
||||
private final Pattern skipPattern;
|
||||
private final Random random;
|
||||
|
||||
@@ -78,14 +86,13 @@ public class TraceFilter extends OncePerRequestFilter
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
|
||||
public TraceFilter(Tracer tracer) {
|
||||
this.tracer = tracer;
|
||||
this.skipPattern = DEFAULT_SKIP_PATTERN;
|
||||
this.random = new Random();
|
||||
public TraceFilter(Tracer tracer, TraceKeys traceKeys) {
|
||||
this(tracer, traceKeys, DEFAULT_SKIP_PATTERN, new Random());
|
||||
}
|
||||
|
||||
public TraceFilter(Tracer tracer, Pattern skipPattern, Random random) {
|
||||
public TraceFilter(Tracer tracer, TraceKeys traceKeys, Pattern skipPattern, Random random) {
|
||||
this.tracer = tracer;
|
||||
this.traceKeys = traceKeys;
|
||||
this.skipPattern = skipPattern;
|
||||
this.random = random;
|
||||
}
|
||||
@@ -117,7 +124,7 @@ public class TraceFilter extends OncePerRequestFilter
|
||||
if (hasHeader(request, response, Trace.TRACE_ID_NAME)) {
|
||||
long traceId = Span.IdConverter.fromHex(getHeader(request, response, Trace.TRACE_ID_NAME));
|
||||
long spanId = hasHeader(request, response, Trace.SPAN_ID_NAME) ?
|
||||
Span.IdConverter.fromHex(getHeader(request, response, Trace.SPAN_ID_NAME)) : random.nextLong();
|
||||
Span.IdConverter.fromHex(getHeader(request, response, Trace.SPAN_ID_NAME)) : this.random.nextLong();
|
||||
|
||||
MilliSpanBuilder span = MilliSpan.builder().traceId(traceId).spanId(spanId);
|
||||
if (skip) {
|
||||
@@ -203,10 +210,22 @@ public class TraceFilter extends OncePerRequestFilter
|
||||
/** Override to add annotations not defined in {@link TraceKeys}. */
|
||||
protected void addRequestTags(HttpServletRequest request) {
|
||||
String uri = this.urlPathHelper.getPathWithinApplication(request);
|
||||
this.tracer.addTag(TraceKeys.HTTP_URL, getFullUrl(request));
|
||||
this.tracer.addTag(TraceKeys.HTTP_HOST, request.getServerName());
|
||||
this.tracer.addTag(TraceKeys.HTTP_PATH, uri);
|
||||
this.tracer.addTag(TraceKeys.HTTP_METHOD, request.getMethod());
|
||||
this.tracer.addTag(this.traceKeys.getHttp().getUrl(), getFullUrl(request));
|
||||
this.tracer.addTag(this.traceKeys.getHttp().getHost(),
|
||||
request.getServerName());
|
||||
this.tracer.addTag(this.traceKeys.getHttp().getPath(), uri);
|
||||
this.tracer.addTag(this.traceKeys.getHttp().getMethod(),
|
||||
request.getMethod());
|
||||
for (String name : this.traceKeys.getHttp().getHeaders()) {
|
||||
Enumeration<String> values = request.getHeaders(name);
|
||||
if (values.hasMoreElements()) {
|
||||
String key = this.traceKeys.getHttp().getPrefix() + name.toLowerCase();
|
||||
ArrayList<String> list = Collections.list(values);
|
||||
String value = list.size() == 1 ? list.get(0)
|
||||
: StringUtils.collectionToDelimitedString(list, ",", "'", "'");
|
||||
this.tracer.addTag(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Override to add annotations not defined in {@link TraceKeys}. */
|
||||
@@ -215,11 +234,11 @@ public class TraceFilter extends OncePerRequestFilter
|
||||
if (httpStatus == HttpServletResponse.SC_OK && e != null) {
|
||||
// Filter chain threw exception but the response status may not have been set
|
||||
// yet, so we have to guess.
|
||||
this.tracer.addTag(TraceKeys.HTTP_STATUS_CODE,
|
||||
this.tracer.addTag(this.traceKeys.getHttp().getStatusCode(),
|
||||
String.valueOf(HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
|
||||
}
|
||||
else if ((httpStatus < 200) || (httpStatus > 299)){
|
||||
this.tracer.addTag(TraceKeys.HTTP_STATUS_CODE,
|
||||
this.tracer.addTag(this.traceKeys.getHttp().getStatusCode(),
|
||||
String.valueOf(response.getStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.embedded.FilterRegistrationBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.sleuth.TraceAccessor;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
|
||||
import org.springframework.cloud.sleuth.instrument.TraceKeys;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -47,6 +49,7 @@ import org.springframework.util.StringUtils;
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnBean(Tracer.class)
|
||||
@AutoConfigureAfter(TraceAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(TraceKeys.class)
|
||||
public class TraceWebAutoConfiguration {
|
||||
|
||||
/**
|
||||
@@ -61,6 +64,9 @@ public class TraceWebAutoConfiguration {
|
||||
@Autowired
|
||||
private TraceAccessor accessor;
|
||||
|
||||
@Autowired
|
||||
private TraceKeys traceKeys;
|
||||
|
||||
@Bean
|
||||
public TraceWebAspect traceWebAspect() {
|
||||
return new TraceWebAspect(this.tracer, this.accessor);
|
||||
@@ -71,7 +77,7 @@ public class TraceWebAutoConfiguration {
|
||||
public TraceFilter traceFilter(ApplicationEventPublisher publisher, Random random) {
|
||||
Pattern pattern = StringUtils.hasText(this.skipPattern) ? Pattern.compile(this.skipPattern)
|
||||
: TraceFilter.DEFAULT_SKIP_PATTERN;
|
||||
TraceFilter filter = new TraceFilter(this.tracer, pattern, random);
|
||||
TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, pattern, random);
|
||||
filter.setApplicationEventPublisher(publisher);
|
||||
return filter;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user