Merge pull request #115 from spring-cloud/pare-down-http

Pares down default http tags, but opens apis for adding more
This commit is contained in:
Adrian Cole
2016-01-19 12:50:56 +08:00
13 changed files with 182 additions and 75 deletions

View File

@@ -88,9 +88,9 @@ public interface TraceManager extends TraceAccessor {
Trace continueSpan(Span s);
/**
* Adds a data annotation to the current span if tracing is currently on.
* Adds a tag to the current span if tracing is currently on.
*/
void addAnnotation(String key, String value);
void addTag(String key, String value);
/**
* Remove this span from the current thread, but don't stop it yet or send it for

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument;
/**
* 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.
*
* <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.
*/
public final 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";
/**
* 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";
/**
* 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 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 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";
private TraceKeys() { // no instances
}
}

View File

@@ -18,7 +18,6 @@ package org.springframework.cloud.sleuth.instrument.web;
import static org.springframework.util.StringUtils.hasText;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Random;
import java.util.regex.Pattern;
@@ -34,6 +33,7 @@ import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.TraceManager;
import org.springframework.cloud.sleuth.event.ServerReceivedEvent;
import org.springframework.cloud.sleuth.event.ServerSentEvent;
import org.springframework.cloud.sleuth.instrument.TraceKeys;
import org.springframework.cloud.sleuth.sampler.IsTracingSampler;
import org.springframework.cloud.sleuth.trace.TraceContextHolder;
import org.springframework.context.ApplicationEvent;
@@ -50,7 +50,13 @@ import org.springframework.web.util.UrlPathHelper;
* {@link Trace#TRACE_ID_NAME} header from either request or response and uses them to
* create a new span.
*
* <p>In order to keep the size of spans manageable, this only add tags defined in {@link TraceKeys}.
* If you need to add additional tags, such as headers subtype this and override
* {@link #addRequestTags} or {@link #addResponseTags}.
*
* @see TraceManager
* @see TraceKeys
* @see TraceWebAutoConfiguration#traceWebFilter(TraceFilter)
*
* @author Jakub Nabrdalik, 4financeIT
* @author Tomasz Nurkiewicz, 4financeIT
@@ -156,7 +162,7 @@ public class TraceFilter extends OncePerRequestFilter
Throwable exception = null;
try {
addRequestAnnotations(request);
addRequestTags(request);
filterChain.doFilter(request, response);
}
@@ -173,7 +179,7 @@ public class TraceFilter extends OncePerRequestFilter
addToResponseIfNotPresent(response, Trace.NOT_SAMPLED_NAME, "");
}
if (trace != null) {
addResponseAnnotations(response, exception);
addResponseTags(response, exception);
addResponseHeaders(response, trace.getSpan());
if (trace.getSaved() != null) {
publish(new ServerSentEvent(this, trace.getSaved().getSpan(),
@@ -198,45 +204,28 @@ public class TraceFilter extends OncePerRequestFilter
}
}
// TODO: move annotation keys to constants
protected void addRequestAnnotations(HttpServletRequest request) {
/** Override to add annotations not defined in {@link TraceKeys}. */
protected void addRequestTags(HttpServletRequest request) {
String uri = this.urlPathHelper.getPathWithinApplication(request);
this.traceManager.addAnnotation("/http/request/uri",
request.getRequestURL().toString());
this.traceManager.addAnnotation("/http/request/endpoint", uri);
this.traceManager.addAnnotation("/http/request/method", request.getMethod());
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
String value = values.nextElement();
String key = "/http/request/headers/" + name.toLowerCase();
this.traceManager.addAnnotation(key, value);
}
}
this.traceManager.addTag(TraceKeys.HTTP_URL, getFullUrl(request));
this.traceManager.addTag(TraceKeys.HTTP_HOST, request.getServerName());
this.traceManager.addTag(TraceKeys.HTTP_PATH, uri);
this.traceManager.addTag(TraceKeys.HTTP_METHOD, request.getMethod());
}
private void addResponseAnnotations(HttpServletResponse response, Throwable e) {
if (response.getStatus() == HttpServletResponse.SC_OK && e != null) {
/** Override to add annotations not defined in {@link TraceKeys}. */
protected void addResponseTags(HttpServletResponse response, Throwable e) {
int httpStatus = response.getStatus();
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.traceManager.addAnnotation("/http/response/status_code",
this.traceManager.addTag(TraceKeys.HTTP_STATUS_CODE,
String.valueOf(HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
}
else {
this.traceManager.addAnnotation("/http/response/status_code",
else if ((httpStatus < 200) || (httpStatus > 299)){
this.traceManager.addTag(TraceKeys.HTTP_STATUS_CODE,
String.valueOf(response.getStatus()));
}
for (String name : response.getHeaderNames()) {
for (String value : response.getHeaders(name)) {
String key = "/http/response/headers/" + name.toLowerCase();
this.traceManager.addAnnotation(key, value);
}
}
}
private boolean hasHeader(HttpServletRequest request, HttpServletResponse response,
@@ -262,4 +251,15 @@ public class TraceFilter extends OncePerRequestFilter
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
private String getFullUrl(HttpServletRequest request) {
StringBuffer requestURI = request.getRequestURL();
String queryString = request.getQueryString();
if (queryString == null) {
return requestURI.toString();
} else {
return requestURI.append('?').append(queryString).toString();
}
}
}

View File

@@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
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;
@@ -66,12 +67,17 @@ public class TraceWebAutoConfiguration {
}
@Bean
public FilterRegistrationBean traceWebFilter(ApplicationEventPublisher publisher, Random random) {
@ConditionalOnMissingBean
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.traceManager, pattern, random);
filter.setApplicationEventPublisher(publisher);
return new FilterRegistrationBean(filter);
return filter;
}
@Bean
public FilterRegistrationBean traceWebFilter(TraceFilter filter) {
return new FilterRegistrationBean(filter);
}
}

View File

@@ -198,7 +198,7 @@ public class DefaultTraceManager implements TraceManager {
}
@Override
public void addAnnotation(String key, String value) {
public void addTag(String key, String value) {
Span s = getCurrentSpan();
if (s != null && s.isExportable()) {
s.tag(key, value);

View File

@@ -39,6 +39,8 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde
import java.util.Random;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -80,7 +82,7 @@ public class TraceFilterTests {
}
public MockHttpServletRequestBuilder builder() {
return get("/").accept(MediaType.APPLICATION_JSON).header("User-Agent",
return get("/?foo=bar").accept(MediaType.APPLICATION_JSON).header("User-Agent",
"MockMvc");
}
@@ -102,7 +104,7 @@ public class TraceFilterTests {
public void startsNewTrace() throws Exception {
TraceFilter filter = new TraceFilter(this.traceManager);
filter.doFilter(this.request, this.response, this.filterChain);
verifyHttpAnnotations();
verifyHttpTags();
assertNull(TraceContextHolder.getCurrentTrace());
}
@@ -115,7 +117,7 @@ public class TraceFilterTests {
TraceFilter filter = new TraceFilter(this.traceManager);
filter.doFilter(this.request, this.response, this.filterChain);
verifyHttpAnnotations();
verifyHttpTags();
assertNull(TraceContextHolder.getCurrentTrace());
}
@@ -129,7 +131,7 @@ public class TraceFilterTests {
TraceFilter filter = new TraceFilter(this.traceManager);
filter.doFilter(this.request, this.response, this.filterChain);
verifyHttpAnnotations();
verifyHttpTags();
assertNull(TraceContextHolder.getCurrentTrace());
}
@@ -151,30 +153,34 @@ public class TraceFilterTests {
catch (RuntimeException e) {
assertEquals("Planned", e.getMessage());
}
verifyHttpAnnotations(HttpStatus.INTERNAL_SERVER_ERROR);
verifyHttpTags(HttpStatus.INTERNAL_SERVER_ERROR);
assertNull(TraceContextHolder.getCurrentTrace());
}
public void verifyHttpAnnotations() {
verifyHttpAnnotations(HttpStatus.OK);
public void verifyHttpTags() {
verifyHttpTags(HttpStatus.OK);
}
public void verifyHttpAnnotations(HttpStatus status) {
hasAnnotation(this.span, "/http/request/uri", "http://localhost/");
hasAnnotation(this.span, "/http/request/endpoint", "/");
hasAnnotation(this.span, "/http/request/method", "GET");
hasAnnotation(this.span, "/http/request/headers/accept",
MediaType.APPLICATION_JSON_VALUE);
hasAnnotation(this.span, "/http/request/headers/user-agent", "MockMvc");
/**
* Shows the expansion of {@link import org.springframework.cloud.sleuth.instrument.TraceKeys}.
*/
public void verifyHttpTags(HttpStatus status) {
assertThat(this.span.tags()).contains(
entry("http/host", "localhost"),
entry("http/url", "http://localhost/?foo=bar"),
entry("http/path", "/"),
entry("http/method", "GET")
);
hasAnnotation(this.span, "/http/response/status_code", status.toString());
hasAnnotation(this.span, "/http/response/headers/content-type",
MediaType.APPLICATION_JSON_VALUE);
}
private void hasAnnotation(Span span, String name, String value) {
assertEquals(value, span.tags().get(name));
// Status is only interesting in non-success case. Omitting it saves at least 20bytes per span.
if (status.is2xxSuccessful()) {
assertThat(this.span.tags())
.doesNotContainKey("http/status_code");
} else {
assertThat(this.span.tags())
.containsEntry("http/status_code", status.toString());
}
}
private class DelegateSampler implements Sampler<Void> {

View File

@@ -40,7 +40,7 @@ public class SampleBackground {
public void background() {
int millis = random.nextInt(1000);
Thread.sleep(millis);
this.traceManager.addAnnotation("background-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("background-sleep-millis", String.valueOf(millis));
}
}

View File

@@ -40,7 +40,7 @@ public class SampleBackground {
final Random random = new Random();
int millis = random.nextInt(1000);
Thread.sleep(millis);
this.traceManager.addAnnotation("background-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("background-sleep-millis", String.valueOf(millis));
}
}

View File

@@ -69,7 +69,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
public String call() throws Exception {
int millis = random.nextInt(1000);
Thread.sleep(millis);
SampleController.this.traceManager.addAnnotation("callable-sleep-millis", String.valueOf(millis));
SampleController.this.traceManager.addTag("callable-sleep-millis", String.valueOf(millis));
Span currentSpan = SampleController.this.accessor.getCurrentSpan();
return "async hi: " + currentSpan;
}
@@ -87,7 +87,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
public String hi2() {
int millis = random.nextInt(1000);
Thread.sleep(millis);
this.traceManager.addAnnotation("random-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("random-sleep-millis", String.valueOf(millis));
return "hi2";
}
@@ -99,7 +99,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
int millis = random.nextInt(1000);
log.info("Sleeping for {} millis", millis);
Thread.sleep(millis);
this.traceManager.addAnnotation("random-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("random-sleep-millis", String.valueOf(millis));
String s = this.restTemplate.getForObject("http://localhost:" + this.port
+ "/call", String.class);
@@ -113,7 +113,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
int millis = random.nextInt(1000);
log.info("Sleeping for {} millis", millis);
Thread.sleep(millis);
this.traceManager.addAnnotation("random-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("random-sleep-millis", String.valueOf(millis));
String s = this.restTemplate.getForObject("http://localhost:" + this.port
+ "/call", String.class);

View File

@@ -40,7 +40,7 @@ public class SampleBackground {
public void background() {
int millis = random.nextInt(1000);
Thread.sleep(millis);
this.traceManager.addAnnotation("background-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("background-sleep-millis", String.valueOf(millis));
}
}

View File

@@ -69,7 +69,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
public String call() throws Exception {
int millis = random.nextInt(1000);
Thread.sleep(millis);
SampleController.this.traceManager.addAnnotation("callable-sleep-millis", String.valueOf(millis));
SampleController.this.traceManager.addTag("callable-sleep-millis", String.valueOf(millis));
Span currentSpan = SampleController.this.accessor.getCurrentSpan();
return "async hi: " + currentSpan;
}
@@ -87,7 +87,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
public String hi2() {
int millis = random.nextInt(1000);
Thread.sleep(millis);
this.traceManager.addAnnotation("random-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("random-sleep-millis", String.valueOf(millis));
return "hi2";
}
@@ -99,7 +99,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
int millis = random.nextInt(1000);
log.info("Sleeping for {} millis", millis);
Thread.sleep(millis);
this.traceManager.addAnnotation("random-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("random-sleep-millis", String.valueOf(millis));
String s = this.restTemplate.getForObject("http://localhost:" + this.port
+ "/call", String.class);
@@ -113,7 +113,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
int millis = random.nextInt(1000);
log.info("Sleeping for {} millis", millis);
Thread.sleep(millis);
this.traceManager.addAnnotation("random-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("random-sleep-millis", String.valueOf(millis));
String s = this.restTemplate.getForObject("http://localhost:" + this.port
+ "/call", String.class);

View File

@@ -40,7 +40,7 @@ public class SampleBackground {
public void background() {
int millis = random.nextInt(1000);
Thread.sleep(millis);
this.traceManager.addAnnotation("background-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("background-sleep-millis", String.valueOf(millis));
}
}

View File

@@ -69,7 +69,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
public String call() throws Exception {
int millis = random.nextInt(1000);
Thread.sleep(millis);
SampleController.this.traceManager.addAnnotation("callable-sleep-millis", String.valueOf(millis));
SampleController.this.traceManager.addTag("callable-sleep-millis", String.valueOf(millis));
Span currentSpan = SampleController.this.accessor.getCurrentSpan();
return "async hi: " + currentSpan;
}
@@ -87,7 +87,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
public String hi2() {
int millis = random.nextInt(1000);
Thread.sleep(millis);
this.traceManager.addAnnotation("random-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("random-sleep-millis", String.valueOf(millis));
return "hi2";
}
@@ -99,7 +99,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
int millis = random.nextInt(1000);
log.info("Sleeping for {} millis", millis);
Thread.sleep(millis);
this.traceManager.addAnnotation("random-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("random-sleep-millis", String.valueOf(millis));
String s = this.restTemplate.getForObject("http://localhost:" + this.port
+ "/call", String.class);
@@ -113,7 +113,7 @@ ApplicationListener<EmbeddedServletContainerInitializedEvent> {
int millis = random.nextInt(1000);
log.info("Sleeping for {} millis", millis);
Thread.sleep(millis);
this.traceManager.addAnnotation("random-sleep-millis", String.valueOf(millis));
this.traceManager.addTag("random-sleep-millis", String.valueOf(millis));
String s = this.restTemplate.getForObject("http://localhost:" + this.port
+ "/call", String.class);