From 3bb7916fffd33072e0b3bd66531a85420fc11aaf Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 16 Mar 2016 16:10:28 +0100 Subject: [PATCH] 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 --- README.adoc | 2 +- docs/src/main/asciidoc/features.adoc | 6 +- .../main/asciidoc/spring-cloud-sleuth.adoc | 87 +++++++++- .../springframework/cloud/sleuth/Span.java | 20 ++- .../TraceEnvironmentPostProcessor.java | 2 +- .../messaging/MessagingSpanExtractor.java | 4 +- .../messaging/MessagingSpanInjector.java | 7 +- .../messaging/TraceChannelInterceptor.java | 2 +- .../TraceSpanMessagingAutoConfiguration.java | 49 ++++++ ...aceSpringIntegrationAutoConfiguration.java | 23 +-- .../TraceWebSocketAutoConfiguration.java | 31 +--- .../web/HttpServletRequestExtractor.java | 10 +- .../sleuth/instrument/web/TraceFilter.java | 23 +-- .../web/TraceWebAutoConfiguration.java | 13 +- .../web/client/HttpRequestInjector.java | 4 +- .../TraceWebClientAutoConfiguration.java | 2 +- .../feign/FeignRequestTemplateInjector.java | 7 +- .../feign/FeignResponseHeadersInjector.java | 4 +- .../zuul/RequestBuilderContextInjector.java | 4 +- .../zuul/RequestContextInjector.java | 7 +- .../zuul/TraceZuulAutoConfiguration.java | 10 +- .../log/SleuthLogAutoConfiguration.java | 2 +- .../main/resources/META-INF/spring.factories | 1 + .../TraceChannelInterceptorTests.java | 2 +- ...ceFilterAlwaysSamplerIntegrationTests.java | 4 +- .../web/TraceFilterCustomExtractorTests.java | 160 ++++++++++++++++++ .../web/TraceFilterIntegrationTests.java | 2 +- .../TraceRestTemplateInterceptorTests.java | 4 +- .../instrument/web/client/WebClientTests.java | 3 +- .../zuul/TracePreZuulFilterTests.java | 25 +-- .../stream/ServerPropertiesHostLocator.java | 2 +- .../StreamEnvironmentPostProcessor.java | 2 +- .../TracerIgnoringChannelInterceptor.java | 2 +- .../TracerIgnoringChannelInterceptorTest.java | 4 +- 34 files changed, 400 insertions(+), 130 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java diff --git a/README.adoc b/README.adoc index 314c0822d..4e436fe2f 100644 --- a/README.adoc +++ b/README.adoc @@ -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*. diff --git a/docs/src/main/asciidoc/features.adoc b/docs/src/main/asciidoc/features.adoc index d440d0ada..f702a5058 100644 --- a/docs/src/main/asciidoc/features.adoc +++ b/docs/src/main/asciidoc/features.adoc @@ -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*. \ No newline at end of file diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 4474da846..4d4f4905b 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -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 messagingSpanExtractor() { + ... + } + + @Bean + public SpanInjector 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 httpServletRequestSpanExtractor() { + ... +} + +@Bean +public SpanInjector 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 diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 56c255af4..e41936cd1 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -45,6 +45,15 @@ import org.springframework.util.StringUtils; *
  • cr - Client Received
  • * * + * Spring Cloud Sleuth uses Zipkin compatible header names + * + *
      + *
    • X-B3-TraceId: 64 encoded bits
    • + *
    • X-B3-SpanId: 64 encoded bits
    • + *
    • X-B3-ParentSpanId: 64 encoded bits
    • + *
    • X-B3-Sampled: Boolean (either “1” or “0”)
    • + *
    + * * @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"; /** diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java index 2760f7b86..9de92aff2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java @@ -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); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractor.java index a18d905d0..f1d163669 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractor.java @@ -50,9 +50,7 @@ public class MessagingSpanExtractor implements SpanExtractor { : 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); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjector.java index f37996296..aacb4ef6c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjector.java @@ -52,8 +52,8 @@ public class MessagingSpanInjector implements SpanInjector { 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 { } 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); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java index 8a70f7ac2..f8b798d65 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java @@ -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); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java new file mode 100644 index 000000000..6111c5f8f --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java @@ -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 messagingSpanExtractor(Random random) { + return new MessagingSpanExtractor(random); + } + + @Bean + public SpanInjector messagingSpanInjector(TraceKeys traceKeys) { + return new MessagingSpanInjector(traceKeys); + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java index 2c4a50b0e..f538c525e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java @@ -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 spanExtractor, - @Qualifier("messagingSpanInjector") SpanInjector spanInjector) { + SpanExtractor spanExtractor, + SpanInjector 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 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 messagingSpanInjector(TraceKeys traceKeys) { - return new MessagingSpanInjector(traceKeys); - } - } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfiguration.java index 1a70c7388..a806f0b8b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfiguration.java @@ -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 spanExtractor; - @Autowired @Qualifier("stompMessagingSpanInjector") SpanInjector spanInjector; + @Autowired SpanExtractor spanExtractor; + @Autowired SpanInjector 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 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 stompMessagingSpanInjector(TraceKeys traceKeys) { - return new MessagingSpanInjector(traceKeys); - } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java index 4bad6e395..5ff84dafa 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractor.java @@ -50,15 +50,23 @@ class HttpServletRequestExtractor implements SpanExtractor { @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); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java index 4450b2064..4a6e2a650 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java @@ -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); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index d357cd34f..772bab68d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -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 spanExtractor, - @Qualifier("httpServletResponseInjector") SpanInjector spanInjector) { + SpanExtractor spanExtractor, + SpanInjector 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 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 httpServletResponseInjector() { + public SpanInjector httpServletResponseSpanInjector() { return new HttpServletResponseInjector(); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java index fcb7f6eac..8f12f3150 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestInjector.java @@ -34,9 +34,7 @@ class HttpRequestInjector implements SpanInjector { 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()); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java index e86a74007..4ea5278cc 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -66,7 +66,7 @@ public class TraceWebClientAutoConfiguration { } @Bean - public SpanInjector httpRequestInjector() { + public SpanInjector httpRequestSpanInjector() { return new HttpRequestInjector(); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTemplateInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTemplateInjector.java index f605a242b..118927f5b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTemplateInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTemplateInjector.java @@ -34,15 +34,14 @@ class FeignRequestTemplateInjector implements SpanInjector { @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)); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersInjector.java index 3e9039799..9040570bb 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersInjector.java @@ -47,9 +47,11 @@ class FeignResponseHeadersInjector implements SpanInjector> 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; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java index 04583249c..d5bc577a8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java @@ -34,9 +34,11 @@ class RequestBuilderContextInjector implements SpanInjector { @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()); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java index a41be18fb..20b0c5d3a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java @@ -37,15 +37,14 @@ class RequestContextInjector implements SpanInjector { public void inject(Span span, RequestContext carrier) { Map 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()); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java index fdf8f70f7..58f12abca 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java @@ -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 spanInjector) { + public TracePreZuulFilter tracePreZuulFilter(Tracer tracer, + SpanInjector spanInjector) { return new TracePreZuulFilter(tracer, spanInjector); } @Bean public TraceRestClientRibbonCommandFactory traceRestClientRibbonCommandFactory(SpringClientFactory factory, - Tracer tracer, SpanInjector spanInjector) { + Tracer tracer, @Qualifier("requestBuilderContextSpanInjector") SpanInjector spanInjector) { return new TraceRestClientRibbonCommandFactory(factory, tracer, spanInjector); } @@ -68,12 +70,12 @@ public class TraceZuulAutoConfiguration { } @Bean - SpanInjector requestContextInjector() { + public SpanInjector requestContextSpanInjector() { return new RequestContextInjector(); } @Bean - SpanInjector requestBuilderContextInjector() { + public SpanInjector requestBuilderContextSpanInjector() { return new RequestBuilderContextInjector(); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java index d3434e8e3..2c3fd36a8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java @@ -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); } diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories index fc1f7cb64..f4b94cc8a 100644 --- a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories @@ -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,\ diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java index f8deb754a..76033268d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java @@ -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); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java index d6799e179..19c842b02 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java @@ -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(); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java new file mode 100644 index 000000000..05684bcb1 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java @@ -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 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 { + int port; + + // tag::configuration[] + @Bean + @Primary + SpanExtractor customHttpServletRequestSpanExtractor() { + return new CustomHttpServletRequestSpanExtractor(); + } + + @Bean + @Primary + SpanInjector 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 { + + @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 { + + @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 headers(@RequestHeader HttpHeaders headers) { + this.span = TestSpanContextHolder.getCurrentSpan(); + Map map = new HashMap<>(); + for (String key : headers.keySet()) { + map.put(key, headers.getFirst(key)); + } + return map; + } + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java index 72d3fd936..8465b19d6 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java @@ -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 diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java index 5772ba3f5..c98c4df7e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java @@ -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 map = new HashMap(); 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; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java index a067199c4..39ddeb572 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/WebClientTests.java @@ -111,7 +111,7 @@ public class WebClientTests { ResponseEntity> 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 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()); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java index 0a94387f5..428ee7229 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java @@ -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); } } diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java index f149daa31..26e4d8fec 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/ServerPropertiesHostLocator.java @@ -26,7 +26,7 @@ import org.springframework.util.Assert; * A {@link HostLocator} that retrieves: * *
      - *
    • service name - either from {@link span#getProcessId()} or current application name
    • + *
    • service name - either from {@link Span#getProcessId()} or current application name
    • *
    • address - from {@link ServerProperties}
    • *
    • port - from lazily assigned port or {@link ServerProperties}
    • *
    diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java index 1920b064c..9ddd19ddc 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/StreamEnvironmentPostProcessor.java @@ -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, diff --git a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/TracerIgnoringChannelInterceptor.java b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/TracerIgnoringChannelInterceptor.java index a61d11ced..32f6d832b 100644 --- a/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/TracerIgnoringChannelInterceptor.java +++ b/spring-cloud-sleuth-stream/src/main/java/org/springframework/cloud/sleuth/stream/TracerIgnoringChannelInterceptor.java @@ -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 diff --git a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TracerIgnoringChannelInterceptorTest.java b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TracerIgnoringChannelInterceptorTest.java index 768c4b815..213334974 100644 --- a/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TracerIgnoringChannelInterceptorTest.java +++ b/spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/TracerIgnoringChannelInterceptorTest.java @@ -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