diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
index 0f1b10947..96f4009cc 100644
--- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
@@ -1083,6 +1083,11 @@ include::{project-root}/spring-cloud-sleuth-core/src/test/java/org/springframewo
That way, you ensure that a new span is created and closed for each execution.
+=== Spring Cloud CircuitBreaker
+
+If you have Spring Cloud CircuitBreaker on the classpath, we will wrap the passed command `Supplier` and the fallback `Function` in its trace representations. In order to disable this instrumentation set `spring.sleuth.circuitbreaker.enabled` to `false`.
+
+
=== Hystrix
==== Custom Concurrency Strategy
diff --git a/pom.xml b/pom.xml
index 6c513597b..d86748f89 100644
--- a/pom.xml
+++ b/pom.xml
@@ -176,6 +176,13 @@
pom
import
+
+ org.springframework.cloud
+ spring-cloud-circuitbreaker-dependencies
+ ${spring-cloud-circuitbreaker.version}
+ pom
+ import
+
org.springframework.cloud
spring-cloud-stream-dependencies
@@ -255,6 +262,7 @@
2.2.1.BUILD-SNAPSHOT
2.2.1.BUILD-SNAPSHOT
2.2.1.BUILD-SNAPSHOT
+ 1.0.1.BUILD-SNAPSHOT
Horsham.RELEASE
2.2.1.BUILD-SNAPSHOT
2.2.1.BUILD-SNAPSHOT
diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml
index 4aeb423b0..27caad81e 100644
--- a/spring-cloud-sleuth-core/pom.xml
+++ b/spring-cloud-sleuth-core/pom.xml
@@ -95,6 +95,11 @@
spring-cloud-starter-gateway
true
+
+ org.springframework.cloud
+ spring-cloud-starter-circuitbreaker-resilience4j
+ true
+
org.springframework.cloud
spring-cloud-starter-openfeign
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/SleuthCircuitBreakerAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/SleuthCircuitBreakerAutoConfiguration.java
new file mode 100644
index 000000000..95886d5c0
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/SleuthCircuitBreakerAutoConfiguration.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2013-2019 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
+ *
+ * https://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.circuitbreaker;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import brave.Tracer;
+import brave.Tracing;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
+import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
+ * Auto-configuration} that registers instrumentation for circuit breakers.
+ *
+ * @author Marcin Grzejszczak
+ * @since 2.2.1
+ */
+@Configuration(proxyBeanMethods = false)
+@AutoConfigureAfter(TraceAutoConfiguration.class)
+@ConditionalOnClass(CircuitBreaker.class)
+@ConditionalOnBean(Tracing.class)
+@ConditionalOnProperty(value = "spring.sleuth.circuitbreaker.enabled",
+ matchIfMissing = true)
+@EnableConfigurationProperties(SleuthCircuitBreakerProperties.class)
+public class SleuthCircuitBreakerAutoConfiguration {
+
+ @Bean
+ TraceCircuitBreakerFactoryAspect traceCircuitBreakerFactoryAspect(Tracer tracer) {
+ return new TraceCircuitBreakerFactoryAspect(tracer);
+ }
+
+}
+
+@Aspect
+class TraceCircuitBreakerFactoryAspect {
+
+ private final Tracer tracer;
+
+ TraceCircuitBreakerFactoryAspect(Tracer tracer) {
+ this.tracer = tracer;
+ }
+
+ @Pointcut("execution(public * org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory.create(..))")
+ private void anyCircuitBreakerFactoryCreate() {
+ } // NOSONAR
+
+ @Around("anyCircuitBreakerFactoryCreate()")
+ public Object wrapFactory(ProceedingJoinPoint pjp) throws Throwable {
+ CircuitBreaker circuitBreaker = (CircuitBreaker) pjp.proceed();
+ return new TraceCircuitBreaker(circuitBreaker, this.tracer);
+ }
+
+}
+
+class TraceCircuitBreaker implements CircuitBreaker {
+
+ private final CircuitBreaker delegate;
+
+ private final Tracer tracer;
+
+ TraceCircuitBreaker(CircuitBreaker delegate, Tracer tracer) {
+ this.delegate = delegate;
+ this.tracer = tracer;
+ }
+
+ @Override
+ public T run(Supplier toRun, Function fallback) {
+ return this.delegate.run(new TraceSupplier<>(this.tracer, toRun),
+ new TraceFunction<>(this.tracer, fallback));
+ }
+
+ @Override
+ public T run(Supplier toRun) {
+ return this.delegate.run(new TraceSupplier<>(this.tracer, toRun));
+ }
+
+}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/SleuthCircuitBreakerProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/SleuthCircuitBreakerProperties.java
new file mode 100644
index 000000000..1cb9665a7
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/SleuthCircuitBreakerProperties.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013-2019 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
+ *
+ * https://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.circuitbreaker;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Sleuth Circuit Breaker settings.
+ *
+ * @author Marcin Grzejszczak
+ * @since 2.2.1
+ */
+@ConfigurationProperties("spring.sleuth.circuitbreaker")
+public class SleuthCircuitBreakerProperties {
+
+ /**
+ * Enable Spring Cloud CircuitBreaker instrumentation.
+ */
+ private boolean enabled = true;
+
+ public boolean isEnabled() {
+ return this.enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/TraceFunction.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/TraceFunction.java
new file mode 100644
index 000000000..a450a2344
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/TraceFunction.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018-2019 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
+ *
+ * https://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.circuitbreaker;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+import brave.Span;
+import brave.Tracer;
+
+/**
+ * Trace representation of a {@link Function}.
+ *
+ * @param type returned by the fallback
+ * @since 2.2.1
+ */
+public class TraceFunction implements Function {
+
+ private final Tracer tracer;
+
+ private final Function delegate;
+
+ private final AtomicReference span;
+
+ public TraceFunction(Tracer tracer, Function delegate) {
+ this.tracer = tracer;
+ this.delegate = delegate;
+ this.span = new AtomicReference<>(this.tracer.nextSpan());
+ }
+
+ @Override
+ public T apply(Throwable throwable) {
+ String name = this.delegate.getClass().getSimpleName();
+ Span span = this.span.get().name(name);
+ Throwable tr = null;
+ try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) {
+ return this.delegate.apply(throwable);
+ }
+ catch (Throwable t) {
+ tr = t;
+ throw t;
+ }
+ finally {
+ if (tr != null) {
+ String message = tr.getMessage() == null ? tr.getClass().getSimpleName()
+ : tr.getMessage();
+ span.tag("error", message);
+ }
+ span.finish();
+ this.span.set(null);
+ }
+ }
+
+}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/TraceSupplier.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/TraceSupplier.java
new file mode 100644
index 000000000..2ca6b2633
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/TraceSupplier.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018-2019 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
+ *
+ * https://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.circuitbreaker;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+import brave.Span;
+import brave.Tracer;
+
+/**
+ * Trace representation of a {@link Supplier}.
+ *
+ * @param type returned by the supplier
+ * @since 2.2.1
+ */
+public class TraceSupplier implements Supplier {
+
+ private final Tracer tracer;
+
+ private final Supplier delegate;
+
+ private final AtomicReference span;
+
+ public TraceSupplier(Tracer tracer, Supplier delegate) {
+ this.tracer = tracer;
+ this.delegate = delegate;
+ this.span = new AtomicReference<>(this.tracer.nextSpan());
+ }
+
+ @Override
+ public T get() {
+ String name = this.delegate.getClass().getSimpleName();
+ Span span = this.span.get().name(name);
+ Throwable tr = null;
+ try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) {
+ return this.delegate.get();
+ }
+ catch (Throwable t) {
+ tr = t;
+ throw t;
+ }
+ finally {
+ if (tr != null) {
+ String message = tr.getMessage() == null ? tr.getClass().getSimpleName()
+ : tr.getMessage();
+ span.tag("error", message);
+ }
+ span.finish();
+ this.span.set(null);
+ }
+ }
+
+}
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 9290a35c7..00b2c3099 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
@@ -16,6 +16,7 @@ org.springframework.cloud.sleuth.instrument.async.AsyncDefaultAutoConfiguration,
org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.web.client.feign.TraceFeignClientAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixAutoConfiguration,\
+org.springframework.cloud.sleuth.instrument.circuitbreaker.SleuthCircuitBreakerAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.rxjava.RxJavaAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.web.TraceWebFluxAutoConfiguration,\
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/CircuitBreakerIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/CircuitBreakerIntegrationTests.java
new file mode 100644
index 000000000..8346cbcb0
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/CircuitBreakerIntegrationTests.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2013-2019 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
+ *
+ * https://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.circuitbreaker;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import brave.ScopedSpan;
+import brave.Span;
+import brave.Tracer;
+import brave.sampler.Sampler;
+import org.assertj.core.api.BDDAssertions;
+import org.junit.Before;
+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.test.context.SpringBootTest;
+import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory;
+import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
+import org.springframework.cloud.sleuth.util.ArrayListSpanReporter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = CircuitBreakerIntegrationTests.Config.class)
+public class CircuitBreakerIntegrationTests {
+
+ @Autowired
+ ArrayListSpanReporter reporter;
+
+ @Autowired
+ Tracer tracer;
+
+ @Autowired
+ CircuitBreakerFactory factory;
+
+ @Before
+ public void setup() {
+ this.reporter.clear();
+ }
+
+ @Test
+ public void should_pass_tracing_information_when_using_circuit_breaker() {
+ // given
+ Tracer tracer = this.tracer;
+ ScopedSpan scopedSpan = null;
+ try {
+ scopedSpan = tracer.startScopedSpan("start");
+ // when
+ Span span = this.factory.create("name").run(tracer::currentSpan);
+
+ then(span).isNotNull();
+ then(scopedSpan.context().traceIdString())
+ .isEqualTo(span.context().traceIdString());
+ }
+ finally {
+ scopedSpan.finish();
+ }
+ }
+
+ @Test
+ public void should_pass_tracing_information_when_using_circuit_breaker_with_fallback() {
+ // given
+ Tracer tracer = this.tracer;
+ AtomicReference first = new AtomicReference<>();
+ AtomicReference second = new AtomicReference<>();
+ ScopedSpan scopedSpan = null;
+ try {
+ scopedSpan = tracer.startScopedSpan("start");
+ // when
+ BDDAssertions.thenThrownBy(() -> this.factory.create("name").run(() -> {
+ first.set(tracer.currentSpan());
+ throw new IllegalStateException("boom");
+ }, throwable -> {
+ second.set(tracer.currentSpan());
+ throw new IllegalStateException("boom2");
+ })).isInstanceOf(IllegalStateException.class).hasMessageContaining("boom2");
+
+ then(this.reporter.getSpans()).hasSize(2);
+ then(scopedSpan.context().traceIdString())
+ .isEqualTo(first.get().context().traceIdString());
+ then(scopedSpan.context().traceIdString())
+ .isEqualTo(second.get().context().traceIdString());
+ then(first.get().context().spanIdString())
+ .isNotEqualTo(second.get().context().spanIdString());
+
+ zipkin2.Span reportedSpan = this.reporter.getSpans().get(0);
+ then(reportedSpan.name()).contains("circuitbreakerintegrationtests");
+ then(reportedSpan.tags().get("error")).contains("boom");
+
+ reportedSpan = this.reporter.getSpans().get(1);
+ then(reportedSpan.name()).contains("circuitbreakerintegrationtests");
+ then(reportedSpan.tags().get("error")).contains("boom2");
+ }
+ finally {
+ scopedSpan.finish();
+ }
+ }
+
+ @Configuration
+ @EnableAutoConfiguration
+ static class Config {
+
+ @Bean
+ ArrayListSpanReporter arrayListSpanReporter() {
+ return new ArrayListSpanReporter();
+ }
+
+ @Bean
+ Resilience4JCircuitBreakerFactory resilience4JCircuitBreakerFactory() {
+ return new Resilience4JCircuitBreakerFactory();
+ }
+
+ @Bean
+ Sampler sampler() {
+ return Sampler.ALWAYS_SAMPLE;
+ }
+
+ }
+
+}
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/CircuitBreakerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/CircuitBreakerTests.java
new file mode 100644
index 000000000..a47e66ad2
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/circuitbreaker/CircuitBreakerTests.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2013-2019 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
+ *
+ * https://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.circuitbreaker;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import brave.ScopedSpan;
+import brave.Span;
+import brave.Tracer;
+import brave.Tracing;
+import brave.propagation.StrictScopeDecorator;
+import brave.propagation.ThreadLocalCurrentTraceContext;
+import brave.sampler.Sampler;
+import org.assertj.core.api.BDDAssertions;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory;
+import org.springframework.cloud.sleuth.util.ArrayListSpanReporter;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+public class CircuitBreakerTests {
+
+ ArrayListSpanReporter reporter = new ArrayListSpanReporter();
+
+ Tracing tracing = Tracing.newBuilder()
+ .currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder()
+ .addScopeDecorator(StrictScopeDecorator.create()).build())
+ .spanReporter(this.reporter).sampler(Sampler.ALWAYS_SAMPLE).build();
+
+ Tracer tracer = this.tracing.tracer();
+
+ @Before
+ public void setup() {
+ this.reporter.clear();
+ }
+
+ @Test
+ public void should_pass_tracing_information_when_using_circuit_breaker() {
+ // given
+ Tracer tracer = this.tracer;
+ ScopedSpan scopedSpan = null;
+ try {
+ scopedSpan = tracer.startScopedSpan("start");
+ // when
+ Span span = new Resilience4JCircuitBreakerFactory().create("name")
+ .run(new TraceSupplier<>(tracer, tracer::currentSpan));
+
+ then(span).isNotNull();
+ then(scopedSpan.context().traceIdString())
+ .isEqualTo(span.context().traceIdString());
+ }
+ finally {
+ scopedSpan.finish();
+ }
+ }
+
+ @Test
+ public void should_pass_tracing_information_when_using_circuit_breaker_with_fallback() {
+ // given
+ Tracer tracer = this.tracer;
+ AtomicReference first = new AtomicReference<>();
+ AtomicReference second = new AtomicReference<>();
+ ScopedSpan scopedSpan = null;
+ try {
+ scopedSpan = tracer.startScopedSpan("start");
+ // when
+ BDDAssertions.thenThrownBy(() -> new Resilience4JCircuitBreakerFactory()
+ .create("name").run(new TraceSupplier<>(tracer, () -> {
+ first.set(tracer.currentSpan());
+ throw new IllegalStateException("boom");
+ }), new TraceFunction<>(tracer, throwable -> {
+ second.set(tracer.currentSpan());
+ throw new IllegalStateException("boom2");
+ }))).isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("boom2");
+
+ then(this.reporter.getSpans()).hasSize(2);
+ then(scopedSpan.context().traceIdString())
+ .isEqualTo(first.get().context().traceIdString());
+ then(scopedSpan.context().traceIdString())
+ .isEqualTo(second.get().context().traceIdString());
+ then(first.get().context().spanIdString())
+ .isNotEqualTo(second.get().context().spanIdString());
+
+ zipkin2.Span reportedSpan = this.reporter.getSpans().get(1);
+ then(reportedSpan.name()).contains("circuitbreakertests");
+ then(reportedSpan.tags().get("error")).contains("boom2");
+ }
+ finally {
+ scopedSpan.finish();
+ }
+ }
+
+}