diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java index 09074ff08..21b8efed1 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java @@ -16,8 +16,10 @@ package org.springframework.cloud.sleuth.instrument.web.client; +import java.net.URI; + import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanAccessor; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.event.ClientReceivedEvent; import org.springframework.cloud.sleuth.event.ClientSentEvent; import org.springframework.context.ApplicationEvent; @@ -38,10 +40,10 @@ abstract class AbstractTraceHttpRequestInterceptor implements ApplicationEventPublisherAware { private ApplicationEventPublisher publisher; - private final SpanAccessor accessor; + protected final Tracer tracer; - protected AbstractTraceHttpRequestInterceptor(SpanAccessor accessor) { - this.accessor = accessor; + protected AbstractTraceHttpRequestInterceptor(Tracer tracer) { + this.tracer = tracer; } @Override @@ -70,7 +72,7 @@ abstract class AbstractTraceHttpRequestInterceptor private void setHeader(HttpRequest request, String name, String value) { if (StringUtils.hasText(value) && !request.getHeaders().containsKey(name) && - this.accessor.isTracing()) { + this.tracer.isTracing()) { request.getHeaders().add(name, value); } } @@ -86,9 +88,11 @@ abstract class AbstractTraceHttpRequestInterceptor * the client sent event */ protected void publishStartEvent(HttpRequest request) { - Span span = currentSpan(); - enrichWithTraceHeaders(request, span); - publish(new ClientSentEvent(this, span)); + URI uri = request.getURI(); + String spanName = uri.getScheme() + ":" + uri.getPath(); + Span newSpan = this.tracer.startTrace(spanName); + enrichWithTraceHeaders(request, newSpan); + publish(new ClientSentEvent(this, newSpan)); } /** @@ -99,6 +103,7 @@ abstract class AbstractTraceHttpRequestInterceptor return; } publish(new ClientReceivedEvent(this, currentSpan())); + this.tracer.close(this.currentSpan()); } private void publish(ApplicationEvent event) { @@ -108,11 +113,11 @@ abstract class AbstractTraceHttpRequestInterceptor } private Span currentSpan() { - return this.accessor.getCurrentSpan(); + return this.tracer.getCurrentSpan(); } protected boolean isTracing() { - return this.accessor.isTracing(); + return this.tracer.isTracing(); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java index 940524a2b..c78fa8838 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java @@ -19,7 +19,6 @@ package org.springframework.cloud.sleuth.instrument.web.client; import java.io.IOException; import java.net.URI; -import org.springframework.cloud.sleuth.SpanAccessor; import org.springframework.cloud.sleuth.Tracer; import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.http.HttpMethod; @@ -42,8 +41,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttpRequestInterceptor implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory { - private final Tracer tracer; - private final AsyncClientHttpRequestFactory delegate; + private final AsyncClientHttpRequestFactory asyncDelegate; private final ClientHttpRequestFactory syncDelegate; /** @@ -55,32 +53,29 @@ public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttp * * @see org.springframework.web.client.AsyncRestTemplate#AsyncRestTemplate(AsyncClientHttpRequestFactory) */ - public TraceAsyncClientHttpRequestFactoryWrapper(SpanAccessor accessor, Tracer tracer, - AsyncClientHttpRequestFactory delegate) { - super(accessor); - this.tracer = tracer; - this.delegate = delegate; - this.syncDelegate = delegate instanceof ClientHttpRequestFactory ? - (ClientHttpRequestFactory) delegate : defaultClientHttpRequestFactory(); + public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, + AsyncClientHttpRequestFactory asyncDelegate) { + super(tracer); + this.asyncDelegate = asyncDelegate; + this.syncDelegate = asyncDelegate instanceof ClientHttpRequestFactory ? + (ClientHttpRequestFactory) asyncDelegate : defaultClientHttpRequestFactory(); } /** * Default implementation that creates a {@link SimpleClientHttpRequestFactory} that * has a wrapped task executor via the {@link TraceAsyncListenableTaskExecutor} */ - public TraceAsyncClientHttpRequestFactoryWrapper(SpanAccessor accessor, Tracer tracer) { - super(accessor); - this.tracer = tracer; + public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer) { + super(tracer); SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = defaultClientHttpRequestFactory(); - this.delegate = simpleClientHttpRequestFactory; + this.asyncDelegate = simpleClientHttpRequestFactory; this.syncDelegate = simpleClientHttpRequestFactory; } - public TraceAsyncClientHttpRequestFactoryWrapper(SpanAccessor accessor, Tracer tracer, - AsyncClientHttpRequestFactory delegate, ClientHttpRequestFactory syncDelegate) { - super(accessor); - this.tracer = tracer; - this.delegate = delegate; + public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, + AsyncClientHttpRequestFactory asyncDelegate, ClientHttpRequestFactory syncDelegate) { + super(tracer); + this.asyncDelegate = asyncDelegate; this.syncDelegate = syncDelegate; } @@ -99,7 +94,8 @@ public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttp @Override public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException { - AsyncClientHttpRequest request = this.delegate.createAsyncRequest(uri, httpMethod); + AsyncClientHttpRequest request = this.asyncDelegate + .createAsyncRequest(uri, httpMethod); if (!isTracing()) { doNotSampleThisSpan(request); return request; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java new file mode 100644 index 000000000..d31a03c29 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java @@ -0,0 +1,119 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web.client; + +import java.net.URI; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.event.ClientReceivedEvent; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.core.task.AsyncListenableTaskExecutor; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.AsyncClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.web.client.AsyncRequestCallback; +import org.springframework.web.client.AsyncRestTemplate; +import org.springframework.web.client.ResponseExtractor; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +/** + * An {@link AsyncRestTemplate} that closes started spans when a response has been + * successfully received. + * + * @author Marcin Grzejszczak + * + * @since 1.0.0 + */ +class TraceAsyncRestTemplate extends AsyncRestTemplate + implements ApplicationEventPublisherAware { + + private ApplicationEventPublisher publisher; + + private final Tracer tracer; + + public TraceAsyncRestTemplate(Tracer tracer) { + super(); + this.tracer = tracer; + } + + public TraceAsyncRestTemplate(AsyncListenableTaskExecutor taskExecutor, Tracer tracer) { + super(taskExecutor); + this.tracer = tracer; + } + + public TraceAsyncRestTemplate(AsyncClientHttpRequestFactory asyncRequestFactory, + Tracer tracer) { + super(asyncRequestFactory); + this.tracer = tracer; + } + + public TraceAsyncRestTemplate(AsyncClientHttpRequestFactory asyncRequestFactory, + ClientHttpRequestFactory syncRequestFactory, Tracer tracer) { + super(asyncRequestFactory, syncRequestFactory); + this.tracer = tracer; + } + + public TraceAsyncRestTemplate(AsyncClientHttpRequestFactory requestFactory, + RestTemplate restTemplate, Tracer tracer) { + super(requestFactory, restTemplate); + this.tracer = tracer; + } + + @Override + protected ListenableFuture doExecute(URI url, HttpMethod method, + AsyncRequestCallback requestCallback, ResponseExtractor responseExtractor) + throws RestClientException { + try { + return super.doExecute(url, method, requestCallback, responseExtractor); + } finally { + finish(); + } + } + + private void finish() { + if (!isTracing()) { + return; + } + publish(new ClientReceivedEvent(this, currentSpan())); + this.tracer.close(this.currentSpan()); + } + + private void publish(ApplicationEvent event) { + if (this.publisher != null) { + this.publisher.publishEvent(event); + } + } + + private Span currentSpan() { + return this.tracer.getCurrentSpan(); + } + + private boolean isTracing() { + return this.tracer.isTracing(); + } + + @Override + public void setApplicationEventPublisher( + ApplicationEventPublisher applicationEventPublisher) { + this.publisher = applicationEventPublisher; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceFeignClientAutoConfiguration.java index a9b16b098..0b5168658 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceFeignClientAutoConfiguration.java @@ -20,6 +20,7 @@ import static java.util.Collections.singletonList; import java.io.IOException; import java.lang.reflect.Type; +import java.net.URI; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -36,7 +37,6 @@ import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder; import org.springframework.cloud.netflix.feign.support.SpringDecoder; import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanAccessor; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.event.ClientReceivedEvent; import org.springframework.cloud.sleuth.event.ClientSentEvent; @@ -84,7 +84,7 @@ public class TraceFeignClientAutoConfiguration { private ApplicationEventPublisher publisher; @Autowired - private SpanAccessor accessor; + private Tracer tracer; @Bean @Scope("prototype") @@ -112,6 +112,7 @@ public class TraceFeignClientAutoConfiguration { Span span = getCurrentSpan(); if (span != null) { publish(new ClientReceivedEvent(this, span)); + TraceFeignClientAutoConfiguration.this.tracer.close(span); } } } @@ -123,7 +124,9 @@ public class TraceFeignClientAutoConfiguration { return new RequestInterceptor() { @Override public void apply(RequestTemplate template) { - Span span = getCurrentSpan(); + URI uri = URI.create(template.url()); + String spanName = uri.getScheme() + ":" + uri.getPath(); + Span span = TraceFeignClientAutoConfiguration.this.tracer.startTrace(spanName); if (span == null) { setHeader(template, Span.NOT_SAMPLED_NAME, "true"); return; @@ -156,7 +159,7 @@ public class TraceFeignClientAutoConfiguration { public void setHeader(RequestTemplate request, String name, String value) { if (StringUtils.hasText(value) && !request.headers().containsKey(name) - && this.accessor.isTracing()) { + && this.tracer.isTracing()) { request.header(name, value); } } @@ -179,7 +182,7 @@ public class TraceFeignClientAutoConfiguration { public void setHeader(Map> headers, String name, String value) { if (StringUtils.hasText(value) && !headers.containsKey(name) - && this.accessor.isTracing()) { + && this.tracer.isTracing()) { headers.put(name, singletonList(value)); } } @@ -192,7 +195,7 @@ public class TraceFeignClientAutoConfiguration { } private Span getCurrentSpan() { - return this.accessor.getCurrentSpan(); + return this.tracer.getCurrentSpan(); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java index 7fbb90052..7d17c0321 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java @@ -17,7 +17,7 @@ package org.springframework.cloud.sleuth.instrument.web.client; import java.io.IOException; -import org.springframework.cloud.sleuth.SpanAccessor; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; @@ -31,15 +31,14 @@ import org.springframework.http.client.ClientHttpResponse; * @author Spencer Gibb * * @see org.springframework.web.client.RestTemplate - * @see SpanAccessor * * @since 1.0.0 */ public class TraceRestTemplateInterceptor extends AbstractTraceHttpRequestInterceptor implements ClientHttpRequestInterceptor { - public TraceRestTemplateInterceptor(SpanAccessor accessor) { - super(accessor); + public TraceRestTemplateInterceptor(Tracer tracer) { + super(tracer); } @Override diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java index 8b5498572..27d0a21b3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java @@ -47,14 +47,15 @@ public class TraceWebAsyncClientAutoConfiguration { @Bean @ConditionalOnMissingBean - public AsyncClientHttpRequestFactory asyncClientHttpRequestFactory(SpanAccessor spanAccessor, Tracer tracer) { - return new TraceAsyncClientHttpRequestFactoryWrapper(spanAccessor, tracer); + public AsyncClientHttpRequestFactory asyncClientHttpRequestFactory(Tracer tracer) { + return new TraceAsyncClientHttpRequestFactoryWrapper(tracer); } @Bean @ConditionalOnMissingBean - public AsyncRestTemplate asyncRestTemplate(AsyncClientHttpRequestFactory asyncClientHttpRequestFactory) { - return new AsyncRestTemplate(asyncClientHttpRequestFactory); + public AsyncRestTemplate asyncRestTemplate(AsyncClientHttpRequestFactory asyncClientHttpRequestFactory, + Tracer tracer) { + return new TraceAsyncRestTemplate(asyncClientHttpRequestFactory, tracer); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java index fd67a721b..bfc7f1935 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -28,7 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cloud.sleuth.SpanAccessor; +import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -46,14 +46,14 @@ import org.springframework.web.client.RestTemplate; @Configuration @ConditionalOnProperty(value = "spring.sleuth.client.enabled", matchIfMissing = true) @ConditionalOnClass(RestTemplate.class) -@ConditionalOnBean(SpanAccessor.class) +@ConditionalOnBean(Tracer.class) @AutoConfigureAfter(TraceAutoConfiguration.class) public class TraceWebClientAutoConfiguration { @Bean @ConditionalOnMissingBean - public TraceRestTemplateInterceptor traceRestTemplateInterceptor(SpanAccessor accessor) { - return new TraceRestTemplateInterceptor(accessor); + public TraceRestTemplateInterceptor traceRestTemplateInterceptor(Tracer tracer) { + return new TraceRestTemplateInterceptor(tracer); } @Bean diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/FeignTraceTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/FeignTraceTests.java index 1c90611e3..c1cf51d09 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/FeignTraceTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/FeignTraceTests.java @@ -90,15 +90,16 @@ public class FeignTraceTests { public void shouldAttachTraceIdWhenUsingFeignClient() { Long currentTraceId = 1L; Long currentParentId = 2L; + Long currentSpanId = generatedId(); this.tracer.continueSpan(Span.builder().traceId(currentTraceId) - .spanId(generatedId()).parent(currentParentId).build()); + .spanId(currentSpanId).parent(currentParentId).build()); ResponseEntity response = this.testFeignInterface.getTraceId(); then(Span.hexToId(getHeader(response, Span.TRACE_ID_NAME))) .isEqualTo(currentTraceId); then(Span.hexToId(getHeader(response, Span.PARENT_ID_NAME))) - .isEqualTo(currentParentId); + .isEqualTo(currentSpanId); then(this.listener.getEvents().size()).isEqualTo(2); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java index 8a689d88e..316c43d18 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java @@ -80,9 +80,9 @@ public class TraceRestTemplateInterceptorTests { @SuppressWarnings("unchecked") Map headers = this.template.getForEntity("/", Map.class) .getBody(); - then(Long.valueOf(headers.get(Span.TRACE_ID_NAME))).isEqualTo(1L); - then(Long.valueOf(headers.get(Span.SPAN_ID_NAME))).isEqualTo(2L); - then(Long.valueOf(headers.get(Span.PARENT_ID_NAME))).isEqualTo(3L); + then(Span.hexToId(headers.get(Span.TRACE_ID_NAME))).isEqualTo(1L); + then(Span.hexToId(headers.get(Span.SPAN_ID_NAME))).isNotEqualTo(2L); + then(Span.hexToId(headers.get(Span.PARENT_ID_NAME))).isEqualTo(2L); } @Test @@ -91,8 +91,8 @@ public class TraceRestTemplateInterceptorTests { @SuppressWarnings("unchecked") Map headers = this.template.getForEntity("/", Map.class) .getBody(); - then(Long.valueOf(headers.get(Span.TRACE_ID_NAME))).isEqualTo(1L); - then(Long.valueOf(headers.get(Span.SPAN_ID_NAME))).isEqualTo(2L); + then(Span.hexToId(headers.get(Span.TRACE_ID_NAME))).isEqualTo(1L); + then(Span.hexToId(headers.get(Span.SPAN_ID_NAME))).isNotEqualTo(2L); then(headers.get(Span.NOT_SAMPLED_NAME)).isEqualTo("true"); } diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java index 64d571663..516299e70 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationTests.java @@ -16,6 +16,7 @@ package integration; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Random; @@ -106,22 +107,32 @@ public class MessagingApplicationTests extends AbstractIntegrationTest { private void thenTheSpansHaveProperParentStructure() { Optional firstHttpSpan = findFirstHttpRequestSpan(); List eventSpans = findAllEventRelatedSpans(); - Optional eventSentSpan = findSpanWithAnnotation(eventSpans, Constants.SERVER_SEND); - Optional eventReceivedSpan = findSpanWithAnnotation(eventSpans, Constants.CLIENT_RECV); - Optional lastHttpSpan = findLastHttpSpan(); - thenAllSpansArePresent(firstHttpSpan, eventSpans, lastHttpSpan, eventSentSpan, eventReceivedSpan); - then(lastHttpSpan.get().parentId).isEqualTo(eventSentSpan.get().id); - then(eventSentSpan.get().parentId).isEqualTo(firstHttpSpan.get().id); - then(eventSentSpan.get()).isNotEqualTo(eventReceivedSpan.get()); + Optional eventSentSpan = findSpanWithAnnotation(Constants.SERVER_SEND); + Optional eventReceivedSpan = findSpanWithAnnotation(Constants.CLIENT_RECV); + Optional lastHttpSpansParent = findLastHttpSpansParent(); + thenAllSpansArePresent(firstHttpSpan, eventSpans, lastHttpSpansParent, eventSentSpan, eventReceivedSpan); + // "http:/parent/" -> "http:/" -> "message:messages" -> "http:/foo" (CS + CR) -> "http:/foo" (SS) -> "http:/foo" + Collections.sort(this.integrationTestSpanCollector.hashedSpans, (s1, s2) -> s1.timestamp.compareTo(s2.timestamp)); + then(this.integrationTestSpanCollector.hashedSpans).hasSize(6); + for (int i=0; i= 0) { + Span parent = this.integrationTestSpanCollector.hashedSpans.get(i - 1); + Span current = this.integrationTestSpanCollector.hashedSpans.get(i); + // there is a pair of spans having cs/cr and ss/sr + if (current.id != parent.id) { + then(current.parentId).isEqualTo(parent.id); + } + } + } } - private Optional findLastHttpSpan() { + private Optional findLastHttpSpansParent() { return this.integrationTestSpanCollector.hashedSpans.stream() - .filter(span -> "http:/foo".equals(span.name)).findFirst(); + .filter(span -> "http:/foo".equals(span.name) && !span.annotations.isEmpty()).findFirst(); } - private Optional findSpanWithAnnotation(List eventSpans, String annotationName) { - return eventSpans.stream() + private Optional findSpanWithAnnotation(String annotationName) { + return this.integrationTestSpanCollector.hashedSpans.stream() .filter(span -> span.annotations.stream().filter(annotation -> annotationName .equals(annotation.value)).findFirst().isPresent()) .findFirst();