From b92db4ce51fb12f01218d89825fe2d300869e51c Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 7 Apr 2020 10:58:49 +0800 Subject: [PATCH] Stops propagating Sleuth 1.x spring-messaging headers To reduce confusion and overhead, the following custom spring-messaging headers added in Sleuth 1.0 are no longer sent, and a log warning is issued once if they are by outside code. * spanId * spanSampled * spanParentSpanId * spanTraceId * spanFlags Sending the above headers actually increases the headers by up to 10 because they are duplicated in the "native" part of messages. This overhead is extreme especially if messages never leave the process. The solution is to only send [b3 single format](https://github.com/openzipkin/b3-propagation#single-header), which has been in sleuth since 2.0 and is compatible with JMS. The B3 single format is always parsed and takes precedence, even if multiple headers are sent, so this is a safe change. Note: Unlike RPC, messaging spans never join with their parent. Better performance is achieved by not propagating the producer's parentId downstream. Note: Deprecated spring-messaging headers such "spanTraceId" as are still read in Sleuth 3.x. However, they will not be at some point in the future. Please pay attention to the log messages and update your code if you are accidentally using them. --- pom.xml | 7 -- .../TraceBaggageAutoConfiguration.java | 7 +- .../messaging/MessageHeaderPropagation.java | 45 ++++---- .../messaging/TraceMessageHeaders.java | 2 + ...gurationPropagationCustomizationTests.java | 12 +-- .../MessageHeaderPropagationTest.java | 26 ++--- .../messaging/PropagationSetterTest.java | 34 +++--- .../TracingChannelInterceptorTest.java | 100 ++++++++---------- .../opentracing/BraveTracerTest.java | 21 ++-- .../instrument/web/TraceFilterTests.java | 89 ++++------------ .../TraceRestTemplateInterceptorTests.java | 5 +- .../TraceRequestHttpHeadersFilterTests.java | 1 + .../TraceResponseHttpHeadersFilterTests.java | 6 +- .../client/integration/WebClientTests.java | 57 +++++----- .../java/tools/RequestSendingRunnable.java | 7 +- .../ITTracingChannelInterceptorTests.java | 6 +- ...extPropagationChannelInterceptorTests.java | 19 ++-- .../TraceStreamChannelInterceptorTests.java | 21 ++-- ...raceCustomFilterResponseInjectorTests.java | 22 ++-- .../web/TraceFilterIntegrationTests.java | 55 ++++------ .../sleuth/instrument/web/GH1102Tests.java | 4 +- .../instrument/web/TraceWebFluxTests.java | 3 +- 22 files changed, 233 insertions(+), 316 deletions(-) diff --git a/pom.xml b/pom.xml index 8a83b05f1..0bc993b77 100644 --- a/pom.xml +++ b/pom.xml @@ -132,13 +132,6 @@ - - - io.projectreactor.netty - reactor-netty - 0.9.6.RELEASE - true - org.springframework.cloud spring-cloud-sleuth-dependencies diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/baggage/TraceBaggageAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/baggage/TraceBaggageAutoConfiguration.java index 7019cf0df..c108609c5 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/baggage/TraceBaggageAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/baggage/TraceBaggageAutoConfiguration.java @@ -72,11 +72,16 @@ public class TraceBaggageAutoConfiguration { * To override the underlying context format, override this bean and set the delegate * to what you need. {@link BaggagePropagation.FactoryBuilder} will unwrap itself if * no fields are configured. + * + *

+ * This will use {@link B3Propagation.Format#SINGLE_NO_PARENT} for non-remote spans, + * such as for messaging. Note: it will still parse incoming multi-header spans. */ @Bean @ConditionalOnMissingBean BaggagePropagation.FactoryBuilder baggagePropagationFactoryBuilder() { - return BaggagePropagation.newFactoryBuilder(B3Propagation.FACTORY); + return BaggagePropagation.newFactoryBuilder(B3Propagation.newFactoryBuilder() + .injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build()); } @Bean diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagation.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagation.java index 4d0fffa9c..517ae3a6b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagation.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagation.java @@ -19,8 +19,11 @@ package org.springframework.cloud.sleuth.instrument.messaging; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import brave.propagation.Propagation; import org.apache.commons.logging.Log; @@ -45,25 +48,19 @@ enum MessageHeaderPropagation private static final Log log = LogFactory.getLog(MessageHeaderPropagation.class); + private static final Set DEPRECATED_HEADERS = new LinkedHashSet<>(); + private static final Map LEGACY_HEADER_MAPPING = new HashMap<>(); - private static final String TRACE_ID_NAME = "X-B3-TraceId"; - - private static final String SPAN_ID_NAME = "X-B3-SpanId"; - - private static final String PARENT_SPAN_ID_NAME = "X-B3-ParentSpanId"; - - private static final String SAMPLED_NAME = "X-B3-Sampled"; - - private static final String FLAGS_NAME = "X-B3-Flags"; - static { - LEGACY_HEADER_MAPPING.put(TRACE_ID_NAME, TraceMessageHeaders.TRACE_ID_NAME); - LEGACY_HEADER_MAPPING.put(SPAN_ID_NAME, TraceMessageHeaders.SPAN_ID_NAME); - LEGACY_HEADER_MAPPING.put(PARENT_SPAN_ID_NAME, + LEGACY_HEADER_MAPPING.put("X-B3-TraceId", TraceMessageHeaders.TRACE_ID_NAME); + LEGACY_HEADER_MAPPING.put("X-B3-SpanId", TraceMessageHeaders.SPAN_ID_NAME); + LEGACY_HEADER_MAPPING.put("X-B3-ParentSpanId", TraceMessageHeaders.PARENT_ID_NAME); - LEGACY_HEADER_MAPPING.put(SAMPLED_NAME, TraceMessageHeaders.SAMPLED_NAME); - LEGACY_HEADER_MAPPING.put(FLAGS_NAME, TraceMessageHeaders.SPAN_FLAGS_NAME); + LEGACY_HEADER_MAPPING.put("X-B3-Sampled", TraceMessageHeaders.SAMPLED_NAME); + LEGACY_HEADER_MAPPING.put("X-B3-Flags", TraceMessageHeaders.SPAN_FLAGS_NAME); + DEPRECATED_HEADERS.addAll(LEGACY_HEADER_MAPPING.keySet()); + DEPRECATED_HEADERS.addAll(LEGACY_HEADER_MAPPING.values()); } static Map propagationHeaders(Map headers, @@ -119,8 +116,15 @@ enum MessageHeaderPropagation return nativeAccessor; } + static final AtomicBoolean LOGGED_PUT_DEPRECATED_HEADER = new AtomicBoolean(); + @Override public void put(MessageHeaderAccessor accessor, String key, String value) { + if (DEPRECATED_HEADERS.contains(key) + && LOGGED_PUT_DEPRECATED_HEADER.compareAndSet(false, true)) { + log.warn("Please update your code so that it uses the 'b3' header " + + "instead of " + key); + } try { doPut(accessor, key, value); } @@ -130,10 +134,6 @@ enum MessageHeaderPropagation + "] from message", ex); } } - String legacyKey = LEGACY_HEADER_MAPPING.get(key); - if (legacyKey != null) { - doPut(accessor, legacyKey, value); - } } private void doPut(MessageHeaderAccessor accessor, String key, String value) { @@ -188,7 +188,14 @@ enum MessageHeaderPropagation return null; } + static final AtomicBoolean LOGGED_GET_DEPRECATED_HEADER = new AtomicBoolean(); + private String doGet(MessageHeaderAccessor accessor, String key) { + if (DEPRECATED_HEADERS.contains(key) + && LOGGED_GET_DEPRECATED_HEADER.compareAndSet(false, true)) { + log.warn("Please update your code so that it uses the 'b3' header " + + "instead of " + key); + } if (accessor instanceof NativeMessageHeaderAccessor) { NativeMessageHeaderAccessor nativeAccessor = (NativeMessageHeaderAccessor) accessor; String result = nativeAccessor.getFirstNativeHeader(key); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java index c90086733..1eb55d4ed 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java @@ -23,7 +23,9 @@ package org.springframework.cloud.sleuth.instrument.messaging; * * @author Marcin Grzejszczak * @since 1.0.4 + * @deprecated Since 3.0 these headers should not be used anymore as "b3" is sent instead */ +@Deprecated public final class TraceMessageHeaders { /** diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationPropagationCustomizationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationPropagationCustomizationTests.java index 6dcf250be..98c1e35f7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationPropagationCustomizationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationPropagationCustomizationTests.java @@ -16,6 +16,7 @@ package org.springframework.cloud.sleuth.autoconfig; +import brave.Tracing; import brave.baggage.BaggagePropagation; import brave.propagation.B3Propagation; import brave.propagation.B3SinglePropagation; @@ -41,8 +42,8 @@ public class TraceAutoConfigurationPropagationCustomizationTests { @Test public void stillCreatesDefault() { this.contextRunner.run((context) -> { - BDDAssertions.then(context.getBean(Propagation.Factory.class)) - .isEqualTo(B3Propagation.FACTORY); + BDDAssertions.then(context.getBean(Tracing.class).propagation()) + .isInstanceOf(B3Propagation.class); }); } @@ -52,8 +53,7 @@ public class TraceAutoConfigurationPropagationCustomizationTests { .withPropertyValues("spring.sleuth.baggage.remote-fields=country-code") .run((context) -> { BDDAssertions.then(context.getBean(Propagation.Factory.class)) - .hasFieldOrPropertyWithValue("delegate", - B3Propagation.FACTORY); + .extracting("delegate").isNotNull(); }); } @@ -61,8 +61,8 @@ public class TraceAutoConfigurationPropagationCustomizationTests { public void defaultValueUsedWhenApplicationNameNotSet() { this.contextRunner.withPropertyValues("spring.application.name=") .run((context) -> { - BDDAssertions.then(context.getBean(Propagation.Factory.class)) - .isEqualTo(B3Propagation.FACTORY); + BDDAssertions.then(context.getBean(Tracing.class).propagation()) + .isInstanceOf(B3Propagation.class); }); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagationTest.java index 8fef12e81..541e4f481 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagationTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagationTest.java @@ -56,26 +56,26 @@ public class MessageHeaderPropagationTest @Test public void testGetByteArrayValue() { MessageHeaderAccessor carrier = carrier(); - carrier.setHeader("X-B3-TraceId", "48485a3953bb6124".getBytes()); - carrier.setHeader("X-B3-TraceId", "48485a3953bb6124000000".getBytes()); - String value = MessageHeaderPropagation.INSTANCE.get(carrier, "X-B3-TraceId"); - assertThat(value).isEqualTo("48485a3953bb6124000000"); + carrier.setHeader("b3", "48485a3953bb6124-1234".getBytes()); + carrier.setHeader("b3", "48485a3953bb6124000000-1234".getBytes()); + String value = MessageHeaderPropagation.INSTANCE.get(carrier, "b3"); + assertThat(value).isEqualTo("48485a3953bb6124000000-1234"); } @Test public void testGetStringValue() { MessageHeaderAccessor carrier = carrier(); - carrier.setHeader("X-B3-TraceId", "48485a3953bb6124"); - carrier.setHeader("X-B3-TraceId", "48485a3953bb61240000000"); - String value = MessageHeaderPropagation.INSTANCE.get(carrier, "X-B3-TraceId"); - assertThat(value).isEqualTo("48485a3953bb61240000000"); + carrier.setHeader("B3", "48485a3953bb6124-1234"); + carrier.setHeader("B3", "48485a3953bb61240000000-1234"); + String value = MessageHeaderPropagation.INSTANCE.get(carrier, "B3"); + assertThat(value).isEqualTo("48485a3953bb61240000000-1234"); } @Test public void testGetNullValue() { MessageHeaderAccessor carrier = carrier(); - carrier.setHeader("X-B3-TraceId", "48485a3953bb6124"); - carrier.setHeader("X-B3-TraceId", "48485a3953bb61240000000"); + carrier.setHeader("B3", "48485a3953bb6124-1234"); + carrier.setHeader("B3", "48485a3953bb61240000000-1234"); String value = MessageHeaderPropagation.INSTANCE.get(carrier, "non existent key"); assertThat(value).isNull(); } @@ -85,7 +85,7 @@ public class MessageHeaderPropagationTest MessageHeaderAccessor carrier = carrier(); carrier.setHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS, "{spanTraceId=[123], spanId=[456], spanSampled=[0]}"); - MessageHeaderPropagation.INSTANCE.get(carrier, "X-B3-SpanId"); + MessageHeaderPropagation.INSTANCE.get(carrier, "b3"); } @Test @@ -94,7 +94,7 @@ public class MessageHeaderPropagationTest carrier.setHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS, "{spanTraceId=[123], spanId=[456], spanSampled=[0]}"); MessageHeaderPropagation.removeAnyTraceHeaders(carrier, - Collections.singletonList("X-B3-SpanId")); + Collections.singletonList("b3")); } @Test @@ -102,7 +102,7 @@ public class MessageHeaderPropagationTest MessageHeaderAccessor carrier = carrier(); carrier.setHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS, "{spanTraceId=[123], spanId=[456], spanSampled=[0]}"); - MessageHeaderPropagation.INSTANCE.put(carrier, "X-B3-SpanId", "1234"); + MessageHeaderPropagation.INSTANCE.put(carrier, "b3", "1234"); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/PropagationSetterTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/PropagationSetterTest.java index 978d27fc9..cb91b2821 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/PropagationSetterTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/PropagationSetterTest.java @@ -40,40 +40,38 @@ public abstract class PropagationSetterTest { @Test public void set() throws Exception { - K key = keyFactory().create("X-B3-TraceId"); - setter().put(carrier(), key, "48485a3953bb6124"); + K key = keyFactory().create("b3"); + setter().put(carrier(), key, "1"); - assertThat(read(carrier(), key)).containsExactly("48485a3953bb6124"); + assertThat(read(carrier(), key)).containsExactly("1"); } @Test public void set128() throws Exception { - K key = keyFactory().create("X-B3-TraceId"); - setter().put(carrier(), key, "463ac35c9f6413ad48485a3953bb6124"); + K key = keyFactory().create("b3"); + setter().put(carrier(), key, "1"); - assertThat(read(carrier(), key)) - .containsExactly("463ac35c9f6413ad48485a3953bb6124"); + assertThat(read(carrier(), key)).containsExactly("1"); } @Test public void setTwoKeys() throws Exception { - K key1 = keyFactory().create("X-B3-TraceId"); - K key2 = keyFactory().create("X-B3-SpanId"); - setter().put(carrier(), key1, "463ac35c9f6413ad48485a3953bb6124"); - setter().put(carrier(), key2, "48485a3953bb6124"); + K key1 = keyFactory().create("b3"); + K key2 = keyFactory().create("baggage"); + setter().put(carrier(), key1, "1"); + setter().put(carrier(), key2, "country-code=FO"); - assertThat(read(carrier(), key1)) - .containsExactly("463ac35c9f6413ad48485a3953bb6124"); - assertThat(read(carrier(), key2)).containsExactly("48485a3953bb6124"); + assertThat(read(carrier(), key1)).containsExactly("1"); + assertThat(read(carrier(), key2)).containsExactly("country-code=FO"); } @Test public void reset() throws Exception { - K key = keyFactory().create("X-B3-TraceId"); - setter().put(carrier(), key, "48485a3953bb6124"); - setter().put(carrier(), key, "463ac35c9f6413ad"); + K key = keyFactory().create("b3"); + setter().put(carrier(), key, "0"); + setter().put(carrier(), key, "1"); - assertThat(read(carrier(), key)).containsExactly("463ac35c9f6413ad"); + assertThat(read(carrier(), key)).containsExactly("1"); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptorTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptorTest.java index b2a275f2a..0903497e3 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptorTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptorTest.java @@ -17,13 +17,14 @@ package org.springframework.cloud.sleuth.instrument.messaging; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import brave.Tracing; +import brave.propagation.B3Propagation; import brave.propagation.StrictCurrentTraceContext; +import brave.propagation.TraceContext; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import zipkin2.Span; @@ -45,6 +46,8 @@ import org.springframework.messaging.support.ExecutorSubscribableChannel; import org.springframework.messaging.support.MessageBuilder; import org.springframework.messaging.support.NativeMessageHeaderAccessor; +import static brave.propagation.B3Propagation.Format.SINGLE; +import static brave.propagation.B3SingleFormat.parseB3SingleFormat; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.messaging.support.NativeMessageHeaderAccessor.NATIVE_HEADERS; @@ -55,6 +58,10 @@ public class TracingChannelInterceptorTest { List spans = new ArrayList<>(); Tracing tracing = Tracing.newBuilder().currentTraceContext(this.currentTraceContext) + // SINGLE_NO_PARENT more appropriate for messaging, but we check parent + // hereTraceMessageHeaders + .propagationFactory( + B3Propagation.newFactoryBuilder().injectFormat(SINGLE).build()) .spanReporter(this.spans::add).build(); ChannelInterceptor interceptor = TracingChannelInterceptor.create(tracing); @@ -92,8 +99,7 @@ public class TracingChannelInterceptorTest { this.channel.send(MessageBuilder.withPayload("foo").build()); - assertThat(this.channel.receive().getHeaders()).containsKeys("X-B3-TraceId", - "X-B3-SpanId", "X-B3-Sampled", "nativeHeaders"); + assertThat(this.channel.receive().getHeaders()).containsKey("b3"); assertThat(this.spans).hasSize(1).flatExtracting(Span::kind) .containsExactly(Span.Kind.PRODUCER); } @@ -105,8 +111,7 @@ public class TracingChannelInterceptorTest { this.directChannel.send(MessageBuilder.withPayload("foo").build()); assertThat(this.message).isNotNull(); - assertThat(this.message.getHeaders()).containsKeys("X-B3-TraceId", "X-B3-SpanId", - "X-B3-Sampled", "nativeHeaders"); + assertThat(this.message.getHeaders()).containsKeys("b3", "nativeHeaders"); assertThat(this.spans).flatExtracting(Span::kind).contains(Span.Kind.CONSUMER, Span.Kind.PRODUCER); } @@ -118,8 +123,7 @@ public class TracingChannelInterceptorTest { this.channel.send(MessageBuilder.withPayload("foo").build()); assertThat((Map) this.channel.receive().getHeaders().get(NATIVE_HEADERS)) - .containsOnlyKeys("X-B3-TraceId", "X-B3-SpanId", "X-B3-Sampled", - "spanTraceId", "spanId", "spanSampled"); + .containsOnlyKeys("b3"); } /** @@ -132,12 +136,11 @@ public class TracingChannelInterceptorTest { this.channel.addInterceptor(producerSideOnly(this.interceptor)); this.channel.send(MessageBuilder.withPayload("foo") - .setHeader("X-B3-TraceId", "000000000000000a") - .setHeader("X-B3-ParentSpanId", "000000000000000a") - .setHeader("X-B3-SpanId", "000000000000000b").build()); + .setHeader("b3", "000000000000000a-000000000000000b-1").build()); - assertThat(this.channel.receive().getHeaders()).containsEntry("X-B3-ParentSpanId", - "000000000000000b"); + TraceContext receiveContext = parseB3SingleFormat( + this.channel.receive().getHeaders().get("b3", String.class)).context(); + assertThat(receiveContext.parentIdString()).isEqualTo("000000000000000b"); } @Test @@ -147,16 +150,16 @@ public class TracingChannelInterceptorTest { NativeMessageHeaderAccessor accessor = new NativeMessageHeaderAccessor() { }; - accessor.setNativeHeader("X-B3-TraceId", "000000000000000a"); - accessor.setNativeHeader("X-B3-ParentSpanId", "000000000000000a"); - accessor.setNativeHeader("X-B3-SpanId", "000000000000000b"); + accessor.setNativeHeader("b3", + "000000000000000a-000000000000000b-1-000000000000000a"); this.channel.send(MessageBuilder.withPayload("foo") .copyHeaders(accessor.toMessageHeaders()).build()); - assertThat((Map) this.channel.receive().getHeaders().get(NATIVE_HEADERS)) - .containsEntry("X-B3-ParentSpanId", - Collections.singletonList("000000000000000b")); + TraceContext receiveContext = parseB3SingleFormat( + ((List) ((Map) this.channel.receive().getHeaders().get(NATIVE_HEADERS)) + .get("b3")).get(0).toString()).context(); + assertThat(receiveContext.parentIdString()).isEqualTo("000000000000000b"); } /** @@ -169,8 +172,8 @@ public class TracingChannelInterceptorTest { this.channel.send(MessageBuilder.withPayload("foo").build()); - assertThat(this.channel.receive().getHeaders()).containsKeys("X-B3-TraceId", - "X-B3-SpanId", "X-B3-Sampled", "nativeHeaders"); + assertThat(this.channel.receive().getHeaders()).containsKeys("b3", + "nativeHeaders"); assertThat(this.spans).hasSize(1).flatExtracting(Span::kind) .containsExactly(Span.Kind.CONSUMER); } @@ -182,8 +185,7 @@ public class TracingChannelInterceptorTest { this.channel.send(MessageBuilder.withPayload("foo").build()); assertThat((Map) this.channel.receive().getHeaders().get(NATIVE_HEADERS)) - .containsOnlyKeys("X-B3-TraceId", "X-B3-SpanId", "X-B3-Sampled", - "spanTraceId", "spanId", "spanSampled"); + .containsOnlyKeys("b3"); } @Test @@ -195,8 +197,8 @@ public class TracingChannelInterceptorTest { channel.send(MessageBuilder.withPayload("foo").build()); - assertThat(messages.get(0).getHeaders()).doesNotContainKeys("X-B3-TraceId", - "X-B3-SpanId", "X-B3-Sampled", "nativeHeaders"); + assertThat(messages.get(0).getHeaders()).doesNotContainKeys("b3", + "nativeHeaders"); assertThat(this.spans).flatExtracting(Span::kind) .containsExactly(Span.Kind.CONSUMER, null); } @@ -215,8 +217,7 @@ public class TracingChannelInterceptorTest { channel.send(MessageBuilder.withPayload("foo").build()); - assertThat(messages.get(0).getHeaders()).doesNotContainKeys("X-B3-TraceId", - "X-B3-SpanId", "X-B3-Sampled"); + assertThat(messages.get(0).getHeaders()).doesNotContainKeys("b3"); } @Test @@ -229,7 +230,7 @@ public class TracingChannelInterceptorTest { channel.send(MessageBuilder.withPayload("foo").build()); assertThat((Map) messages.get(0).getHeaders().get(NATIVE_HEADERS)) - .doesNotContainKeys("X-B3-TraceId", "X-B3-SpanId", "X-B3-Sampled"); + .doesNotContainKeys("b3"); } @Test @@ -264,27 +265,23 @@ public class TracingChannelInterceptorTest { Map errorChannelHeaders = new HashMap<>(); errorChannelHeaders.put(MessageHeaders.REPLY_CHANNEL, errorsReplyChannel); errorChannelHeaders.put(MessageHeaders.ERROR_CHANNEL, errorsReplyChannel); - this.channel - .send(new ErrorMessage( - new MessagingException(MessageBuilder.withPayload("hi") - .setHeader(TraceMessageHeaders.TRACE_ID_NAME, - "000000000000000a") - .setHeader(TraceMessageHeaders.SPAN_ID_NAME, - "000000000000000a") - .setReplyChannel(deadReplyChannel) - .setErrorChannel(deadReplyChannel).build()), - errorChannelHeaders)); + this.channel.send(new ErrorMessage( + new MessagingException(MessageBuilder.withPayload("hi") + .setHeader("b3", "000000000000000a-000000000000000a") + .setReplyChannel(deadReplyChannel) + .setErrorChannel(deadReplyChannel).build()), + errorChannelHeaders)); this.message = this.channel.receive(); assertThat(this.message).isNotNull(); - String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, - String.class); - assertThat(spanId).isNotNull(); - String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, - String.class); - assertThat(traceId).isEqualTo("000000000000000a"); - assertThat(spanId).isNotEqualTo("000000000000000a"); + + // Parse fails if trace or span ID are missing + TraceContext context = parseB3SingleFormat( + this.message.getHeaders().get("b3", String.class)).context(); + + assertThat(context.traceIdString()).isEqualTo("000000000000000a"); + assertThat(context.spanIdString()).isNotEqualTo("000000000000000a"); assertThat(this.spans).hasSize(2); assertThat(this.message.getHeaders().getReplyChannel()) .isSameAs(errorsReplyChannel); @@ -318,21 +315,16 @@ public class TracingChannelInterceptorTest { public void errorMessageHeadersWithNullPayloadRetained() { this.channel.addInterceptor(this.interceptor); Map errorChannelHeaders = new HashMap<>(); - errorChannelHeaders.put(TraceMessageHeaders.TRACE_ID_NAME, "000000000000000a"); - errorChannelHeaders.put(TraceMessageHeaders.SPAN_ID_NAME, "000000000000000a"); + errorChannelHeaders.put("b3", "000000000000000a-000000000000000a"); this.channel.send(new ErrorMessage(new MessagingException("exception"), errorChannelHeaders)); this.message = this.channel.receive(); - assertThat(this.message).isNotNull(); - String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, - String.class); - assertThat(spanId).isNotNull(); - String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, - String.class); - assertThat(traceId).isEqualTo("000000000000000a"); - assertThat(spanId).isNotEqualTo("000000000000000a"); + TraceContext receiveContext = parseB3SingleFormat( + this.message.getHeaders().get("b3", String.class)).context(); + assertThat(receiveContext.traceIdString()).isEqualTo("000000000000000a"); + assertThat(receiveContext.spanIdString()).isNotEqualTo("000000000000000a"); assertThat(this.spans).hasSize(2); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/opentracing/BraveTracerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/opentracing/BraveTracerTest.java index 77d2d6c7c..26cfd89b0 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/opentracing/BraveTracerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/opentracing/BraveTracerTest.java @@ -41,6 +41,7 @@ import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.data.MapEntry.entry; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE; @@ -82,10 +83,8 @@ public class BraveTracerTest { @Test public void extractTraceContext() { - Map map = new LinkedHashMap<>(); - map.put("X-B3-TraceId", "0000000000000001"); - map.put("X-B3-SpanId", "0000000000000002"); - map.put("X-B3-Sampled", "1"); + Map map = singletonMap("b3", + "0000000000000001-0000000000000002-1"); BraveSpanContext openTracingContext = this.opentracing .extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(map)); @@ -97,9 +96,7 @@ public class BraveTracerTest { @Test public void extractBaggage() { Map map = new LinkedHashMap<>(); - map.put("X-B3-TraceId", "0000000000000001"); - map.put("X-B3-SpanId", "0000000000000002"); - map.put("X-B3-Sampled", "1"); + map.put("b3", "0000000000000001-0000000000000002-1"); map.put("country-code", "FO"); BraveSpanContext openTracingContext = this.opentracing @@ -111,10 +108,8 @@ public class BraveTracerTest { @Test public void extractTraceContextTextMap() { - Map map = new LinkedHashMap<>(); - map.put("X-B3-TraceId", "0000000000000001"); - map.put("X-B3-SpanId", "0000000000000002"); - map.put("X-B3-Sampled", "1"); + Map map = singletonMap("b3", + "0000000000000001-0000000000000002-1"); BraveSpanContext openTracingContext = this.opentracing .extract(Format.Builtin.TEXT_MAP, new TextMapAdapter(map)); @@ -126,9 +121,7 @@ public class BraveTracerTest { @Test public void extractTraceContextCaseInsensitive() { Map map = new LinkedHashMap<>(); - map.put("X-B3-TraceId", "0000000000000001"); - map.put("x-b3-spanid", "0000000000000002"); - map.put("x-b3-SaMpLeD", "1"); + map.put("B3", "0000000000000001-0000000000000002-1"); map.put("other", "1"); BraveSpanContext openTracingContext = this.opentracing diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java index 9bb9d3172..f3657d089 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java @@ -36,7 +36,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; -import org.springframework.cloud.sleuth.util.SpanUtil; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -55,13 +54,6 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder */ public class TraceFilterTests { - static final String PARENT_ID = SpanUtil.idToHex(10L); - static final String TRACE_ID_NAME = "X-B3-TraceId"; - static final String SPAN_ID_NAME = "X-B3-SpanId"; - static final String PARENT_SPAN_ID_NAME = "X-B3-ParentSpanId"; - static final String SAMPLED_ID_NAME = "X-B3-Sampled"; - static final String SPAN_FLAGS = "X-B3-Flags"; - ArrayListSpanReporter reporter = new ArrayListSpanReporter(); Tracing tracing = Tracing.newBuilder() @@ -152,16 +144,15 @@ public class TraceFilterTests { @Test public void startsNewTraceWithParentIdInHeaders() throws Exception { - this.request = builder().header(SPAN_ID_NAME, PARENT_ID) - .header(TRACE_ID_NAME, SpanUtil.idToHex(2L)) - .header(PARENT_SPAN_ID_NAME, SpanUtil.idToHex(3L)) + this.request = builder() + .header("b3", "0000000000000002-0000000000000003-1-000000000000000a") .buildRequest(new MockServletContext()); this.filter.doFilter(this.request, this.response, this.filterChain); then(Tracing.current().tracer().currentSpan()).isNull(); then(this.reporter.getSpans()).hasSize(1); - then(this.reporter.getSpans().get(0).id()).isEqualTo(PARENT_ID); + then(this.reporter.getSpans().get(0).id()).isEqualTo("0000000000000003"); then(this.reporter.getSpans().get(0).tags()).containsEntry("http.path", "/") .containsEntry("http.method", HttpMethod.GET.toString()); } @@ -169,10 +160,8 @@ public class TraceFilterTests { @Test public void continuesATraceWhenSpanNotSampled() throws Exception { AtomicReference span = new AtomicReference<>(); - this.request = builder().header(SPAN_ID_NAME, PARENT_ID) - .header(TRACE_ID_NAME, SpanUtil.idToHex(2L)) - .header(PARENT_SPAN_ID_NAME, SpanUtil.idToHex(3L)) - .header(SAMPLED_ID_NAME, 0).buildRequest(new MockServletContext()); + this.request = builder().header("b3", "0000000000000014-000000000000000a-0") + .buildRequest(new MockServletContext()); this.filter.doFilter(this.request, this.response, (req, resp) -> { this.filterChain.doFilter(req, resp); @@ -180,7 +169,7 @@ public class TraceFilterTests { }); then(Tracing.current().tracer().currentSpan()).isNull(); - then(span.get().context().traceIdString()).isEqualTo(SpanUtil.idToHex(2L)); + then(span.get().context().traceIdString()).isEqualTo("0000000000000014"); } @Test @@ -215,8 +204,7 @@ public class TraceFilterTests { @Test public void continuesSpanFromHeaders() throws Exception { - this.request = builder().header(SPAN_ID_NAME, PARENT_ID) - .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + this.request = builder().header("b3", "0000000000000014-000000000000000a") .buildRequest(new MockServletContext()); this.filter.doFilter(this.request, this.response, this.filterChain); @@ -232,8 +220,7 @@ public class TraceFilterTests { .addScopeDecorator(StrictScopeDecorator.create()).build()) .spanReporter(this.reporter).supportsJoin(false).build(); HttpTracing httpTracing = HttpTracing.create(tracing); - this.request = builder().header(SPAN_ID_NAME, PARENT_ID) - .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + this.request = builder().header("b3", "0000000000000014-000000000000000a") .buildRequest(new MockServletContext()); TracingFilter.create(httpTracing).doFilter(this.request, this.response, @@ -241,13 +228,12 @@ public class TraceFilterTests { then(Tracing.current().tracer().currentSpan()).isNull(); then(this.reporter.getSpans()).hasSize(1); - then(this.reporter.getSpans().get(0).parentId()).isEqualTo(PARENT_ID); + then(this.reporter.getSpans().get(0).parentId()).isEqualTo("000000000000000a"); } @Test public void shouldAnnotateSpanWithErrorWhenExceptionIsThrown() throws Exception { - this.request = builder().header(SPAN_ID_NAME, PARENT_ID) - .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + this.request = builder().header("b3", "0000000000000014-000000000000000a") .buildRequest(new MockServletContext()); this.filterChain = new MockFilterChain() { @@ -273,8 +259,7 @@ public class TraceFilterTests { @Test public void detachesSpanWhenResponseStatusIsNot2xx() throws Exception { - this.request = builder().header(SPAN_ID_NAME, PARENT_ID) - .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + this.request = builder().header("b3", "14-a") .buildRequest(new MockServletContext()); this.response.setStatus(404); @@ -285,8 +270,7 @@ public class TraceFilterTests { @Test public void closesSpanWhenResponseStatusIs2xx() throws Exception { - this.request = builder().header(SPAN_ID_NAME, PARENT_ID) - .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + this.request = builder().header("b3", "0000000000000014-000000000000000a") .buildRequest(new MockServletContext()); this.response.setStatus(200); @@ -298,8 +282,7 @@ public class TraceFilterTests { @Test public void closesSpanWhenResponseStatusIs3xx() throws Exception { - this.request = builder().header(SPAN_ID_NAME, PARENT_ID) - .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + this.request = builder().header("b3", "0000000000000014-000000000000000a") .buildRequest(new MockServletContext()); this.response.setStatus(302); @@ -311,8 +294,7 @@ public class TraceFilterTests { @Test public void returns400IfSpanIsMalformedAndCreatesANewSpan() throws Exception { - this.request = builder().header(SPAN_ID_NAME, "asd") - .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + this.request = builder().header("b3", "asd") .buildRequest(new MockServletContext()); this.filter.doFilter(this.request, this.response, this.filterChain); @@ -324,9 +306,7 @@ public class TraceFilterTests { @Test public void returns200IfSpanParentIsMalformedAndCreatesANewSpan() throws Exception { - this.request = builder().header(SPAN_ID_NAME, PARENT_ID) - .header(PARENT_SPAN_ID_NAME, "-") - .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + this.request = builder().header("b3", "asd") .buildRequest(new MockServletContext()); this.filter.doFilter(this.request, this.response, this.filterChain); @@ -337,10 +317,8 @@ public class TraceFilterTests { } @Test - public void samplesASpanRegardlessOfTheSamplerWhenXB3FlagsIsPresentAndSetTo1() - throws Exception { - this.request = builder().header(SPAN_FLAGS, 1) - .buildRequest(new MockServletContext()); + public void samplesASpanRegardlessOfTheSamplerWhenDebugIsPresent() throws Exception { + this.request = builder().header("b3", "d").buildRequest(new MockServletContext()); neverSampleFilter().doFilter(this.request, this.response, this.filterChain); @@ -348,38 +326,10 @@ public class TraceFilterTests { then(this.reporter.getSpans()).isNotEmpty(); } - @Test - public void doesNotOverrideTheSampledFlagWhenXB3FlagIsSetToOtherValueThan1() - throws Exception { - this.request = builder().header(SPAN_FLAGS, 0) - .buildRequest(new MockServletContext()); - - this.filter.doFilter(this.request, this.response, this.filterChain); - - then(Tracing.current().tracer().currentSpan()).isNull(); - then(this.reporter.getSpans()).isNotEmpty(); - } - - @SuppressWarnings("Duplicates") - @Test - public void samplesWhenDebugFlagIsSetTo1AndOnlySpanIdIsSet() throws Exception { - this.request = builder().header(SPAN_FLAGS, 1) - .header(SPAN_ID_NAME, SpanUtil.idToHex(10L)) - .buildRequest(new MockServletContext()); - - neverSampleFilter().doFilter(this.request, this.response, this.filterChain); - - then(Tracing.current().tracer().currentSpan()).isNull(); - // It is ok to go without a trace ID, if sampling or debug is set - then(this.reporter.getSpans()).hasSize(1).extracting("id") - .isNotEqualTo(SpanUtil.idToHex(10L)); - } - @SuppressWarnings("Duplicates") @Test public void usesSamplingMechanismWhenIncomingTraceIsMalformed() throws Exception { - this.request = builder().header(SPAN_FLAGS, 1) - .header(TRACE_ID_NAME, SpanUtil.idToHex(10L)) + this.request = builder().header("b3", "asd") .buildRequest(new MockServletContext()); neverSampleFilter().doFilter(this.request, this.response, this.filterChain); @@ -407,8 +357,7 @@ public class TraceFilterTests { @Test public void samplesASpanDebugFlagWithInterceptor() throws Exception { - this.request = builder().header(SPAN_FLAGS, 1) - .buildRequest(new MockServletContext()); + this.request = builder().header("b3", "d").buildRequest(new MockServletContext()); neverSampleFilter().doFilter(this.request, this.response, this.filterChain); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceRestTemplateInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceRestTemplateInterceptorTests.java index ac48af08d..660971168 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceRestTemplateInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceRestTemplateInterceptorTests.java @@ -93,6 +93,7 @@ public class TraceRestTemplateInterceptorTests { Map headers = this.template.getForEntity("/", Map.class) .getBody(); + // Default inject format for client spans is B3 multi then(headers.get("X-B3-TraceId")).isNotNull(); then(headers.get("X-B3-SpanId")).isNotNull(); } @@ -109,6 +110,7 @@ public class TraceRestTemplateInterceptorTests { span.finish(); } + // Default inject format for client spans is B3 multi then(headers.get("X-B3-TraceId")) .isEqualTo(SpanUtil.idToHex(span.context().traceId())); then(headers.get("X-B3-SpanId")) @@ -205,7 +207,8 @@ public class TraceRestTemplateInterceptorTests { @RequestMapping("/") public Map home(@RequestHeader HttpHeaders headers) { this.span = TraceRestTemplateInterceptorTests.this.tracer.currentSpan(); - Map map = new HashMap(); + Map map = new HashMap<>(); + // Default inject format for client spans is B3 multi addHeaders(map, headers, "X-B3-SpanId", "X-B3-TraceId", "X-B3-ParentSpanId"); return map; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRequestHttpHeadersFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRequestHttpHeadersFilterTests.java index 2dae352d6..cdb5f662d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRequestHttpHeadersFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRequestHttpHeadersFilterTests.java @@ -32,6 +32,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; +// This test uses B3 multi format as it is the default for client propagation public class TraceRequestHttpHeadersFilterTests { StrictCurrentTraceContext currentTraceContext = StrictCurrentTraceContext.create(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceResponseHttpHeadersFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceResponseHttpHeadersFilterTests.java index 7ae0ede25..222d56002 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceResponseHttpHeadersFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceResponseHttpHeadersFilterTests.java @@ -51,8 +51,7 @@ public class TraceResponseHttpHeadersFilterTests { HttpHeadersFilter filter = TraceResponseHttpHeadersFilter .create(this.httpTracing); HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.set("X-B3-TraceId", "52f112af7472aff0"); - httpHeaders.set("X-B3-SpanId", "53e6ab6fc5dfee58"); + httpHeaders.set("b3", "52f112af7472aff0-53e6ab6fc5dfee58"); MockServerHttpRequest request = MockServerHttpRequest.post("foo/bar") .headers(httpHeaders).build(); MockServerWebExchange exchange = MockServerWebExchange.builder(request).build(); @@ -67,8 +66,7 @@ public class TraceResponseHttpHeadersFilterTests { HttpHeadersFilter filter = TraceResponseHttpHeadersFilter .create(this.httpTracing); HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.set("X-B3-TraceId", "52f112af7472aff0"); - httpHeaders.set("X-B3-SpanId", "53e6ab6fc5dfee58"); + httpHeaders.set("b3", "52f112af7472aff0-53e6ab6fc5dfee58"); MockServerHttpRequest request = MockServerHttpRequest.post("foo/bar") .headers(httpHeaders).build(); MockServerWebExchange exchange = MockServerWebExchange.builder(request).build(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index a9d4abd9c..38d612522 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -32,6 +32,9 @@ import javax.servlet.http.HttpServletRequest; import brave.Span; import brave.Tracer; import brave.Tracing; +import brave.baggage.BaggagePropagation; +import brave.propagation.B3Propagation; +import brave.propagation.B3SingleFormat; import brave.propagation.SamplingFlags; import brave.propagation.TraceContextOrSamplingFlags; import brave.sampler.Sampler; @@ -90,6 +93,8 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.function.client.UnknownHttpStatusCodeException; import org.springframework.web.reactive.function.client.WebClient; +import static brave.Span.Kind.CLIENT; +import static brave.propagation.B3Propagation.Format.SINGLE_NO_PARENT; import static org.assertj.core.api.Assertions.fail; import static org.assertj.core.api.BDDAssertions.then; @@ -100,11 +105,6 @@ import static org.assertj.core.api.BDDAssertions.then; @DirtiesContext public class WebClientTests { - static final String TRACE_ID_NAME = "X-B3-TraceId"; - static final String SPAN_ID_NAME = "X-B3-SpanId"; - static final String SAMPLED_NAME = "X-B3-Sampled"; - static final String PARENT_ID_NAME = "X-B3-ParentSpanId"; - private static final Log log = LogFactory.getLog(WebClientTests.class); @Autowired @@ -163,8 +163,7 @@ public class WebClientTests { ResponseEntity response = provider.get(this); Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> { - then(getHeader(response, TRACE_ID_NAME)).isNull(); - then(getHeader(response, SPAN_ID_NAME)).isNull(); + then(getHeader(response, "b3")).isNull(); List spans = this.reporter.getSpans(); then(spans).isNotEmpty(); Optional noTraceSpan = new ArrayList<>(spans).stream() @@ -220,8 +219,7 @@ public class WebClientTests { try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { ResponseEntity> response = provider.get(this); - then(response.getBody().get(TRACE_ID_NAME.toLowerCase())).isNotNull(); - then(response.getBody().get(SAMPLED_NAME.toLowerCase())).isEqualTo("0"); + then(response.getBody().get("b3")).isNotNull().endsWith("-0"); // not sampled } finally { span.finish(); @@ -250,8 +248,7 @@ public class WebClientTests { // https://github.com/spring-cloud/spring-cloud-sleuth/issues/327 // we don't want to respond with any tracing data - then(getHeader(response, SAMPLED_NAME)).isNull(); - then(getHeader(response, TRACE_ID_NAME)).isNull(); + then(getHeader(response, "b3")).isNull(); } finally { span.finish(); @@ -480,7 +477,7 @@ public class WebClientTests { AtomicReference traceId = new AtomicReference<>(); try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { this.webClientBuilder.filter((request, exchange) -> { - traceId.set(request.headers().getFirst("X-B3-SpanId")); + traceId.set(request.headers().getFirst("b3")); return exchange.exchange(request); }).build().get().uri("http://localhost:" + this.port + "/traceid").retrieve() @@ -531,6 +528,13 @@ public class WebClientTests { configuration = SimpleLoadBalancerClientConfiguration.class) public static class TestConfiguration { + @Bean + BaggagePropagation.FactoryBuilder baggagePropagationFactoryBuilder() { + // Use b3 single format as it is less verbose + return BaggagePropagation.newFactoryBuilder(B3Propagation.newFactoryBuilder() + .injectFormat(CLIENT, SINGLE_NO_PARENT).build()); + } + @Bean FooController fooController() { return new FooController(); @@ -625,26 +629,21 @@ public class WebClientTests { @RestController public static class FooController { - @Autowired - Tracer tracer; - Span span; @RequestMapping(value = "/notrace", method = RequestMethod.GET) public String notrace( - @RequestHeader(name = TRACE_ID_NAME, required = false) String traceId) { - then(traceId).isNotNull(); + @RequestHeader(name = "b3", required = false) String b3Single) { + then(b3Single).isNotNull(); return "OK"; } @RequestMapping(value = "/traceid", method = RequestMethod.GET) - public String traceId(@RequestHeader(TRACE_ID_NAME) String traceId, - @RequestHeader(SPAN_ID_NAME) String spanId, - @RequestHeader(PARENT_ID_NAME) String parentId) { - then(traceId).isNotEmpty(); - then(parentId).isNotEmpty(); - then(spanId).isNotEmpty(); - return traceId; + public String traceId(@RequestHeader("b3") String b3Single) { + TraceContextOrSamplingFlags traceContext = B3SingleFormat + .parseB3SingleFormat(b3Single); + then(traceContext.context()).isNotNull(); + return b3Single; } @RequestMapping("/") @@ -657,12 +656,10 @@ public class WebClientTests { } @RequestMapping("/noresponse") - public void noResponse(@RequestHeader(TRACE_ID_NAME) String traceId, - @RequestHeader(SPAN_ID_NAME) String spanId, - @RequestHeader(PARENT_ID_NAME) String parentId) { - then(traceId).isNotEmpty(); - then(parentId).isNotEmpty(); - then(spanId).isNotEmpty(); + public void noResponse(@RequestHeader("b3") String b3Single) { + TraceContextOrSamplingFlags traceContext = B3SingleFormat + .parseB3SingleFormat(b3Single); + then(traceContext.context()).isNotNull(); } public Span getSpan() { diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/RequestSendingRunnable.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/RequestSendingRunnable.java index eae741a9c..373245fea 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/RequestSendingRunnable.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/RequestSendingRunnable.java @@ -30,6 +30,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.BDDAssertions.then; +import static tools.SpanUtil.idToHex; /** * Runnable that will send a request via the provide rest template to the given url. It @@ -39,9 +40,6 @@ import static org.assertj.core.api.BDDAssertions.then; */ public class RequestSendingRunnable implements Runnable { - static final String TRACE_ID_NAME = "X-B3-TraceId"; - static final String SPAN_ID_NAME = "X-B3-SpanId"; - private static final Log log = LogFactory.getLog(RequestSendingRunnable.class); private final RestTemplate restTemplate; @@ -75,8 +73,7 @@ public class RequestSendingRunnable implements Runnable { private RequestEntity requestWithTraceId() { HttpHeaders headers = new HttpHeaders(); - headers.add(TRACE_ID_NAME, SpanUtil.idToHex(this.traceId)); - headers.add(SPAN_ID_NAME, SpanUtil.idToHex(this.spanId)); + headers.add("b3", idToHex(this.traceId) + "-" + idToHex(this.spanId)); URI uri = URI.create(this.url); RequestEntity requestEntity = new RequestEntity<>(headers, HttpMethod.GET, uri); diff --git a/tests/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/ITTracingChannelInterceptorTests.java b/tests/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/ITTracingChannelInterceptorTests.java index b582ff22f..71c87a90b 100644 --- a/tests/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/ITTracingChannelInterceptorTests.java +++ b/tests/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/ITTracingChannelInterceptorTests.java @@ -100,10 +100,10 @@ public class ITTracingChannelInterceptorTests implements MessageHandler { // formerly known as TraceChannelInterceptorTest.executableSpanCreation @Test public void propagatesNoopSpan() { - this.directChannel.send( - MessageBuilder.withPayload("hi").setHeader("X-B3-Sampled", "0").build()); + this.directChannel + .send(MessageBuilder.withPayload("hi").setHeader("b3", "0").build()); - assertThat(this.message.getHeaders()).containsEntry("X-B3-Sampled", "0"); + assertThat(this.message.getHeaders()).containsEntry("b3", "0"); assertThat(this.currentSpan.isNoop()).isTrue(); } diff --git a/tests/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java b/tests/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java index 9cbd75c06..269a45ffa 100644 --- a/tests/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java +++ b/tests/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java @@ -19,6 +19,8 @@ package org.springframework.cloud.sleuth.instrument.messaging; import brave.Span; import brave.Tracer; import brave.Tracing; +import brave.propagation.B3SingleFormat; +import brave.propagation.TraceContext; import brave.sampler.Sampler; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -80,19 +82,12 @@ public class TraceContextPropagationChannelInterceptorTests { Message message = this.channel.receive(0); assertThat(message).as("message was null").isNotNull(); - String spanId = message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, - String.class); - assertThat(spanId).as("spanId was equal to parent's id") + String b3 = message.getHeaders().get("b3", String.class); + // Trace and Span IDs are implicitly checked + TraceContext extracted = B3SingleFormat.parseB3SingleFormat(b3).context(); + + assertThat(extracted.spanIdString()).as("spanId was equal to parent's id") .isNotEqualTo(expectedSpanId); - - String traceId = message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, - String.class); - assertThat(traceId).as("traceId was null").isNotNull(); - - String parentId = message.getHeaders().get(TraceMessageHeaders.PARENT_ID_NAME, - String.class); - assertThat(parentId).as("parentId was not equal to parent's id") - .isEqualTo(this.reporter.getSpans().get(0).id()); } @Configuration diff --git a/tests/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceStreamChannelInterceptorTests.java b/tests/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceStreamChannelInterceptorTests.java index b4cefbb70..9c1617d59 100644 --- a/tests/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceStreamChannelInterceptorTests.java +++ b/tests/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceStreamChannelInterceptorTests.java @@ -19,6 +19,8 @@ package org.springframework.cloud.sleuth.instrument.messaging; import brave.Span; import brave.Tracer; import brave.Tracing; +import brave.propagation.B3SingleFormat; +import brave.propagation.TraceContext; import brave.sampler.Sampler; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -83,21 +85,12 @@ public class TraceStreamChannelInterceptorTests { Message message = this.channel.receive(0); assertThat(message).as("message was null").isNotNull(); - String spanId = message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, - String.class); - assertThat(spanId).as("spanId was equal to parent's id") + String b3 = message.getHeaders().get("b3", String.class); + // Trace and Span IDs are implicitly checked + TraceContext extracted = B3SingleFormat.parseB3SingleFormat(b3).context(); + + assertThat(extracted.spanIdString()).as("spanId was equal to parent's id") .isNotEqualTo(expectedSpanId); - - String traceId = message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, - String.class); - assertThat(traceId).as("traceId was null").isNotNull(); - - String parentId = message.getHeaders().get(TraceMessageHeaders.PARENT_ID_NAME, - String.class); - // [0] - producer - // [1] - http:testsendmessage - assertThat(parentId).as("parentId was not equal to parent's id") - .isEqualTo(this.reporter.getSpans().get(1).id()); } @Configuration diff --git a/tests/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java b/tests/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java index f531b0afd..296243663 100644 --- a/tests/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java +++ b/tests/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java @@ -28,14 +28,15 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import brave.Span; +import brave.baggage.BaggagePropagation; import brave.http.HttpTracing; +import brave.propagation.B3Propagation; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent; -import org.springframework.cloud.sleuth.util.SpanUtil; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -49,6 +50,9 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import org.springframework.web.filter.GenericFilterBean; +import static brave.Span.Kind.CLIENT; +import static brave.propagation.B3Propagation.Format.SINGLE_NO_PARENT; +import static brave.propagation.B3SingleFormat.writeB3SingleFormat; import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @@ -57,9 +61,6 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen @DirtiesContext public class TraceCustomFilterResponseInjectorTests { - static final String TRACE_ID_NAME = "X-B3-TraceId"; - static final String SPAN_ID_NAME = "X-B3-SpanId"; - @Autowired RestTemplate restTemplate; @@ -80,7 +81,7 @@ public class TraceCustomFilterResponseInjectorTests { ResponseEntity responseEntity = this.restTemplate.exchange(requestEntity, Map.class); - then(responseEntity.getHeaders()).containsKeys(TRACE_ID_NAME, SPAN_ID_NAME) + then(responseEntity.getHeaders()).containsKey("b3") .as("Trace headers must be present in response headers"); } @@ -90,6 +91,13 @@ public class TraceCustomFilterResponseInjectorTests { int port; + @Bean + BaggagePropagation.FactoryBuilder baggagePropagationFactoryBuilder() { + // Use b3 single format as it is less verbose + return BaggagePropagation.newFactoryBuilder(B3Propagation.newFactoryBuilder() + .injectFormat(CLIENT, SINGLE_NO_PARENT).build()); + } + // tag::configuration[] @Bean HttpResponseInjectingTraceFilter responseInjectingTraceFilter( @@ -129,9 +137,7 @@ public class TraceCustomFilterResponseInjectorTests { FilterChain filterChain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) servletResponse; Span currentSpan = this.httpTracing.tracing().tracer().currentSpan(); - response.addHeader("X-B3-TraceId", currentSpan.context().traceIdString()); - response.addHeader("X-B3-SpanId", - SpanUtil.idToHex(currentSpan.context().spanId())); + response.addHeader("b3", writeB3SingleFormat(currentSpan.context())); filterChain.doFilter(request, response); } diff --git a/tests/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java b/tests/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java index 49d3ae960..261d00891 100644 --- a/tests/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java +++ b/tests/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java @@ -69,12 +69,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest(classes = TraceFilterIntegrationTests.Config.class) public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { - static final String TRACE_ID_NAME = "X-B3-TraceId"; - static final String SPAN_ID_NAME = "X-B3-SpanId"; - static final String SAMPLED_NAME = "X-B3-Sampled"; - - private static Log logger = LogFactory.getLog(TraceFilterIntegrationTests.class); - private static Span span; @Autowired @@ -244,72 +238,69 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { } private MvcResult whenSentPingWithTraceId(Long passedTraceId) throws Exception { - return sendPingWithTraceId(TRACE_ID_NAME, passedTraceId); + return sendPingWithTraceId(passedTraceId); } private MvcResult whenSentInfoWithTraceId(Long passedTraceId) throws Exception { - return sendRequestWithTraceId("/additionalContextPath/info", TRACE_ID_NAME, - passedTraceId); + return sendRequestWithTraceId("/additionalContextPath/info", passedTraceId); } private MvcResult whenSentFutureWithTraceId(Long passedTraceId) throws Exception { - return sendRequestWithTraceId("/future", TRACE_ID_NAME, passedTraceId); + return sendRequestWithTraceId("/future", passedTraceId); } private MvcResult whenSentDeferredWithTraceId(Long passedTraceId) throws Exception { - return sendDeferredWithTraceId(TRACE_ID_NAME, passedTraceId); + return sendDeferredWithTraceId(passedTraceId); } private MvcResult whenSentToNonExistentEndpointWithTraceId(Long passedTraceId) throws Exception { - return sendRequestWithTraceId("/exception/nonExistent", TRACE_ID_NAME, - passedTraceId, HttpStatus.NOT_FOUND); + return sendRequestWithTraceId("/exception/nonExistent", passedTraceId, + HttpStatus.NOT_FOUND); } private MvcResult whenSentToExceptionThrowingEndpoint(Long passedTraceId) throws Exception { - return sendRequestWithTraceId("/throwsException", TRACE_ID_NAME, passedTraceId, + return sendRequestWithTraceId("/throwsException", passedTraceId, HttpStatus.INTERNAL_SERVER_ERROR); } - private MvcResult sendPingWithTraceId(String headerName, Long traceId) - throws Exception { - return sendRequestWithTraceId("/ping", headerName, traceId); + private MvcResult sendPingWithTraceId(Long traceId) throws Exception { + return sendRequestWithTraceId("/ping", traceId); } - private MvcResult sendDeferredWithTraceId(String headerName, Long traceId) - throws Exception { - return sendRequestWithTraceId("/deferred", headerName, traceId); + private MvcResult sendDeferredWithTraceId(Long traceId) throws Exception { + return sendRequestWithTraceId("/deferred", traceId); } - private MvcResult sendRequestWithTraceId(String path, String headerName, Long traceId) - throws Exception { + private MvcResult sendRequestWithTraceId(String path, Long traceId) throws Exception { return this.mockMvc .perform(MockMvcRequestBuilders.get(path).accept(MediaType.TEXT_PLAIN) - .header(headerName, SpanUtil.idToHex(traceId)) - .header(SPAN_ID_NAME, SpanUtil.idToHex(new Random().nextLong()))) + .header("b3", + SpanUtil.idToHex(traceId) + "-" + + SpanUtil.idToHex(new Random().nextLong()))) .andReturn(); } private MvcResult whenSentRequestWithTraceIdAndNoSpanId(Long traceId) throws Exception { - return this.mockMvc - .perform(MockMvcRequestBuilders.get("/ping").accept(MediaType.TEXT_PLAIN) - .header(TRACE_ID_NAME, SpanUtil.idToHex(traceId))) + return this.mockMvc.perform(MockMvcRequestBuilders.get("/ping") + .accept(MediaType.TEXT_PLAIN).header("b3", SpanUtil.idToHex(traceId))) .andReturn(); } - private MvcResult sendRequestWithTraceId(String path, String headerName, Long traceId, - HttpStatus status) throws Exception { + private MvcResult sendRequestWithTraceId(String path, Long traceId, HttpStatus status) + throws Exception { return this.mockMvc .perform(MockMvcRequestBuilders.get(path).accept(MediaType.TEXT_PLAIN) - .header(headerName, SpanUtil.idToHex(traceId)) - .header(SPAN_ID_NAME, SpanUtil.idToHex(new Random().nextLong()))) + .header("b3", + SpanUtil.idToHex(traceId) + "-" + + SpanUtil.idToHex(new Random().nextLong()))) .andExpect(status().is(status.value())).andReturn(); } private boolean notSampledHeaderIsPresent(MvcResult mvcResult) { - return "0".equals(mvcResult.getResponse().getHeader(SAMPLED_NAME)); + return "0".equals(mvcResult.getResponse().getHeader("b3")); } @EnableAutoConfiguration diff --git a/tests/spring-cloud-sleuth-instrumentation-webflux-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/GH1102Tests.java b/tests/spring-cloud-sleuth-instrumentation-webflux-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/GH1102Tests.java index ea6d437a1..206e2a862 100644 --- a/tests/spring-cloud-sleuth-instrumentation-webflux-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/GH1102Tests.java +++ b/tests/spring-cloud-sleuth-instrumentation-webflux-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/GH1102Tests.java @@ -52,9 +52,6 @@ public class GH1102Tests { @Autowired TestRetry testRetry; - @Autowired - ArrayListSpanReporter reporter; - @LocalServerPort int port; @@ -73,6 +70,7 @@ public class GH1102Tests { foo.finish(); } + // Default inject format for client spans is B3 multi BDDAssertions.then(this.testRetry.getHttpHeaders().get("x-b3-traceid")) .hasSize(1); } diff --git a/tests/spring-cloud-sleuth-instrumentation-webflux-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxTests.java b/tests/spring-cloud-sleuth-instrumentation-webflux-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxTests.java index 322a28a18..eca71f507 100644 --- a/tests/spring-cloud-sleuth-instrumentation-webflux-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxTests.java +++ b/tests/spring-cloud-sleuth-instrumentation-webflux-tests/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxTests.java @@ -155,8 +155,7 @@ public class TraceWebFluxTests { private ClientResponse whenNonSampledRequestIsSent(int port) { Mono exchange = WebClient.create().get() .uri("http://localhost:" + port + "/api/c2/10") - .header("X-B3-SpanId", EXPECTED_TRACE_ID) - .header("X-B3-TraceId", EXPECTED_TRACE_ID).header("X-B3-Sampled", "0") + .header("b3", EXPECTED_TRACE_ID + "-" + EXPECTED_TRACE_ID + "-0") .exchange(); return exchange.block(); }