Merge branch 'master' into 2.0.x
This commit is contained in:
@@ -350,6 +350,9 @@ tracer.addTag(baggageKey, baggageValue);
|
||||
|
||||
=== Adding to the project
|
||||
|
||||
IMPORTANT: To ensure that your application name is properly displayed in Zipkin
|
||||
set the `spring.application.name` property in `bootstrap.yml`.
|
||||
|
||||
==== Only Sleuth (log correlation)
|
||||
|
||||
If you want to profit only from Spring Cloud Sleuth without the Zipkin integration just add
|
||||
|
||||
@@ -229,6 +229,9 @@ tracer.addTag(baggageKey, baggageValue);
|
||||
|
||||
=== Adding to the project
|
||||
|
||||
IMPORTANT: To ensure that your application name is properly displayed in Zipkin
|
||||
set the `spring.application.name` property in `bootstrap.yml`.
|
||||
|
||||
==== Only Sleuth (log correlation)
|
||||
|
||||
If you want to profit only from Spring Cloud Sleuth without the Zipkin integration just add
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
|
||||
package org.springframework.cloud.sleuth.instrument.web;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.cloud.sleuth.SpanTextMap;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
@@ -34,29 +35,47 @@ import org.springframework.web.util.UrlPathHelper;
|
||||
class HttpServletRequestTextMap implements SpanTextMap {
|
||||
|
||||
private final HttpServletRequest delegate;
|
||||
private final Map<String, String> additionalHeaders = new HashMap<>();
|
||||
private final UrlPathHelper urlPathHelper;
|
||||
|
||||
HttpServletRequestTextMap(HttpServletRequest delegate) {
|
||||
this.delegate = delegate;
|
||||
UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
this.additionalHeaders.put(ZipkinHttpSpanExtractor.URI_HEADER,
|
||||
urlPathHelper.getPathWithinApplication(delegate));
|
||||
this.urlPathHelper = new UrlPathHelper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<String, String>> iterator() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
Enumeration<String> headerNames = this.delegate.getHeaderNames();
|
||||
while (headerNames != null && headerNames.hasMoreElements()) {
|
||||
String name = headerNames.nextElement();
|
||||
map.put(name, this.delegate.getHeader(name));
|
||||
}
|
||||
map.putAll(this.additionalHeaders);
|
||||
return map.entrySet().iterator();
|
||||
final Enumeration<String> headerNames = this.delegate.getHeaderNames();
|
||||
|
||||
return new Iterator<Map.Entry<String, String>>() {
|
||||
|
||||
private boolean useAdditionalHeader = true;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return useAdditionalHeader
|
||||
|| (headerNames != null && headerNames.hasMoreElements());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<String, String> next() {
|
||||
if (useAdditionalHeader) {
|
||||
useAdditionalHeader = false;
|
||||
return new AbstractMap.SimpleImmutableEntry<>(
|
||||
ZipkinHttpSpanMapper.URI_HEADER,
|
||||
HttpServletRequestTextMap.this.urlPathHelper
|
||||
.getPathWithinApplication(
|
||||
HttpServletRequestTextMap.this.delegate));
|
||||
}
|
||||
|
||||
String name = headerNames.nextElement();
|
||||
String value = HttpServletRequestTextMap.this.delegate.getHeader(name);
|
||||
return new AbstractMap.SimpleEntry<>(name, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String key, String value) {
|
||||
this.additionalHeaders.put(key, value);
|
||||
throw new UnsupportedOperationException("change servlet request isn't supported");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import java.util.regex.Pattern;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.SpanTextMap;
|
||||
import org.springframework.cloud.sleuth.util.TextMapUtil;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -21,35 +20,33 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor {
|
||||
|
||||
private static final org.apache.commons.logging.Log log = LogFactory.getLog(
|
||||
MethodHandles.lookup().lookupClass());
|
||||
private static final String HEADER_DELIMITER = "-";
|
||||
static final String URI_HEADER = "X-Span-Uri";
|
||||
|
||||
private static final String HTTP_COMPONENT = "http";
|
||||
|
||||
private static final ZipkinHttpSpanMapper SPAN_CARRIER_MAPPER = new ZipkinHttpSpanMapper();
|
||||
|
||||
private final Pattern skipPattern;
|
||||
private final Random random;
|
||||
|
||||
public ZipkinHttpSpanExtractor(Pattern skipPattern) {
|
||||
this.skipPattern = skipPattern;
|
||||
this.random = new Random();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span joinTrace(SpanTextMap textMap) {
|
||||
Map<String, String> carrier = TextMapUtil.asMap(textMap);
|
||||
Map<String, String> carrier = SPAN_CARRIER_MAPPER.convert(textMap);
|
||||
|
||||
boolean debug = Span.SPAN_SAMPLED.equals(carrier.get(Span.SPAN_FLAGS));
|
||||
boolean idToBeGenerated = debug && onlySpanIdIsPresent(carrier);
|
||||
if (idToBeGenerated) {
|
||||
// we're only generating Trace ID since if there's no Span ID will assume
|
||||
// that it's equal to Trace ID - we're trying to fix a malformed request
|
||||
generateIdIfMissing(carrier, Span.TRACE_ID_NAME);
|
||||
} else if (traceIdIsMissing(carrier)) {
|
||||
// we're only generating Trace ID since if there's no Span ID will assume
|
||||
// that it's equal to Trace ID - we're trying to fix a malformed request
|
||||
if (!idToBeGenerated && traceIdIsMissing(carrier)) {
|
||||
// can't build a Span without trace id
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String uri = carrier.get(URI_HEADER);
|
||||
boolean skip = this.skipPattern.matcher(uri).matches()
|
||||
|| Span.SPAN_NOT_SAMPLED.equals(carrier.get(Span.SAMPLED_NAME));
|
||||
long spanId = spanId(carrier);
|
||||
return buildParentSpan(carrier, uri, skip, spanId, idToBeGenerated);
|
||||
return buildParentSpan(carrier, idToBeGenerated);
|
||||
} catch (Exception e) {
|
||||
log.error("Exception occurred while trying to extract span from carrier", e);
|
||||
return null;
|
||||
@@ -68,46 +65,51 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor {
|
||||
return carrier.get(Span.SPAN_ID_NAME) != null;
|
||||
}
|
||||
|
||||
private void generateIdIfMissing(Map<String, String> carrier, String key) {
|
||||
if (!carrier.containsKey(key)) {
|
||||
carrier.put(key, Span.idToHex(new Random().nextLong()));
|
||||
}
|
||||
private String generateId() {
|
||||
return Span.idToHex(this.random.nextLong());
|
||||
}
|
||||
|
||||
private long spanId(Map<String, String> carrier) {
|
||||
String spanId = carrier.get(Span.SPAN_ID_NAME);
|
||||
private long spanId(String spanId, String traceId) {
|
||||
if (spanId == null) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Request is missing a span id but it has a trace id. We'll assume that this is "
|
||||
+ "a root span with span id equal to the lower 64-bits of the trace id");
|
||||
}
|
||||
return Span.hexToId(carrier.get(Span.TRACE_ID_NAME));
|
||||
return Span.hexToId(traceId);
|
||||
} else {
|
||||
return Span.hexToId(spanId);
|
||||
}
|
||||
}
|
||||
|
||||
private Span buildParentSpan(Map<String, String> carrier, String uri, boolean skip,
|
||||
long spanId, boolean idToBeGenerated) {
|
||||
private Span buildParentSpan(Map<String, String> carrier, boolean idToBeGenerated) {
|
||||
String traceId = carrier.get(Span.TRACE_ID_NAME);
|
||||
if (traceId == null) {
|
||||
traceId = generateId();
|
||||
}
|
||||
Span.SpanBuilder span = Span.builder()
|
||||
.traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0)
|
||||
.traceId(Span.hexToId(traceId))
|
||||
.spanId(spanId);
|
||||
String processId = carrier.get(Span.PROCESS_ID_NAME);
|
||||
.spanId(spanId(carrier.get(Span.SPAN_ID_NAME), traceId));
|
||||
String parentName = carrier.get(Span.SPAN_NAME_NAME);
|
||||
if (StringUtils.hasText(parentName)) {
|
||||
span.name(parentName);
|
||||
} else {
|
||||
span.name(HTTP_COMPONENT + ":/parent" + uri);
|
||||
span.name(HTTP_COMPONENT + ":/parent"
|
||||
+ carrier.get(ZipkinHttpSpanMapper.URI_HEADER));
|
||||
}
|
||||
String processId = carrier.get(Span.PROCESS_ID_NAME);
|
||||
if (StringUtils.hasText(processId)) {
|
||||
span.processId(processId);
|
||||
}
|
||||
if (carrier.containsKey(Span.PARENT_ID_NAME)) {
|
||||
span.parent(Span.hexToId(carrier.get(Span.PARENT_ID_NAME)));
|
||||
String parentId = carrier.get(Span.PARENT_ID_NAME);
|
||||
if (parentId != null) {
|
||||
span.parent(Span.hexToId(parentId));
|
||||
}
|
||||
span.remote(true);
|
||||
|
||||
boolean skip = this.skipPattern
|
||||
.matcher(carrier.get(ZipkinHttpSpanMapper.URI_HEADER)).matches()
|
||||
|| Span.SPAN_NOT_SAMPLED.equals(carrier.get(Span.SAMPLED_NAME));
|
||||
// trace, span id were retrieved from the headers and span is sampled
|
||||
span.shared(!(skip || idToBeGenerated));
|
||||
boolean debug = Span.SPAN_SAMPLED.equals(carrier.get(Span.SPAN_FLAGS));
|
||||
@@ -117,7 +119,8 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor {
|
||||
span.exportable(false);
|
||||
}
|
||||
for (Map.Entry<String, String> entry : carrier.entrySet()) {
|
||||
if (entry.getKey().toLowerCase().startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX + HEADER_DELIMITER)) {
|
||||
if (entry.getKey().toLowerCase()
|
||||
.startsWith(ZipkinHttpSpanMapper.BAGGAGE_PREFIX)) {
|
||||
span.baggage(unprefixedKey(entry.getKey()), entry.getValue());
|
||||
}
|
||||
}
|
||||
@@ -125,7 +128,8 @@ public class ZipkinHttpSpanExtractor implements HttpSpanExtractor {
|
||||
}
|
||||
|
||||
private String unprefixedKey(String key) {
|
||||
return key.substring(key.indexOf(HEADER_DELIMITER) + 1).toLowerCase();
|
||||
return key.substring(key.indexOf(ZipkinHttpSpanMapper.HEADER_DELIMITER) + 1)
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.SpanTextMap;
|
||||
import org.springframework.cloud.sleuth.util.TextMapUtil;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -15,11 +14,11 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class ZipkinHttpSpanInjector implements HttpSpanInjector {
|
||||
|
||||
private static final String HEADER_DELIMITER = "-";
|
||||
private static final ZipkinHttpSpanMapper SPAN_CARRIER_MAPPER = new ZipkinHttpSpanMapper();
|
||||
|
||||
@Override
|
||||
public void inject(Span span, SpanTextMap map) {
|
||||
Map<String, String> carrier = TextMapUtil.asMap(map);
|
||||
Map<String, String> carrier = SPAN_CARRIER_MAPPER.convert(map);
|
||||
setHeader(map, carrier, Span.TRACE_ID_NAME, span.traceIdString());
|
||||
setIdHeader(map, carrier, Span.SPAN_ID_NAME, span.getSpanId());
|
||||
setHeader(map, carrier, Span.SAMPLED_NAME, span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);
|
||||
@@ -32,10 +31,12 @@ public class ZipkinHttpSpanInjector implements HttpSpanInjector {
|
||||
}
|
||||
|
||||
private String prefixedKey(String key) {
|
||||
if (key.startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX + HEADER_DELIMITER)) {
|
||||
if (key.startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX
|
||||
+ ZipkinHttpSpanMapper.HEADER_DELIMITER)) {
|
||||
return key;
|
||||
}
|
||||
return Span.SPAN_BAGGAGE_HEADER_PREFIX + HEADER_DELIMITER + key;
|
||||
return Span.SPAN_BAGGAGE_HEADER_PREFIX + ZipkinHttpSpanMapper.HEADER_DELIMITER
|
||||
+ key;
|
||||
}
|
||||
|
||||
private Long getParentId(Span span) {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2013-2017 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.web;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.SpanTextMap;
|
||||
|
||||
/**
|
||||
* Mapper util for filter Zipkin compatible carrier values only from {@link SpanTextMap}
|
||||
*
|
||||
* @author Anton Kislitsyn
|
||||
*/
|
||||
class ZipkinHttpSpanMapper {
|
||||
|
||||
static final String HEADER_DELIMITER = "-";
|
||||
static final String BAGGAGE_PREFIX = Span.SPAN_BAGGAGE_HEADER_PREFIX
|
||||
+ HEADER_DELIMITER;
|
||||
static final String URI_HEADER = "X-Span-Uri";
|
||||
|
||||
private static Comparator<String> IGNORE_CASE_COMPARATOR = new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
return o1.toLowerCase().compareTo(o2.toLowerCase());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Acceptable span fields
|
||||
*/
|
||||
private static final Set<String> SPAN_FIELDS;
|
||||
|
||||
static {
|
||||
TreeSet<String> fields = new TreeSet<>(IGNORE_CASE_COMPARATOR);
|
||||
Collections.addAll(fields, Span.SPAN_FLAGS, Span.TRACE_ID_NAME, Span.SPAN_ID_NAME,
|
||||
Span.PROCESS_ID_NAME, Span.SPAN_NAME_NAME, Span.PARENT_ID_NAME,
|
||||
Span.SAMPLED_NAME, URI_HEADER);
|
||||
SPAN_FIELDS = Collections.unmodifiableSet(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new Map of carrier values
|
||||
*/
|
||||
Map<String, String> convert(SpanTextMap textMap) {
|
||||
Map<String, String> carrier = new TreeMap<>(IGNORE_CASE_COMPARATOR);
|
||||
for (Map.Entry<String, String> entry : textMap) {
|
||||
if (isAcceptable(entry.getKey())) {
|
||||
carrier.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(carrier);
|
||||
}
|
||||
|
||||
private boolean isAcceptable(String key) {
|
||||
return SPAN_FIELDS.contains(key) || key.startsWith(BAGGAGE_PREFIX);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2013-2017 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.web;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanMapper.URI_HEADER;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.SpanTextMap;
|
||||
|
||||
/**
|
||||
* @author Anton Kislitsyn
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class ZipkinHttpSpanMapperTest {
|
||||
|
||||
private static final ZipkinHttpSpanMapper MAPPER = new ZipkinHttpSpanMapper();
|
||||
|
||||
private final String headerName;
|
||||
private final String value;
|
||||
private final boolean suitable;
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<Object[]> parameters() {
|
||||
return Arrays.asList(new Object[] { Span.TRACE_ID_NAME, "traceId", true },
|
||||
new Object[] { Span.SPAN_ID_NAME, "spanId", true },
|
||||
new Object[] { Span.SPAN_FLAGS, "flags", true },
|
||||
new Object[] { Span.PROCESS_ID_NAME, "process", true },
|
||||
new Object[] { Span.SPAN_NAME_NAME, "name", true },
|
||||
new Object[] { Span.PARENT_ID_NAME, "parent", true },
|
||||
new Object[] { Span.SAMPLED_NAME, "sampled", true },
|
||||
new Object[] { URI_HEADER, "uri", true },
|
||||
new Object[] { UUID.randomUUID().toString(), UUID.randomUUID().toString(),
|
||||
false },
|
||||
new Object[] { Span.SPAN_ID_NAME, null, false },
|
||||
new Object[] { UUID.randomUUID().toString(), null, false });
|
||||
}
|
||||
|
||||
public ZipkinHttpSpanMapperTest(String headerName, String value, boolean suitable) {
|
||||
this.headerName = headerName;
|
||||
this.value = value;
|
||||
this.suitable = suitable;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_map_zipkin_suitable_fields() throws Exception {
|
||||
Map<String, String> map = MAPPER
|
||||
.convert(textMap(Collections.singletonMap(headerName, value)));
|
||||
assertThat(map.get(headerName), suitable ? equalTo(value) : is(nullValue()));
|
||||
}
|
||||
|
||||
private SpanTextMap textMap(Map<String, String> textMap) {
|
||||
return new SpanTextMap() {
|
||||
@Override
|
||||
public Iterator<Map.Entry<String, String>> iterator() {
|
||||
return textMap.entrySet().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String key, String value) {
|
||||
textMap.put(key, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user