Initial implementation of http annotations

Part of gh-11
This commit is contained in:
Spencer Gibb
2015-08-04 14:37:46 -06:00
parent 4a7a9deefe
commit a16fbab0a1
3 changed files with 177 additions and 8 deletions

View File

@@ -23,6 +23,7 @@ import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME;
import static org.springframework.util.StringUtils.hasText;
import java.io.IOException;
import java.util.Enumeration;
import java.util.regex.Pattern;
import javax.servlet.FilterChain;
@@ -50,11 +51,12 @@ import org.springframework.web.util.UrlPathHelper;
* @author Tomasz Nurkiewicz, 4financeIT
* @author Marcin Grzejszczak, 4financeIT
* @author Spencer Gibb
* @author Dave Syer
*/
@Order(Ordered.HIGHEST_PRECEDENCE + 5)
public class TraceFilter extends OncePerRequestFilter {
private static final String TRACE_REQUEST_ATTR = TraceFilter.class.getName()
protected static final String TRACE_REQUEST_ATTR = TraceFilter.class.getName()
+ ".TRACE";
public static final Pattern DEFAULT_SKIP_PATTERN = Pattern
@@ -92,26 +94,28 @@ public class TraceFilter extends OncePerRequestFilter {
String name = "http" + uri;
if (hasText(spanId) && hasText(traceId)) {
MilliSpanBuilder traceInfo = MilliSpan.builder().traceId(traceId)
MilliSpanBuilder span = MilliSpan.builder().traceId(traceId)
.spanId(spanId);
String parentId = getHeader(request, response, PARENT_ID_NAME);
String processId = getHeader(request, response, PROCESS_ID_NAME);
String parentName = getHeader(request, response, SPAN_NAME_NAME);
if (parentName != null) {
traceInfo.name(parentName);
span.name(parentName);
}
if (processId != null) {
traceInfo.processId(processId);
span.processId(processId);
}
if (parentId != null) {
traceInfo.parent(parentId);
span.parent(parentId);
}
traceInfo.remote(true);
span.remote(true);
// TODO: trace description?
traceScope = this.trace.startSpan(name, traceInfo.build());
traceScope = this.trace.startSpan(name, span.build());
request.setAttribute(TRACE_REQUEST_ATTR, traceScope);
// Send new span id back
addToResponseIfNotPresent(response, TRACE_ID_NAME, traceScope.getSpan()
.getTraceId());
addToResponseIfNotPresent(response, SPAN_ID_NAME, traceScope.getSpan()
.getSpanId());
}
@@ -121,18 +125,57 @@ public class TraceFilter extends OncePerRequestFilter {
}
try {
addRequestAnnotations(request);
filterChain.doFilter(request, response);
}
finally {
if (request.isAsyncSupported() && request.isAsyncStarted()) {
//TODO: howto deal with response annotations and async?
return;
}
if (traceScope != null) {
addResponseAnnotations(response);
traceScope.close();
}
}
}
protected void addRequestAnnotations(HttpServletRequest request) {
String uri = this.urlPathHelper.getPathWithinApplication(request);
this.trace.addKVAnnotation("/http/request/uri",
request.getRequestURL().toString());
this.trace.addKVAnnotation("/http/request/endpoint", uri);
this.trace.addKVAnnotation("/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.trace.addKVAnnotation(key, value);
}
}
}
private void addResponseAnnotations(HttpServletResponse response) {
this.trace.addKVAnnotation("/http/response/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.trace.addKVAnnotation(key, value);
}
}
}
private String getHeader(HttpServletRequest request, HttpServletResponse response,
String name) {
String value = request.getHeader(name);

View File

@@ -72,7 +72,7 @@ public class TraceWebAspect {
log.debug("Wrapping callable with span ["
+ TraceContextHolder.getCurrentSpan() + "]");
return new TraceCallable<Object>(this.trace, callable);
return new TraceCallable<>(this.trace, callable);
}
else {
return callable;

View File

@@ -0,0 +1,126 @@
package org.springframework.cloud.sleuth.instrument.web;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import lombok.SneakyThrows;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Trace;
import org.springframework.cloud.sleuth.TraceScope;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
/**
* @author Spencer Gibb
*/
public class TraceFilterTests {
@Mock
private Trace trace;
@Mock
private TraceScope traceScope;
@Mock
private Span span;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private MockFilterChain filterChain;
@Before
@SneakyThrows
public void init() {
initMocks(this);
request = builder()
.buildRequest(new MockServletContext());
response = new MockHttpServletResponse();
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
filterChain = new MockFilterChain();
}
public MockHttpServletRequestBuilder builder() {
return get("/")
.accept(MediaType.APPLICATION_JSON)
.header("User-Agent", "MockMvc");
}
@Test
public void startsNewTrace() throws Exception {
TraceFilter filter = new TraceFilter(trace);
when(this.trace.startSpan(anyString())).thenReturn(traceScope);
filter.doFilter(request, response, filterChain);
verify(this.trace).startSpan(anyString());
verifyHttpAnnotations();
verify(this.traceScope).close();
}
@Test
public void continuesSpanInRequestAttr() throws Exception {
request.setAttribute(TraceFilter.TRACE_REQUEST_ATTR, this.traceScope);
TraceFilter filter = new TraceFilter(trace);
filter.doFilter(request, response, filterChain);
verify(this.trace).continueSpan((Span) anyObject());
verifyHttpAnnotations();
verify(this.traceScope).close();
}
@Test
public void continuesSpanFromHeaders() throws Exception {
request = builder()
.header(SPAN_ID_NAME, "myspan")
.header(TRACE_ID_NAME, "mytrace")
.buildRequest(new MockServletContext());
when(this.trace.startSpan(anyString(), (Span) anyObject())).thenReturn(traceScope);
when(this.traceScope.getSpan()).thenReturn(this.span);
when(this.span.getSpanId()).thenReturn("myspan");
when(this.span.getTraceId()).thenReturn("mytrace");
TraceFilter filter = new TraceFilter(trace);
filter.doFilter(request, response, filterChain);
verify(this.trace).startSpan(anyString(), (Span) anyObject());
verifyHttpAnnotations();
verify(this.traceScope).close();
}
public void verifyHttpAnnotations() {
verify(this.trace).addKVAnnotation("/http/request/uri", "http://localhost/");
verify(this.trace).addKVAnnotation("/http/request/endpoint", "/");
verify(this.trace).addKVAnnotation("/http/request/method", "GET");
verify(this.trace).addKVAnnotation("/http/request/headers/accept", MediaType.APPLICATION_JSON_VALUE);
verify(this.trace).addKVAnnotation("/http/request/headers/user-agent", "MockMvc");
verify(this.trace).addKVAnnotation("/http/response/status_code", HttpStatus.OK.toString());
verify(this.trace).addKVAnnotation("/http/response/headers/content-type", MediaType.APPLICATION_JSON_VALUE);
}
}