Initial implementation of http annotations
Part of gh-11
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user