diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncAutoConfiguration.java index 029e6b82a..e9f6ec110 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncAutoConfiguration.java @@ -33,7 +33,7 @@ public class AsyncAutoConfiguration { @Bean ContextRefreshedListener traceContextRefreshedListener() { - return new ContextRefreshedListener(); + return new ContextRefreshedListener(false); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ContextRefreshedListener.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ContextRefreshedListener.java index 17d68a89a..ca2afe4f5 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ContextRefreshedListener.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ContextRefreshedListener.java @@ -16,19 +16,32 @@ package org.springframework.cloud.sleuth.instrument.async; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.SmartApplicationListener; class ContextRefreshedListener extends AtomicBoolean implements SmartApplicationListener { + private static final Log log = LogFactory.getLog(ContextRefreshedListener.class); + + static final Map CACHE = new ConcurrentHashMap<>(); + ContextRefreshedListener(boolean initialValue) { super(initialValue); } ContextRefreshedListener() { + this(false); } @Override @@ -39,8 +52,23 @@ class ContextRefreshedListener extends AtomicBoolean implements SmartApplication @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent) { - set(true); + if (log.isDebugEnabled()) { + log.debug("Context successfully refreshed"); + } + ContextRefreshedEvent contextRefreshedEvent = (ContextRefreshedEvent) event; + ApplicationContext context = contextRefreshedEvent.getApplicationContext(); + BeanFactory beanFactory = context; + if (context instanceof ConfigurableApplicationContext) { + beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory(); + } + ContextRefreshedListener listener = CACHE.getOrDefault(beanFactory, this); + listener.set(true); + CACHE.put(beanFactory, listener); } } + static ContextRefreshedListener getBean(BeanFactory beanFactory) { + return CACHE.getOrDefault(beanFactory, new ContextRefreshedListener(false)); + } + } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ContextUtil.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ContextUtil.java index 2e0060525..35959f8e3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ContextUtil.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ContextUtil.java @@ -16,9 +16,6 @@ package org.springframework.cloud.sleuth.instrument.async; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -34,17 +31,8 @@ class ContextUtil { private static final Log log = LogFactory.getLog(ContextUtil.class); - private static Map CACHE = new ConcurrentHashMap<>(); - static boolean isContextInCreation(BeanFactory beanFactory) { - ContextRefreshedListener bean = CACHE.compute(beanFactory, - (beanFactory1, contextRefreshedListener) -> { - if (contextRefreshedListener != null) { - return contextRefreshedListener; - } - return beanFactory.getBean(ContextRefreshedListener.class); - }); - boolean contextRefreshed = bean.get(); + boolean contextRefreshed = ContextRefreshedListener.getBean(beanFactory).get(); if (!contextRefreshed && log.isDebugEnabled()) { log.debug("Context is not ready yet"); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/ContextRefreshedListenerAccessor.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/ContextRefreshedListenerAccessor.java new file mode 100644 index 000000000..5c9905470 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/ContextRefreshedListenerAccessor.java @@ -0,0 +1,27 @@ +/* + * 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 + * + * 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.async; + +import org.springframework.beans.factory.BeanFactory; + +public class ContextRefreshedListenerAccessor { + + public static void set(BeanFactory beanFactory, boolean refreshed) { + ContextRefreshedListener.CACHE.put(beanFactory, + new ContextRefreshedListener(refreshed)); + } + +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/ExecutorBeanPostProcessorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/ExecutorBeanPostProcessorTests.java index 921fc497e..138c2583f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/ExecutorBeanPostProcessorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/ExecutorBeanPostProcessorTests.java @@ -44,8 +44,6 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.aop.framework.AopConfigException; import org.springframework.aop.framework.ProxyFactoryBean; import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.ClassUtils; @@ -71,11 +69,6 @@ public class ExecutorBeanPostProcessorTests { this.sleuthAsyncProperties = new SleuthAsyncProperties(); Mockito.when(this.beanFactory.getBean(SleuthAsyncProperties.class)) .thenReturn(this.sleuthAsyncProperties); - Mockito.when(this.beanFactory.getBean(Tracing.class)).thenReturn(this.tracing); - Mockito.when(this.beanFactory.getBean(SpanNamer.class)) - .thenReturn(new DefaultSpanNamer()); - Mockito.when(this.beanFactory.getBean(ContextRefreshedListener.class)) - .thenReturn(new ContextRefreshedListener(true)); } @After @@ -299,12 +292,6 @@ public class ExecutorBeanPostProcessorTests { @Test public void should_throw_real_exception_when_using_proxy() throws Exception { - // for LazyTraceExecutor - Mockito.when(this.beanFactory.getBean(Tracing.class)) - .thenReturn(Tracing.newBuilder().build()); - Mockito.when(this.beanFactory.getBean(SpanNamer.class)) - .thenReturn(new DefaultSpanNamer()); - Object o = new ExecutorBeanPostProcessor(this.beanFactory) .postProcessAfterInitialization(new RejectedExecutionExecutor(), "fooExecutor"); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java index bce07ed75..b75696bbb 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java @@ -231,8 +231,7 @@ public class TraceableExecutorServiceTests { .willReturn(this.tracing); BDDMockito.given(this.beanFactory.getBean(SpanNamer.class)) .willReturn(new DefaultSpanNamer()); - BDDMockito.given(this.beanFactory.getBean(ContextRefreshedListener.class)) - .willReturn(new ContextRefreshedListener(refreshed)); + ContextRefreshedListenerAccessor.set(this.beanFactory, refreshed); return this.beanFactory; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorServiceTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorServiceTest.java index 2c84df2c0..2fdf8364c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorServiceTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorServiceTest.java @@ -111,8 +111,7 @@ public class TraceableScheduledExecutorServiceTest { @Test public void should_not_schedule_a_trace_runnable_when_context_not_ready() throws Exception { - BDDMockito.given(this.beanFactory.getBean(ContextRefreshedListener.class)) - .willReturn(new ContextRefreshedListener(false)); + ContextRefreshedListenerAccessor.set(this.beanFactory, false); this.traceableScheduledExecutorService.schedule(aRunnable(), 1L, TimeUnit.DAYS); then(this.scheduledExecutorService).should(never()).schedule( @@ -124,8 +123,7 @@ public class TraceableScheduledExecutorServiceTest { @Test public void should_not_schedule_a_trace_callable_when_context_not_ready() throws Exception { - BDDMockito.given(this.beanFactory.getBean(ContextRefreshedListener.class)) - .willReturn(new ContextRefreshedListener(false)); + ContextRefreshedListenerAccessor.set(this.beanFactory, false); this.traceableScheduledExecutorService.schedule(aCallable(), 1L, TimeUnit.DAYS); then(this.scheduledExecutorService).should(never()).schedule( @@ -137,8 +135,7 @@ public class TraceableScheduledExecutorServiceTest { @Test public void should_not_schedule_at_fixed_rate_a_trace_runnable_when_context_not_ready() throws Exception { - BDDMockito.given(this.beanFactory.getBean(ContextRefreshedListener.class)) - .willReturn(new ContextRefreshedListener(false)); + ContextRefreshedListenerAccessor.set(this.beanFactory, false); this.traceableScheduledExecutorService.scheduleAtFixedRate(aRunnable(), 1L, 1L, TimeUnit.DAYS); @@ -151,8 +148,7 @@ public class TraceableScheduledExecutorServiceTest { @Test public void should_not_schedule_with_fixed_delay_a_trace_runnable_when_context_not_ready() throws Exception { - BDDMockito.given(this.beanFactory.getBean(ContextRefreshedListener.class)) - .willReturn(new ContextRefreshedListener(false)); + ContextRefreshedListenerAccessor.set(this.beanFactory, false); this.traceableScheduledExecutorService.scheduleWithFixedDelay(aRunnable(), 1L, 1L, TimeUnit.DAYS); @@ -184,8 +180,7 @@ public class TraceableScheduledExecutorServiceTest { .willReturn(this.tracing); BDDMockito.given(this.beanFactory.getBean(SpanNamer.class)) .willReturn(new DefaultSpanNamer()); - BDDMockito.given(this.beanFactory.getBean(ContextRefreshedListener.class)) - .willReturn(new ContextRefreshedListener(true)); + ContextRefreshedListenerAccessor.set(this.beanFactory, true); return this.beanFactory; } 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 deb1b74de..026f8ec7d 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 @@ -16,12 +16,13 @@ import org.springframework.mock.web.server.MockServerWebExchange; public class TraceRequestHttpHeadersFilterTests { ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() .currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder() .addScopeDecorator(StrictScopeDecorator.create()).build()) .spanReporter(this.reporter).build(); - HttpTracing httpTracing = HttpTracing - .newBuilder(this.tracing).build(); + + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing).build(); @Test public void should_override_any_tracing_headers() { @@ -29,13 +30,9 @@ public class TraceRequestHttpHeadersFilterTests { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("X-B3-TraceId", "52f112af7472aff0"); httpHeaders.set("X-B3-SpanId", "53e6ab6fc5dfee58"); - MockServerHttpRequest request = MockServerHttpRequest - .post("foo/bar") - .headers(httpHeaders) - .build(); - MockServerWebExchange exchange = MockServerWebExchange - .builder(request) - .build(); + MockServerHttpRequest request = MockServerHttpRequest.post("foo/bar") + .headers(httpHeaders).build(); + MockServerWebExchange exchange = MockServerWebExchange.builder(request).build(); HttpHeaders filteredHeaders = filter.filter(httpHeaders, exchange); @@ -43,7 +40,10 @@ public class TraceRequestHttpHeadersFilterTests { .isNotEqualTo(httpHeaders.get("X-B3-TraceId")); BDDAssertions.then(filteredHeaders.get("X-B3-SpanId")) .isNotEqualTo(httpHeaders.get("X-B3-SpanId")); - BDDAssertions.then((Object) exchange.getAttribute(TraceRequestHttpHeadersFilter.SPAN_ATTRIBUTE)).isNotNull(); + BDDAssertions + .then((Object) exchange + .getAttribute(TraceRequestHttpHeadersFilter.SPAN_ATTRIBUTE)) + .isNotNull(); } } \ No newline at end of file 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 0f25d9efd..6386b2170 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 @@ -16,46 +16,42 @@ import org.springframework.mock.web.server.MockServerWebExchange; public class TraceResponseHttpHeadersFilterTests { ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() .currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder() .addScopeDecorator(StrictScopeDecorator.create()).build()) .spanReporter(this.reporter).build(); - HttpTracing httpTracing = HttpTracing - .newBuilder(this.tracing).build(); + + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing).build(); @Test public void should_not_report_span_when_no_span_was_present_in_attribute() { - HttpHeadersFilter filter = TraceResponseHttpHeadersFilter.create(this.httpTracing); + HttpHeadersFilter filter = TraceResponseHttpHeadersFilter + .create(this.httpTracing); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("X-B3-TraceId", "52f112af7472aff0"); httpHeaders.set("X-B3-SpanId", "53e6ab6fc5dfee58"); - MockServerHttpRequest request = MockServerHttpRequest - .post("foo/bar") - .headers(httpHeaders) - .build(); - MockServerWebExchange exchange = MockServerWebExchange - .builder(request) - .build(); + MockServerHttpRequest request = MockServerHttpRequest.post("foo/bar") + .headers(httpHeaders).build(); + MockServerWebExchange exchange = MockServerWebExchange.builder(request).build(); filter.filter(httpHeaders, exchange); BDDAssertions.then(this.reporter.getSpans()).isEmpty(); } - + @Test public void should_report_span_when_span_was_present_in_attribute() { - HttpHeadersFilter filter = TraceResponseHttpHeadersFilter.create(this.httpTracing); + HttpHeadersFilter filter = TraceResponseHttpHeadersFilter + .create(this.httpTracing); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("X-B3-TraceId", "52f112af7472aff0"); httpHeaders.set("X-B3-SpanId", "53e6ab6fc5dfee58"); - MockServerHttpRequest request = MockServerHttpRequest - .post("foo/bar") - .headers(httpHeaders) - .build(); - MockServerWebExchange exchange = MockServerWebExchange - .builder(request) - .build(); - exchange.getAttributes().put(TraceResponseHttpHeadersFilter.SPAN_ATTRIBUTE, this.tracing.tracer().nextSpan()); + MockServerHttpRequest request = MockServerHttpRequest.post("foo/bar") + .headers(httpHeaders).build(); + MockServerWebExchange exchange = MockServerWebExchange.builder(request).build(); + exchange.getAttributes().put(TraceResponseHttpHeadersFilter.SPAN_ATTRIBUTE, + this.tracing.tracer().nextSpan()); filter.filter(httpHeaders, exchange); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java deleted file mode 100644 index 1a008c405..000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java +++ /dev/null @@ -1,347 +0,0 @@ -/* - * Copyright 2013-2018 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.client.feign.servererrors; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import brave.Tracer; -import brave.Tracing; -import brave.sampler.Sampler; -import com.netflix.hystrix.exception.HystrixRuntimeException; -import com.netflix.loadbalancer.BaseLoadBalancer; -import com.netflix.loadbalancer.ILoadBalancer; -import com.netflix.loadbalancer.Server; -import feign.Logger; -import feign.codec.Decoder; -import feign.codec.ErrorDecoder; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.awaitility.Awaitility; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.netflix.ribbon.RibbonClient; -import org.springframework.cloud.netflix.ribbon.RibbonClients; -import org.springframework.cloud.openfeign.EnableFeignClients; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; -import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; -import zipkin2.Span; - -import static org.assertj.core.api.Assertions.fail; -import static org.assertj.core.api.BDDAssertions.then; - -/** - * Related to https://github.com/spring-cloud/spring-cloud-sleuth/issues/257 - * - * @author ryarabori - */ -@RunWith(SpringRunner.class) -@SpringBootTest(classes = FeignClientServerErrorTests.TestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@TestPropertySource(properties = { "spring.application.name=fooservice", - "feign.hystrix.enabled=true" }) -// TODO: TRY TO REPLICATE IT LOCALLY -@Ignore("FAILS ON CI. DOESN'T PROPAGATE TRACING HEADERS TO ANOTHER THREAD") -public class FeignClientServerErrorTests { - - private static final Log log = LogFactory.getLog(FeignClientServerErrorTests.class); - - @Autowired - TestFeignInterface feignInterface; - - @Autowired - TestFeignWithCustomConfInterface customConfFeignInterface; - - @Autowired - ArrayListSpanReporter reporter; - - @Autowired - Tracer tracer; - - @Before - public void setup() { - this.reporter.clear(); - } - - @Test - public void shouldCloseSpanOnInternalServerError() { - try (Tracer.SpanInScope ws = this.tracer - .withSpanInScope(this.tracer.nextSpan().name("foo").start())) { - log.info("sending a request"); - this.feignInterface.internalError(); - fail("Must throw an exception"); - } - catch (HystrixRuntimeException e) { - log.info("Expected exception thrown", e); - } - - Awaitility.await().untilAsserted(() -> { - List spans = this.reporter.getSpans(); - log.info("Spans " + spans); - Optional spanWithError = spans.stream() - .filter(span -> span.tags().containsKey("error")).findFirst(); - then(spanWithError.isPresent()).isTrue(); - then(spanWithError.get().tags()).containsEntry("error", "500") - .containsEntry("http.status_code", "500"); - }); - } - - @Test - public void shouldCloseSpanOnNotFound() { - try (Tracer.SpanInScope ws = this.tracer - .withSpanInScope(this.tracer.nextSpan().name("foo").start())) { - log.info("sending a request"); - this.feignInterface.notFound(); - fail("Must throw an exception"); - } - catch (HystrixRuntimeException e) { - log.info("Expected exception thrown", e); - } - - Awaitility.await().untilAsserted(() -> { - List spans = this.reporter.getSpans(); - log.info("Spans " + spans); - Optional spanWithError = spans.stream() - .filter(span -> span.tags().containsKey("http.status_code")) - .findFirst(); - then(spanWithError.isPresent()).isTrue(); - then(spanWithError.get().tags()).containsEntry("http.status_code", "404"); - }); - } - - @Test - public void shouldCloseSpanOnOk() { - try (Tracer.SpanInScope ws = this.tracer - .withSpanInScope(this.tracer.nextSpan().name("foo").start())) { - log.info("sending a request"); - this.feignInterface.ok(); - } - catch (HystrixRuntimeException e) { - log.info("Expected exception thrown", e); - } - - Awaitility.await().untilAsserted(() -> { - List spans = this.reporter.getSpans(); - log.info("Spans " + spans); - Optional httpSpan = spans.stream() - .filter(span -> span.tags().containsKey("http.method")).findFirst(); - then(httpSpan.isPresent()).isTrue(); - then(httpSpan.get().tags()).containsEntry("http.method", "GET") - .doesNotContainEntry("http.url", "http://fooservice/ok"); - }); - } - - @Test - public void shouldCloseSpanOnOkWithCustomFeignConfiguration() { - try (Tracer.SpanInScope ws = this.tracer - .withSpanInScope(this.tracer.nextSpan().name("foo").start())) { - log.info("sending a request"); - this.customConfFeignInterface.ok(); - fail("Must throw an exception"); - } - catch (HystrixRuntimeException e) { - log.info("Expected exception thrown", e); - } - - Awaitility.await().untilAsserted(() -> { - List spans = this.reporter.getSpans(); - log.info("Spans " + spans); - then(spans.size()).isGreaterThanOrEqualTo(1); - Optional httpSpan = spans.stream() - .filter(span -> span.tags().containsKey("http.method")).findFirst(); - then(httpSpan.isPresent()).isTrue(); - then(httpSpan.get().tags()).containsEntry("http.method", "GET"); - }); - } - - @Test - public void shouldCloseSpanOnNotFoundWithCustomFeignConfiguration() { - try (Tracer.SpanInScope ws = this.tracer - .withSpanInScope(this.tracer.nextSpan().name("foo").start())) { - log.info("sending a request"); - this.customConfFeignInterface.notFound(); - fail("Must throw an exception"); - } - catch (HystrixRuntimeException e) { - log.info("Expected exception thrown", e); - } - - Awaitility.await().untilAsserted(() -> { - List spans = this.reporter.getSpans(); - log.info("Spans " + spans); - Optional spanWithError = spans.stream() - .filter(span -> span.tags().containsKey("error")).findFirst(); - then(spanWithError.isPresent()).isTrue(); - then(spanWithError.get().tags()).containsEntry("error", "404") - .containsEntry("http.status_code", "404"); - }); - } - - @Configuration - @EnableAutoConfiguration(exclude = TraceWebServletAutoConfiguration.class) - @EnableFeignClients - @RibbonClients({ - @RibbonClient(value = "fooservice", configuration = SimpleRibbonClientConfiguration.class), - @RibbonClient(value = "customConfFooService", configuration = SimpleRibbonClientConfiguration.class) }) - public static class TestConfiguration { - - @Bean - FooController fooController() { - return new FooController(); - } - - @Bean - ArrayListSpanReporter listener() { - return new ArrayListSpanReporter(); - } - - @LoadBalanced - @Bean - RestTemplate restTemplate() { - return new RestTemplate(); - } - - @Bean - Sampler alwaysSampler() { - return Sampler.ALWAYS_SAMPLE; - } - - @Bean - Logger.Level feignLoggerLevel() { - return Logger.Level.FULL; - } - - } - - @FeignClient(value = "fooservice") - public interface TestFeignInterface { - - @RequestMapping(method = RequestMethod.GET, value = "/internalerror") - ResponseEntity internalError(); - - @RequestMapping(method = RequestMethod.GET, value = "/notfound") - ResponseEntity notFound(); - - @RequestMapping(method = RequestMethod.GET, value = "/ok") - ResponseEntity ok(); - - } - - @FeignClient(value = "customConfFooService", configuration = CustomFeignClientConfiguration.class) - public interface TestFeignWithCustomConfInterface { - - @RequestMapping(method = RequestMethod.GET, value = "/notfound") - ResponseEntity notFound(); - - @RequestMapping(method = RequestMethod.GET, value = "/ok") - ResponseEntity ok(); - - } - - @Configuration - public static class CustomFeignClientConfiguration { - - @Bean - Decoder decoder() { - return new Decoder.Default(); - } - - @Bean - ErrorDecoder errorDecoder() { - return new ErrorDecoder.Default(); - } - - } - - @RestController - public static class FooController { - - @Autowired - Tracing tracer; - - @RequestMapping("/internalerror") - public ResponseEntity internalError( - @RequestHeader("X-B3-TraceId") String traceId, - @RequestHeader("X-B3-SpanId") String spanId, - @RequestHeader("X-B3-ParentSpanId") String parentId) { - log.info("Will respond with internal error"); - logHeaders(traceId, spanId, parentId); - throw new RuntimeException("Internal Error"); - } - - @RequestMapping("/notfound") - public ResponseEntity notFound( - @RequestHeader("X-B3-TraceId") String traceId, - @RequestHeader("X-B3-SpanId") String spanId, - @RequestHeader("X-B3-ParentSpanId") String parentId) { - log.info("Will respond with not found"); - logHeaders(traceId, spanId, parentId); - return new ResponseEntity<>("not found", HttpStatus.NOT_FOUND); - } - - @RequestMapping("/ok") - public ResponseEntity ok(@RequestHeader("X-B3-TraceId") String traceId, - @RequestHeader("X-B3-SpanId") String spanId, - @RequestHeader("X-B3-ParentSpanId") String parentId) { - log.info("Will respond with OK"); - logHeaders(traceId, spanId, parentId); - return new ResponseEntity<>("ok", HttpStatus.OK); - } - - private void logHeaders(String traceId, String spanId, String parentId) { - log.info("Trace [" + traceId + "], span [" + spanId + "], parent [" + parentId - + "]"); - } - - } - - @Configuration - public static class SimpleRibbonClientConfiguration { - - @Value("${local.server.port}") - private int port = 0; - - @Bean - public ILoadBalancer ribbonLoadBalancer() { - BaseLoadBalancer balancer = new BaseLoadBalancer(); - balancer.setServersList( - Collections.singletonList(new Server("localhost", this.port))); - return balancer; - } - - } - -}