adding traceid and other headers for feign
fixes gh-20
This commit is contained in:
committed by
Spencer Gibb
parent
516a7e26a4
commit
1622372061
11
pom.xml
11
pom.xml
@@ -144,6 +144,12 @@
|
||||
<version>${spring-cloud-netflix.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-feign</artifactId>
|
||||
<version>${spring-cloud-netflix.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
@@ -160,6 +166,11 @@
|
||||
<artifactId>zuul-core</artifactId>
|
||||
<version>1.0.28</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.feign</groupId>
|
||||
<artifactId>feign-core</artifactId>
|
||||
<version>8.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjrt</artifactId>
|
||||
|
||||
@@ -44,8 +44,14 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-integration</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-feign</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-core</artifactId>
|
||||
<version>${spring-integration.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -53,6 +59,11 @@
|
||||
<artifactId>hystrix-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.feign</groupId>
|
||||
<artifactId>feign-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.zuul</groupId>
|
||||
<artifactId>zuul-core</artifactId>
|
||||
@@ -79,6 +90,22 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.archaius</groupId>
|
||||
<artifactId>archaius-core</artifactId>
|
||||
<version>0.7.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>2.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2013-2015 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 static java.util.Collections.singletonList;
|
||||
import static org.springframework.cloud.sleuth.Trace.PARENT_ID_NAME;
|
||||
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
|
||||
import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME;
|
||||
import static org.springframework.cloud.sleuth.TraceContextHolder.getCurrentSpan;
|
||||
import static org.springframework.cloud.sleuth.TraceContextHolder.isTracing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
|
||||
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.event.ClientSentEvent;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import feign.Client;
|
||||
import feign.FeignException;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import feign.Response;
|
||||
import feign.codec.Decoder;
|
||||
|
||||
/**
|
||||
*
|
||||
* Configuration for ensuring that TraceID is set on the response
|
||||
*
|
||||
* @author Marcin Grzejszczak, 4financeIT
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "spring.sleuth.feign.enabled", matchIfMissing = true)
|
||||
@ConditionalOnClass(Client.class)
|
||||
public class TraceFeignClientAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private ObjectFactory<HttpMessageConverters> messageConverters;
|
||||
|
||||
@Autowired
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public Decoder feignDecoder() {
|
||||
return new ResponseEntityDecoder(new SpringDecoder(messageConverters)) {
|
||||
@Override
|
||||
public Object decode(Response response, Type type) throws IOException,
|
||||
FeignException {
|
||||
return super.decode(Response.create(response.status(), response.reason(),
|
||||
headersWithTraceId(response.headers()), response.body()), type);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor traceIdRequestInterceptor() {
|
||||
return new RequestInterceptor() {
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
template.header(TRACE_ID_NAME, getCurrentSpan().getTraceId());
|
||||
setHeader(template, TRACE_ID_NAME, getCurrentSpan().getTraceId());
|
||||
setHeader(template, SPAN_ID_NAME, getCurrentSpan().getSpanId());
|
||||
setHeader(template, PARENT_ID_NAME, getParentId(getCurrentSpan()));
|
||||
publish(new ClientSentEvent(this, getCurrentSpan()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void publish(ApplicationEvent event) {
|
||||
if (this.publisher != null) {
|
||||
this.publisher.publishEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private String getParentId(Span span) {
|
||||
return span.getParents() != null && !span.getParents().isEmpty() ? span
|
||||
.getParents().get(0) : null;
|
||||
}
|
||||
|
||||
public void setHeader(RequestTemplate request, String name, String value) {
|
||||
if (value != null && !request.headers().containsKey(name) && isTracing()) {
|
||||
request.header(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Collection<String>> headersWithTraceId(
|
||||
Map<String, Collection<String>> headers) {
|
||||
Map<String, Collection<String>> newHeaders = new HashMap<>();
|
||||
newHeaders.putAll(headers);
|
||||
setHeader(newHeaders, TRACE_ID_NAME, getCurrentSpan().getTraceId());
|
||||
setHeader(newHeaders, SPAN_ID_NAME, getCurrentSpan().getSpanId());
|
||||
setHeader(newHeaders, PARENT_ID_NAME, getParentId(getCurrentSpan()));
|
||||
return newHeaders;
|
||||
}
|
||||
|
||||
public void setHeader(Map<String, Collection<String>> headers, String name, String value) {
|
||||
if (value != null && !headers.containsKey(name) && isTracing()) {
|
||||
headers.put(name, singletonList(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,5 @@ org.springframework.cloud.sleuth.instrument.async.AsyncCustomAutoConfiguration,\
|
||||
org.springframework.cloud.sleuth.instrument.async.AsyncDefaultAutoConfiguration,\
|
||||
org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAutoConfiguration,\
|
||||
org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration,\
|
||||
org.springframework.cloud.sleuth.instrument.web.client.TraceWebClientAutoConfiguration
|
||||
org.springframework.cloud.sleuth.instrument.web.client.TraceWebClientAutoConfiguration,\
|
||||
org.springframework.cloud.sleuth.instrument.web.client.TraceFeignClientAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package org.springframework.cloud.sleuth.instrument.web.client;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.cloud.sleuth.Trace.PARENT_ID_NAME;
|
||||
import static org.springframework.cloud.sleuth.Trace.SPAN_ID_NAME;
|
||||
import static org.springframework.cloud.sleuth.Trace.TRACE_ID_NAME;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.netflix.loadbalancer.BaseLoadBalancer;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
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.SpringApplicationConfiguration;
|
||||
import org.springframework.boot.test.WebIntegrationTest;
|
||||
import org.springframework.cloud.netflix.feign.EnableFeignClients;
|
||||
import org.springframework.cloud.netflix.feign.FeignClient;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.cloud.sleuth.MilliSpan;
|
||||
import org.springframework.cloud.sleuth.TraceContextHolder;
|
||||
import org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = { TraceWebAutoConfiguration.class,
|
||||
FeignTraceTest.TestConfiguration.class })
|
||||
@WebIntegrationTest(value = "spring.application.name=fooservice", randomPort = true)
|
||||
public class FeignTraceTest {
|
||||
|
||||
@Autowired
|
||||
TestFeignInterface testFeignInterface;
|
||||
|
||||
@Test
|
||||
public void shouldAttachTraceIdWhenUsingFeignClient() {
|
||||
// given
|
||||
String currentTraceId = "currentTraceId";
|
||||
String currentSpanId = "currentSpanId";
|
||||
String currentParentId = "currentParentId";
|
||||
TraceContextHolder.setCurrentSpan(MilliSpan.builder().traceId(currentTraceId)
|
||||
.spanId(currentSpanId).parent(currentParentId).build());
|
||||
|
||||
// when
|
||||
ResponseEntity<String> response = testFeignInterface.getHealth();
|
||||
|
||||
// then
|
||||
assertThat(getHeader(response, TRACE_ID_NAME)).isEqualTo(currentTraceId);
|
||||
assertThat(getHeader(response, SPAN_ID_NAME)).isEqualTo(currentSpanId);
|
||||
assertThat(getHeader(response, PARENT_ID_NAME)).isEqualTo(currentParentId);
|
||||
}
|
||||
|
||||
private String getHeader(ResponseEntity<String> response, String name) {
|
||||
List<String> headers = response.getHeaders().get(name);
|
||||
assertThat(headers).asList().isNotEmpty();
|
||||
return headers.get(0);
|
||||
}
|
||||
|
||||
@FeignClient("fooservice")
|
||||
public interface TestFeignInterface {
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/traceid")
|
||||
ResponseEntity<String> getHealth();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@EnableFeignClients
|
||||
@RibbonClient(name = "fooservice", configuration = SimpleRibbonClientConfiguration.class)
|
||||
public static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
FooController fooController() {
|
||||
return new FooController();
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
public static class FooController {
|
||||
|
||||
@RequestMapping(value = "/traceid", method = RequestMethod.GET)
|
||||
public String foo(@RequestHeader(TRACE_ID_NAME) String traceId,
|
||||
@RequestHeader(SPAN_ID_NAME) String spanId,
|
||||
@RequestHeader(PARENT_ID_NAME) String parentId) {
|
||||
assertThat(traceId).isNotEmpty();
|
||||
assertThat(parentId).isNotEmpty();
|
||||
assertThat(spanId).isNotEmpty();
|
||||
return traceId;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class SimpleRibbonClientConfiguration {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port = 0;
|
||||
|
||||
@Bean
|
||||
public ILoadBalancer ribbonLoadBalancer() {
|
||||
BaseLoadBalancer balancer = new BaseLoadBalancer();
|
||||
balancer.setServersList(Arrays.asList(new Server("localhost", port)));
|
||||
return balancer;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user