Customizable headers

* Fixed the HttpServlet extractors
    (now the response can contain custom headers)
    * Changed header names to be Zipkin compatible
    * removed qualifiers and properties
    * updated the docs

fixes #19
This commit is contained in:
Marcin Grzejszczak
2016-03-16 16:10:28 +01:00
parent 7c5af8c84e
commit 3bb7916fff
34 changed files with 400 additions and 130 deletions

View File

@@ -184,7 +184,7 @@ IMPORTANT: If using Zipkin or Stream, configure the percentage of spans exported
NOTE: the SLF4J MDC is always set and logback users will immediately see the trace and span ids in logs per the example
above. Other logging systems have to configure their own formatter to get the same result. The default is
`logging.pattern.level` set to `%clr(%5p) %clr([${spring.application.name:},%X{X-Trace-Id:-},%X{X-Span-Id:-},%X{X-Span-Export:-}]){yellow}`
`logging.pattern.level` set to `%clr(%5p) %clr([${spring.application.name:},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow}`
(this is a Spring Boot feature for logback users).
*This means that if you're not using SLF4J this pattern WILL NOT be automatically applied*.

View File

@@ -30,6 +30,10 @@ latency in your applications. Sleuth is written to not log too much, and to not
* Instruments common ingress and egress points from Spring applications (servlet filter, async endpoints,
rest template, scheduled actions, message channels, zuul filters, feign client).
* Sleuth includes default logic to join a trace across http or messaging boundaries. For example, http propagation
works via Zipkin-compatible request headers. This propagation logic is defined and customized via
`SpanInjector` and `SpanExtractor` implementations.
* Provides simple metrics of accepted / dropped spans.
* If `spring-cloud-sleuth-zipkin` then the app will generate and collect Zipkin-compatible traces.
@@ -45,6 +49,6 @@ IMPORTANT: If using Zipkin or Stream, configure the percentage of spans exported
NOTE: the SLF4J MDC is always set and logback users will immediately see the trace and span ids in logs per the example
above. Other logging systems have to configure their own formatter to get the same result. The default is
`logging.pattern.level` set to `%clr(%5p) %clr([${spring.application.name:},%X{X-Trace-Id:-},%X{X-Span-Id:-},%X{X-Span-Export:-}]){yellow}`
`logging.pattern.level` set to `%clr(%5p) %clr([${spring.application.name:},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow}`
(this is a Spring Boot feature for logback users).
*This means that if you're not using SLF4J this pattern WILL NOT be automatically applied*.

View File

@@ -191,7 +191,92 @@ adding a Channel Binder implementation
(e.g. `spring-cloud-starter-stream-rabbit` for RabbitMQ or
`spring-cloud-starter-stream-kafka` for Kafka). This will
automatically turn your app into a producer of messages with payload
type `Spans`.
type `Spans`.
== Customizations
Thanks to the `SpanInjector` and `SpanExtractor` you can customize the way spans
are created and propagated.
There are currently two built-in ways to pass tracing information between processes:
* via Spring Integration
* via HTTP
Span ids are extracted from Zipkin-compatible (B3) headers (either `Message`
or HTTP headers), to start or join an existing trace. Trace information is
injected into any outbound requests so the next hop can extract them.
=== Spring Integration
For Spring Integration these are the beans responsible for creation of a Span from a `Message`
and filling in the `MessageBuilder` with tracing information.
[source,java]
----
@Bean
public SpanExtractor<Message> messagingSpanExtractor() {
...
}
@Bean
public SpanInjector<MessageBuilder> messagingSpanInjector() {
...
}
----
You can override them by providing your own implementation and by adding a `@Primary` annotation
to your bean definition.
=== HTTP
For HTTP these are the beans responsible for creation of a Span from a `HttpServletRequest`
and filling in the `HttpServletResponse` with tracing information.
[source,java]
----
@Bean
public SpanExtractor<HttpServletRequest> httpServletRequestSpanExtractor() {
...
}
@Bean
public SpanInjector<HttpServletResponse> httpServletResponseSpanInjector() {
...
}
----
You can override them by providing your own implementation and by adding a `@Primary` annotation
to your bean definition.
=== Example
Let's assume that instead of the standard Zipkin compatible tracing HTTP header names
you have
* for trace id - `correlationId`
* for span id - `mySpanId`
This is a an example of a `SpanExtractor`
[source,java]
----
include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[tags=extractor,indent=0]
----
The following `SpanInjector` could be created
[source,java]
----
include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[tags=injector,indent=0]
----
And you could register them like this:
[source,java]
----
include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[tags=configuration,indent=0]
----
=== Zipkin Consumer

View File

@@ -45,6 +45,15 @@ import org.springframework.util.StringUtils;
* <li><b>cr</b> - Client Received</li>
* </ul>
*
* Spring Cloud Sleuth uses Zipkin compatible header names
*
* <ul>
* <li>X-B3-TraceId: 64 encoded bits</li>
* <li>X-B3-SpanId: 64 encoded bits</li>
* <li>X-B3-ParentSpanId: 64 encoded bits</li>
* <li>X-B3-Sampled: Boolean (either “1” or “0”)</li>
* </ul>
*
* @author Spencer Gibb
* @author Marcin Grzejszczak
* @since 1.0.0
@@ -56,14 +65,17 @@ import org.springframework.util.StringUtils;
*/
public class Span {
public static final String NOT_SAMPLED_NAME = "X-Not-Sampled";
public static final String SAMPLED_NAME = "X-B3-Sampled";
public static final String PROCESS_ID_NAME = "X-Process-Id";
public static final String PARENT_ID_NAME = "X-Parent-Id";
public static final String TRACE_ID_NAME = "X-Trace-Id";
public static final String PARENT_ID_NAME = "X-B3-ParentSpanId";
public static final String TRACE_ID_NAME = "X-B3-TraceId";
public static final String SPAN_NAME_NAME = "X-Span-Name";
public static final String SPAN_ID_NAME = "X-Span-Id";
public static final String SPAN_ID_NAME = "X-B3-SpanId";
public static final String SPAN_EXPORT_NAME = "X-Span-Export";
public static final String SPAN_SAMPLED = "1";
public static final String SPAN_NOT_SAMPLED = "0";
public static final String SPAN_LOCAL_COMPONENT_TAG_NAME = "lc";
/**

View File

@@ -51,7 +51,7 @@ public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor {
// This doesn't work with all logging systems but it's a useful default so you see
// traces in logs without having to configure it.
map.put("logging.pattern.level",
"%clr(%5p) %clr([${spring.application.name:},%X{X-Trace-Id:-},%X{X-Span-Id:-},%X{X-Span-Export:-}]){yellow}");
"%clr(%5p) %clr([${spring.application.name:},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow}");
map.put("spring.aop.proxyTargetClass", "true");
addOrReplace(environment.getPropertySources(), map);
}

View File

@@ -50,9 +50,7 @@ public class MessagingSpanExtractor implements SpanExtractor<Message> {
: this.random.nextLong();
long traceId = Span.hexToId(getHeader(carrier, Span.TRACE_ID_NAME));
SpanBuilder spanBuilder = Span.builder().traceId(traceId).spanId(spanId);
if (hasHeader(carrier, Span.NOT_SAMPLED_NAME)) {
spanBuilder.exportable(false);
}
spanBuilder.exportable(Span.SPAN_SAMPLED.equals(getHeader(carrier, Span.SAMPLED_NAME)));
String parentId = getHeader(carrier, Span.PARENT_ID_NAME);
String processId = getHeader(carrier, Span.PROCESS_ID_NAME);
String spanName = getHeader(carrier, Span.SPAN_NAME_NAME);

View File

@@ -52,8 +52,8 @@ public class MessagingSpanInjector implements SpanInjector<MessageBuilder> {
MessageHeaderAccessor accessor = MessageHeaderAccessor
.getMutableAccessor(initialMessage);
if (span == null) {
if (!initialMessage.getHeaders().containsKey(Span.NOT_SAMPLED_NAME)) {
accessor.setHeader(Span.NOT_SAMPLED_NAME, "true");
if (!Span.SPAN_SAMPLED.equals(initialMessage.getHeaders().get(Span.SAMPLED_NAME))) {
accessor.setHeader(Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED);
carrier.setHeaders(accessor);
return;
}
@@ -70,9 +70,10 @@ public class MessagingSpanInjector implements SpanInjector<MessageBuilder> {
}
addHeader(headers, Span.SPAN_NAME_NAME, span.getName());
addHeader(headers, Span.PROCESS_ID_NAME, span.getProcessId());
addHeader(headers, Span.SAMPLED_NAME, Span.SPAN_SAMPLED);
}
else {
addHeader(headers, Span.NOT_SAMPLED_NAME, "true");
addHeader(headers, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED);
}
accessor.setHeader(SPAN_HEADER, span);
accessor.copyHeaders(headers);

View File

@@ -63,7 +63,7 @@ public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor {
if (span != null) {
return getTracer().createSpan(name, span);
}
if (message.getHeaders().containsKey(Span.NOT_SAMPLED_NAME)) {
if (Span.SPAN_NOT_SAMPLED.equals(message.getHeaders().get(Span.SAMPLED_NAME))) {
return getTracer().createSpan(name, NeverSampler.INSTANCE);
}
return getTracer().createSpan(name);

View File

@@ -0,0 +1,49 @@
/*
* 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.messaging;
import java.util.Random;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
/**
* AutoConfiguration containing Span extractor and injector for messaging.
* Will be reused by Messaging and WebSockets
*
* @author Marcin Grzejszczak
*
* @since 1.0.0
*/
@Configuration
public class TraceSpanMessagingAutoConfiguration {
@Bean
public SpanExtractor<Message> messagingSpanExtractor(Random random) {
return new MessagingSpanExtractor(random);
}
@Bean
public SpanInjector<MessageBuilder> messagingSpanInjector(TraceKeys traceKeys) {
return new MessagingSpanInjector(traceKeys);
}
}

View File

@@ -18,7 +18,6 @@ package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.Random;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -47,7 +46,7 @@ import org.springframework.messaging.support.MessageBuilder;
@Configuration
@ConditionalOnClass(GlobalChannelInterceptor.class)
@ConditionalOnBean(Tracer.class)
@AutoConfigureAfter(TraceAutoConfiguration.class)
@AutoConfigureAfter({TraceAutoConfiguration.class, TraceSpanMessagingAutoConfiguration.class})
@ConditionalOnProperty(value = "spring.sleuth.integration.enabled", matchIfMissing = true)
@EnableConfigurationProperties(TraceKeys.class)
public class TraceSpringIntegrationAutoConfiguration {
@@ -56,25 +55,9 @@ public class TraceSpringIntegrationAutoConfiguration {
@GlobalChannelInterceptor
public TraceChannelInterceptor traceChannelInterceptor(Tracer tracer,
TraceKeys traceKeys, Random random,
@Qualifier("messagingSpanExtractor") SpanExtractor<Message> spanExtractor,
@Qualifier("messagingSpanInjector") SpanInjector<MessageBuilder> spanInjector) {
SpanExtractor<Message> spanExtractor,
SpanInjector<MessageBuilder> spanInjector) {
return new TraceChannelInterceptor(tracer, traceKeys, spanExtractor, spanInjector);
}
// TODO: Qualifier + ConditionalOnProp cause autowiring generics doesn't work
@Bean
@Qualifier("messagingSpanExtractor")
@ConditionalOnProperty(value = "spring.sleuth.integration.injector.enabled", matchIfMissing = true)
public SpanExtractor<Message> messagingSpanExtractor(Random random) {
return new MessagingSpanExtractor(random);
}
// TODO: Qualifier + ConditionalOnProp cause autowiring generics doesn't work
@Bean
@Qualifier("messagingSpanInjector")
@ConditionalOnProperty(value = "spring.sleuth.integration.injector.enabled", matchIfMissing = true)
public SpanInjector<MessageBuilder> messagingSpanInjector(TraceKeys traceKeys) {
return new MessagingSpanInjector(traceKeys);
}
}

View File

@@ -1,9 +1,6 @@
package org.springframework.cloud.sleuth.instrument.messaging.websocket;
import java.util.Random;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -12,11 +9,8 @@ import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.messaging.MessagingSpanExtractor;
import org.springframework.cloud.sleuth.instrument.messaging.MessagingSpanInjector;
import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptor;
import org.springframework.cloud.sleuth.instrument.messaging.TraceSpringIntegrationAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.cloud.sleuth.instrument.messaging.TraceSpanMessagingAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.simp.config.ChannelRegistration;
@@ -37,16 +31,17 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
*/
@Component
@Configuration
@AutoConfigureAfter(TraceSpringIntegrationAutoConfiguration.class)
@AutoConfigureAfter(TraceSpanMessagingAutoConfiguration.class)
@ConditionalOnClass(DelegatingWebSocketMessageBrokerConfiguration.class)
@ConditionalOnBean(AbstractWebSocketMessageBrokerConfigurer.class)
@ConditionalOnProperty(value = "spring.sleuth.integration.websockets.enabled", matchIfMissing = true)
public class TraceWebSocketAutoConfiguration
extends AbstractWebSocketMessageBrokerConfigurer {
@Autowired Tracer tracer;
@Autowired TraceKeys traceKeys;
@Autowired @Qualifier("stompMessagingSpanExtractor") SpanExtractor<Message> spanExtractor;
@Autowired @Qualifier("stompMessagingSpanInjector") SpanInjector<MessageBuilder> spanInjector;
@Autowired SpanExtractor<Message> spanExtractor;
@Autowired SpanInjector<MessageBuilder> spanInjector;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
@@ -64,20 +59,4 @@ public class TraceWebSocketAutoConfiguration
registration.setInterceptors(
new TraceChannelInterceptor(this.tracer, this.traceKeys, this.spanExtractor, this.spanInjector));
}
// TODO: Qualifier + ConditionalOnProp cause autowiring generics doesn't work
@Bean
@Qualifier("stompMessagingSpanExtractor")
@ConditionalOnProperty(value = "spring.sleuth.integration.websocket.injector.enabled", matchIfMissing = true)
public SpanExtractor<Message> stompMessagingSpanExtractor(Random random) {
return new MessagingSpanExtractor(random);
}
// TODO: Qualifier + ConditionalOnProp cause autowiring generics doesn't work
@Bean
@Qualifier("stompMessagingSpanInjector")
@ConditionalOnProperty(value = "spring.sleuth.integration.websocket.injector.enabled", matchIfMissing = true)
public SpanInjector<MessageBuilder> stompMessagingSpanInjector(TraceKeys traceKeys) {
return new MessagingSpanInjector(traceKeys);
}
}

View File

@@ -50,15 +50,23 @@ class HttpServletRequestExtractor implements SpanExtractor<HttpServletRequest> {
@Override
public Span joinTrace(HttpServletRequest carrier) {
if (carrier.getHeader(Span.TRACE_ID_NAME) == null) {
// can't build a Span without trace id
return null;
}
String uri = this.urlPathHelper.getPathWithinApplication(carrier);
boolean skip = this.skipPattern.matcher(uri).matches()
|| carrier.getHeader(Span.NOT_SAMPLED_NAME) != null;
|| Span.SPAN_NOT_SAMPLED.equals(carrier.getHeader(Span.SAMPLED_NAME));
long traceId = Span
.hexToId(carrier.getHeader(Span.TRACE_ID_NAME));
long spanId = carrier.getHeader(Span.SPAN_ID_NAME) != null
? Span.hexToId(carrier.getHeader(Span.SPAN_ID_NAME))
: this.random.nextLong();
return buildParentSpan(carrier, uri, skip, traceId, spanId);
}
private Span buildParentSpan(HttpServletRequest carrier, String uri, boolean skip,
long traceId, long spanId) {
SpanBuilder span = Span.builder().traceId(traceId).spanId(spanId);
String processId = carrier.getHeader(Span.PROCESS_ID_NAME);
String parentName = carrier.getHeader(Span.SPAN_NAME_NAME);

View File

@@ -39,7 +39,6 @@ import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UrlPathHelper;
import static org.springframework.cloud.sleuth.instrument.web.ServletUtils.hasHeader;
import static org.springframework.util.StringUtils.hasText;
/**
@@ -107,16 +106,14 @@ public class TraceFilter extends OncePerRequestFilter {
throws ServletException, IOException {
String uri = this.urlPathHelper.getPathWithinApplication(request);
boolean skip = this.skipPattern.matcher(uri).matches()
|| ServletUtils.getHeader(request, response, Span.NOT_SAMPLED_NAME) != null;
|| Span.SPAN_NOT_SAMPLED.equals(ServletUtils.getHeader(request, response, Span.SAMPLED_NAME));
Span spanFromRequest = (Span) request.getAttribute(TRACE_REQUEST_ATTR);
if (spanFromRequest != null) {
this.tracer.continueSpan(spanFromRequest);
}
else if (skip) {
addToResponseIfNotPresent(response, Span.NOT_SAMPLED_NAME, "");
}
addToResponseIfNotPresent(response, Span.SAMPLED_NAME, skip ? Span.SPAN_NOT_SAMPLED : Span.SPAN_SAMPLED);
String name = HTTP_COMPONENT + ":" + uri;
spanFromRequest = createSpan(request, response, skip, spanFromRequest, name);
spanFromRequest = createSpan(request, skip, spanFromRequest, name);
Throwable exception = null;
try {
addRequestTags(request);
@@ -135,9 +132,7 @@ public class TraceFilter extends OncePerRequestFilter {
// TODO: how to deal with response annotations and async?
return;
}
if (skip) {
addToResponseIfNotPresent(response, Span.NOT_SAMPLED_NAME, "");
}
addToResponseIfNotPresent(response, Span.SAMPLED_NAME, skip ? Span.SPAN_NOT_SAMPLED : Span.SPAN_SAMPLED);
if (spanFromRequest != null) {
addResponseTags(response, exception);
if (spanFromRequest.hasSavedSpan()) {
@@ -156,16 +151,16 @@ public class TraceFilter extends OncePerRequestFilter {
/**
* Creates a span and appends it as the current request's attribute
*/
private Span createSpan(HttpServletRequest request, HttpServletResponse response,
private Span createSpan(HttpServletRequest request,
boolean skip, Span spanFromRequest, String name) {
if (spanFromRequest != null) {
return spanFromRequest;
}
if (hasHeader(request, response, Span.TRACE_ID_NAME)) {
Span parent = this.spanExtractor
.joinTrace(request);
Span parent = this.spanExtractor
.joinTrace(request);
if (parent != null) {
spanFromRequest = this.tracer.createSpan(name, parent);
if (parent != null && parent.isRemote()) {
if (parent.isRemote()) {
parent.logEvent(Span.SERVER_RECV);
}
request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest);

View File

@@ -21,7 +21,6 @@ import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@@ -77,26 +76,20 @@ public class TraceWebAutoConfiguration {
@ConditionalOnMissingBean
public TraceFilter traceFilter(Tracer tracer, TraceKeys traceKeys,
SkipPatternProvider skipPatternProvider, SpanReporter spanReporter,
@Qualifier("httpServletRequestSpanExtractor") SpanExtractor<HttpServletRequest> spanExtractor,
@Qualifier("httpServletResponseInjector") SpanInjector<HttpServletResponse> spanInjector) {
SpanExtractor<HttpServletRequest> spanExtractor,
SpanInjector<HttpServletResponse> spanInjector) {
return new TraceFilter(tracer, traceKeys, skipPatternProvider.skipPattern(),
spanReporter, spanExtractor, spanInjector);
}
// TODO: Qualifier + ConditionalOnProp cause autowiring generics doesn't work
@Bean
@Qualifier("httpServletRequestSpanExtractor")
@ConditionalOnProperty(value = "spring.sleuth.web.extractor.enabled", matchIfMissing = true)
public SpanExtractor<HttpServletRequest> httpServletRequestSpanExtractor(Random random,
SkipPatternProvider skipPatternProvider) {
return new HttpServletRequestExtractor(random, skipPatternProvider.skipPattern());
}
// TODO: Qualifier + ConditionalOnProp cause autowiring generics doesn't work
@Bean
@Qualifier("httpServletResponseInjector")
@ConditionalOnProperty(value = "spring.sleuth.web.injector.enabled", matchIfMissing = true)
public SpanInjector<HttpServletResponse> httpServletResponseInjector() {
public SpanInjector<HttpServletResponse> httpServletResponseSpanInjector() {
return new HttpServletResponseInjector();
}

View File

@@ -34,9 +34,7 @@ class HttpRequestInjector implements SpanInjector<HttpRequest> {
public void inject(Span span, HttpRequest carrier) {
setIdHeader(carrier, Span.TRACE_ID_NAME, span.getTraceId());
setIdHeader(carrier, Span.SPAN_ID_NAME, span.getSpanId());
if (!span.isExportable()) {
setHeader(carrier, Span.NOT_SAMPLED_NAME, "true");
}
setHeader(carrier, Span.SAMPLED_NAME, span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);
setHeader(carrier, Span.SPAN_NAME_NAME, span.getName());
setIdHeader(carrier, Span.PARENT_ID_NAME, getParentId(span));
setHeader(carrier, Span.PROCESS_ID_NAME, span.getProcessId());

View File

@@ -66,7 +66,7 @@ public class TraceWebClientAutoConfiguration {
}
@Bean
public SpanInjector httpRequestInjector() {
public SpanInjector<HttpRequest> httpRequestSpanInjector() {
return new HttpRequestInjector();
}

View File

@@ -34,15 +34,14 @@ class FeignRequestTemplateInjector implements SpanInjector<RequestTemplate> {
@Override
public void inject(Span span, RequestTemplate carrier) {
if (span == null) {
setHeader(carrier, Span.NOT_SAMPLED_NAME, "true");
setHeader(carrier, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED);
return;
}
carrier.header(Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
setHeader(carrier, Span.SPAN_NAME_NAME, span.getName());
setHeader(carrier, Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
if (!span.isExportable()) {
setHeader(carrier, Span.NOT_SAMPLED_NAME, "true");
}
setHeader(carrier, Span.SAMPLED_NAME, span.isExportable() ?
Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);
Long parentId = getParentId(span);
if (parentId != null) {
setHeader(carrier, Span.PARENT_ID_NAME, Span.idToHex(parentId));

View File

@@ -47,9 +47,11 @@ class FeignResponseHeadersInjector implements SpanInjector<FeignResponseHeadersH
Map<String, Collection<String>> newHeaders = new HashMap<>();
newHeaders.putAll(headers);
if (span == null) {
setHeader(newHeaders, Span.NOT_SAMPLED_NAME, "true");
setHeader(newHeaders, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED);
return newHeaders;
}
setHeader(newHeaders, Span.SAMPLED_NAME, span.isExportable() ?
Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);
setHeader(newHeaders, Span.TRACE_ID_NAME, span.getTraceId());
setHeader(newHeaders, Span.SPAN_ID_NAME, span.getSpanId());
return newHeaders;

View File

@@ -34,9 +34,11 @@ class RequestBuilderContextInjector implements SpanInjector<Builder> {
@Override
public void inject(Span span, Builder carrier) {
if (span == null) {
setHeader(carrier, Span.NOT_SAMPLED_NAME, "true");
setHeader(carrier, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED);
return;
}
setHeader(carrier, Span.SAMPLED_NAME, span.isExportable() ?
Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);
setHeader(carrier, Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId()));
setHeader(carrier, Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
setHeader(carrier, Span.SPAN_NAME_NAME, span.getName());

View File

@@ -37,15 +37,14 @@ class RequestContextInjector implements SpanInjector<RequestContext> {
public void inject(Span span, RequestContext carrier) {
Map<String, String> requestHeaders = carrier.getZuulRequestHeaders();
if (span == null) {
setHeader(requestHeaders, Span.NOT_SAMPLED_NAME, "true");
setHeader(requestHeaders, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED);
return;
}
setHeader(requestHeaders, Span.SPAN_ID_NAME, span.getSpanId());
setHeader(requestHeaders, Span.TRACE_ID_NAME, span.getTraceId());
setHeader(requestHeaders, Span.SPAN_NAME_NAME, span.getName());
if (!span.isExportable()) {
setHeader(requestHeaders, Span.NOT_SAMPLED_NAME, "true");
}
setHeader(requestHeaders, Span.SAMPLED_NAME, span.isExportable() ?
Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);
setHeader(requestHeaders, Span.PARENT_ID_NAME, getParentId(span));
setHeader(requestHeaders, Span.PROCESS_ID_NAME, span.getProcessId());
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.cloud.sleuth.instrument.zuul;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -51,13 +52,14 @@ public class TraceZuulAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TracePreZuulFilter tracePreZuulFilter(Tracer tracer, SpanInjector<RequestContext> spanInjector) {
public TracePreZuulFilter tracePreZuulFilter(Tracer tracer,
SpanInjector<RequestContext> spanInjector) {
return new TracePreZuulFilter(tracer, spanInjector);
}
@Bean
public TraceRestClientRibbonCommandFactory traceRestClientRibbonCommandFactory(SpringClientFactory factory,
Tracer tracer, SpanInjector<HttpRequest.Builder> spanInjector) {
Tracer tracer, @Qualifier("requestBuilderContextSpanInjector") SpanInjector<HttpRequest.Builder> spanInjector) {
return new TraceRestClientRibbonCommandFactory(factory, tracer, spanInjector);
}
@@ -68,12 +70,12 @@ public class TraceZuulAutoConfiguration {
}
@Bean
SpanInjector<RequestContext> requestContextInjector() {
public SpanInjector<RequestContext> requestContextSpanInjector() {
return new RequestContextInjector();
}
@Bean
SpanInjector<HttpRequest.Builder> requestBuilderContextInjector() {
public SpanInjector<HttpRequest.Builder> requestBuilderContextSpanInjector() {
return new RequestBuilderContextInjector();
}

View File

@@ -56,7 +56,7 @@ public class SleuthLogAutoConfiguration {
@Bean
@ConditionalOnProperty(value = "spring.sleuth.log.slf4j.enabled", matchIfMissing = true)
public SpanLogger slf4jSpanLogger() {
// Sets up MDC entries X-Trace-Id and X-Span-Id
// Sets up MDC entries X-B3-TraceId and X-B3-SpanId
return new Slf4jSpanLogger(this.nameSkipPattern);
}

View File

@@ -3,6 +3,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration,\
org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration,\
org.springframework.cloud.sleuth.log.SleuthLogAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.messaging.TraceSpanMessagingAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.messaging.TraceSpringIntegrationAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.messaging.websocket.TraceWebSocketAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.async.AsyncCustomAutoConfiguration,\

View File

@@ -94,7 +94,7 @@ public class TraceChannelInterceptorTests implements MessageHandler {
@Test
public void nonExportableSpanCreation() {
this.channel.send(MessageBuilder.withPayload("hi")
.setHeader(Span.NOT_SAMPLED_NAME, "true").build());
.setHeader(Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build());
assertNotNull("message was null", this.message);
String spanId = this.message.getHeaders().get(Span.SPAN_ID_NAME, String.class);

View File

@@ -99,9 +99,7 @@ public class TraceFilterAlwaysSamplerIntegrationTests extends AbstractMvcIntegra
.accept(MediaType.TEXT_PLAIN)
.header(headerName, Span.idToHex(correlationId))
.header(Span.SPAN_ID_NAME, Span.idToHex(new Random().nextLong()));
if (!sampling) {
request.header(Span.NOT_SAMPLED_NAME, "true");
}
request.header(Span.SAMPLED_NAME, sampling ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED);
return this.mockMvc.perform(request).andReturn();
}

View File

@@ -0,0 +1,160 @@
/*
* 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.web;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanExtractor;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(TraceFilterCustomExtractorTests.Config.class)
@WebIntegrationTest(randomPort = true)
public class TraceFilterCustomExtractorTests {
@Autowired Random random;
@Autowired RestTemplate restTemplate;
@Autowired Config config;
@Autowired CustomRestController customRestController;
@Test
@SuppressWarnings("unchecked")
public void should_create_a_valid_span_from_custom_headers() {
long spanId = this.random.nextLong();
long traceId = this.random.nextLong();
RequestEntity requestEntity = RequestEntity.get(
URI.create("http://localhost:" + this.config.port + "/headers"))
.header("correlationId", Span.idToHex(traceId))
.header("mySpanId", Span.idToHex(spanId))
.build();
ResponseEntity<Map> requestHeaders =
this.restTemplate.exchange(requestEntity, Map.class);
then(this.customRestController.span)
.hasTraceIdEqualTo(traceId);
then(requestHeaders.getBody())
.containsEntry("correlationId", Span.idToHex(traceId))
.containsEntry("mySpanId", Span.idToHex(spanId))
.as("input request headers");
then(requestHeaders.getHeaders())
.containsEntry("correlationId", Collections.singletonList(Span.idToHex(traceId)))
.containsKey("mySpanId")
.as("response headers");
}
@Configuration
@EnableAutoConfiguration
static class Config implements
ApplicationListener<EmbeddedServletContainerInitializedEvent> {
int port;
// tag::configuration[]
@Bean
@Primary
SpanExtractor<HttpServletRequest> customHttpServletRequestSpanExtractor() {
return new CustomHttpServletRequestSpanExtractor();
}
@Bean
@Primary
SpanInjector<HttpServletResponse> customHttpServletResponseSpanInjector() {
return new CustomHttpServletResponseSpanInjector();
}
// end::configuration[]
@Override
public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
this.port = event.getEmbeddedServletContainer().getPort();
}
@Bean CustomRestController customRestController() {
return new CustomRestController();
}
}
// tag::extractor[]
static class CustomHttpServletRequestSpanExtractor implements SpanExtractor<HttpServletRequest> {
@Override
public Span joinTrace(HttpServletRequest carrier) {
long traceId = Span.hexToId(carrier.getHeader("correlationId"));
long spanId = Span.hexToId(carrier.getHeader("mySpanId"));
// extract all necessary headers
Span.SpanBuilder builder = Span.builder().traceId(traceId).spanId(spanId);
// build rest of the Span
return builder.build();
}
}
// end::extractor[]
// tag::injector[]
static class CustomHttpServletResponseSpanInjector implements SpanInjector<HttpServletResponse> {
@Override
public void inject(Span span, HttpServletResponse carrier) {
carrier.addHeader("correlationId", Span.idToHex(span.getTraceId()));
carrier.addHeader("mySpanId", Span.idToHex(span.getSpanId()));
// inject the rest of Span values to the header
}
}
// end::injector[]
@RestController
static class CustomRestController {
Span span;
@RequestMapping("/headers")
public Map<String, String> headers(@RequestHeader HttpHeaders headers) {
this.span = TestSpanContextHolder.getCurrentSpan();
Map<String, String> map = new HashMap<>();
for (String key : headers.keySet()) {
map.put(key, headers.getFirst(key));
}
return map;
}
}
}

View File

@@ -134,7 +134,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest {
}
private boolean notSampledHeaderIsPresent(MvcResult mvcResult) {
return mvcResult.getResponse().containsHeader(Span.NOT_SAMPLED_NAME);
return Span.SPAN_NOT_SAMPLED.equals(mvcResult.getResponse().getHeader(Span.SAMPLED_NAME));
}
@Configuration

View File

@@ -103,7 +103,7 @@ public class TraceRestTemplateInterceptorTests {
.getBody();
then(Span.hexToId(headers.get(Span.TRACE_ID_NAME))).isEqualTo(1L);
then(Span.hexToId(headers.get(Span.SPAN_ID_NAME))).isNotEqualTo(2L);
then(headers.get(Span.NOT_SAMPLED_NAME)).isEqualTo("true");
then(headers.get(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED);
}
// issue #198
@@ -141,7 +141,7 @@ public class TraceRestTemplateInterceptorTests {
this.span = TraceRestTemplateInterceptorTests.this.tracer.getCurrentSpan();
Map<String, String> map = new HashMap<String, String>();
addHeaders(map, headers, Span.SPAN_ID_NAME, Span.TRACE_ID_NAME,
Span.PARENT_ID_NAME, Span.NOT_SAMPLED_NAME);
Span.PARENT_ID_NAME, Span.SAMPLED_NAME);
return map;
}

View File

@@ -111,7 +111,7 @@ public class WebClientTests {
ResponseEntity<Map<String, String>> response = provider.get(this);
then(response.getBody().get(Span.TRACE_ID_NAME)).isNotNull();
then(response.getBody().get(Span.NOT_SAMPLED_NAME)).isNotNull();
then(response.getBody().get(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED);
then(this.listener.getEvents()).isNotEmpty();
}
@@ -132,6 +132,7 @@ public class WebClientTests {
ResponseEntity<String> response = provider.get(this);
then(getHeader(response, Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_SAMPLED);
then(Span.hexToId(getHeader(response, Span.TRACE_ID_NAME)))
.isEqualTo(currentTraceId);
thenRegisteredClientSentAndReceivedEvents(spanWithClientEvents());

View File

@@ -32,10 +32,7 @@ import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import com.netflix.zuul.context.RequestContext;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.assertj.core.api.BDDAssertions.then;
/**
* @author Dave Syer
@@ -58,23 +55,27 @@ public class TracePreZuulFilterTests {
@Test
public void filterAddsHeaders() throws Exception {
this.tracer.createSpan("http:start");
this.filter.run();
RequestContext ctx = RequestContext.getCurrentContext();
assertThat(ctx.getZuulRequestHeaders().get(Span.TRACE_ID_NAME),
is(notNullValue()));
assertThat(ctx.getZuulRequestHeaders().get(Span.NOT_SAMPLED_NAME),
is(nullValue()));
then(ctx.getZuulRequestHeaders().get(Span.TRACE_ID_NAME))
.isNotNull();
then(ctx.getZuulRequestHeaders().get(Span.SAMPLED_NAME))
.isEqualTo(Span.SPAN_SAMPLED);
}
@Test
public void notSampledIfNotExportable() throws Exception {
this.tracer.createSpan("http:start", NeverSampler.INSTANCE);
this.filter.run();
RequestContext ctx = RequestContext.getCurrentContext();
assertThat(ctx.getZuulRequestHeaders().get(Span.TRACE_ID_NAME),
is(notNullValue()));
assertThat(ctx.getZuulRequestHeaders().get(Span.NOT_SAMPLED_NAME),
is(notNullValue()));
then(ctx.getZuulRequestHeaders().get(Span.TRACE_ID_NAME))
.isNotNull();
then(ctx.getZuulRequestHeaders().get(Span.SAMPLED_NAME))
.isEqualTo(Span.SPAN_NOT_SAMPLED);
}
}

View File

@@ -26,7 +26,7 @@ import org.springframework.util.Assert;
* A {@link HostLocator} that retrieves:
*
* <ul>
* <li><b>service name</b> - either from {@link span#getProcessId()} or current application name</li>
* <li><b>service name</b> - either from {@link Span#getProcessId()} or current application name</li>
* <li><b>address</b> - from {@link ServerProperties}</li>
* <li><b>port</b> - from lazily assigned port or {@link ServerProperties}</li>
* </ul>

View File

@@ -49,7 +49,7 @@ public class StreamEnvironmentPostProcessor implements EnvironmentPostProcessor
private static final String PROPERTY_SOURCE_NAME = "defaultProperties";
private static String[] headers = new String[] { Span.SPAN_ID_NAME,
Span.TRACE_ID_NAME, Span.PARENT_ID_NAME, Span.PROCESS_ID_NAME,
Span.NOT_SAMPLED_NAME, Span.SPAN_NAME_NAME };
Span.SAMPLED_NAME, Span.SPAN_NAME_NAME };
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,

View File

@@ -43,7 +43,7 @@ class TracerIgnoringChannelInterceptor extends ChannelInterceptorAdapter {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
return MessageBuilder.fromMessage(message)
.setHeader(Span.NOT_SAMPLED_NAME, "true").build();
.setHeader(Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build();
}
@Override

View File

@@ -49,8 +49,8 @@ public class TracerIgnoringChannelInterceptorTest {
Message interceptedMessage = this.tracerIgnoringChannelInterceptor.preSend(message, this.messageChannel);
then(interceptedMessage.getHeaders().containsKey(
Span.NOT_SAMPLED_NAME)).isTrue();
then(interceptedMessage.getHeaders().get(
Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED);
}
@Test