diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml
index 5f54e6c2f..f90085dcb 100644
--- a/spring-cloud-sleuth-core/pom.xml
+++ b/spring-cloud-sleuth-core/pom.xml
@@ -75,6 +75,16 @@
rxjava
true
+
+ com.squareup.okhttp3
+ okhttp
+ true
+
+
+ org.apache.httpcomponents
+ httpclient
+ true
+
org.aspectj
aspectjrt
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java
new file mode 100644
index 000000000..0715052d0
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java
@@ -0,0 +1,45 @@
+/*
+ * 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.zuul;
+
+import org.apache.http.client.methods.RequestBuilder;
+import org.springframework.cloud.sleuth.Tracer;
+
+/**
+ * Customization of a Ribbon request for Apache HttpClient
+ *
+ * @author Marcin Grzejszczak
+ * @since 1.1.0
+ */
+class ApacheHttpClientRibbonRequestCustomizer extends SpanInjectingRibbonRequestCustomizer {
+
+ ApacheHttpClientRibbonRequestCustomizer(Tracer tracer) {
+ super(tracer);
+ }
+
+ @Override
+ public boolean accepts(Class aClass) {
+ return aClass == RequestBuilder.class;
+ }
+
+ @Override
+ void setHeader(RequestBuilder builder, String name, String value) {
+ if (value != null) {
+ builder.setHeader(name, value);
+ }
+ }
+}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java
new file mode 100644
index 000000000..01d11adb8
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java
@@ -0,0 +1,46 @@
+/*
+ * 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.zuul;
+
+import org.springframework.cloud.sleuth.Tracer;
+
+import okhttp3.Request;
+
+/**
+ * Customization of a Ribbon request for OkHttp
+ *
+ * @author Marcin Grzejszczak
+ * @since 1.1.0
+ */
+class OkHttpClientRibbonRequestCustomizer extends SpanInjectingRibbonRequestCustomizer {
+
+ OkHttpClientRibbonRequestCustomizer(Tracer tracer) {
+ super(tracer);
+ }
+
+ @Override
+ public boolean accepts(Class aClass) {
+ return aClass == Request.Builder.class;
+ }
+
+ @Override
+ void setHeader(Request.Builder builder, String name, String value) {
+ if (value != null) {
+ builder.addHeader(name, value);
+ }
+ }
+}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java
index 20b0c5d3a..ca730870e 100644
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextInjector.java
@@ -18,17 +18,16 @@ package org.springframework.cloud.sleuth.instrument.zuul;
import java.util.Map;
+import com.netflix.zuul.context.RequestContext;
+
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.util.StringUtils;
-import com.netflix.zuul.context.RequestContext;
-
/**
* Span injector that injects tracing info to {@link RequestContext}
*
* @author Marcin Grzejszczak
- *
* @since 1.0.0
*/
class RequestContextInjector implements SpanInjector {
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java
new file mode 100644
index 000000000..d3fca982e
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java
@@ -0,0 +1,46 @@
+/*
+ * 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.zuul;
+
+import com.netflix.client.http.HttpRequest;
+
+import org.springframework.cloud.sleuth.Tracer;
+
+/**
+ * Customization of a Ribbon request for Netflix HttpClient
+ *
+ * @author Marcin Grzejszczak
+ * @since 1.1.0
+ */
+class RestClientRibbonRequestCustomizer extends SpanInjectingRibbonRequestCustomizer {
+
+ RestClientRibbonRequestCustomizer(Tracer tracer) {
+ super(tracer);
+ }
+
+ @Override
+ public boolean accepts(Class aClass) {
+ return aClass == HttpRequest.Builder.class;
+ }
+
+ @Override
+ void setHeader(HttpRequest.Builder builder, String name, String value) {
+ if (value != null) {
+ builder.header(name, value);
+ }
+ }
+}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java
similarity index 50%
rename from spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java
rename to spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java
index d5bc577a8..db7fbf1ce 100644
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestBuilderContextInjector.java
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java
@@ -16,23 +16,49 @@
package org.springframework.cloud.sleuth.instrument.zuul;
+import java.lang.invoke.MethodHandles;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanInjector;
-
-import com.netflix.client.http.HttpRequest;
-import com.netflix.client.http.HttpRequest.Builder;
+import org.springframework.cloud.sleuth.Tracer;
/**
- * Span injector that injects tracing info to {@link Builder}
+ * Abstraction over customization of Ribbon Requests. All clients will inject the span
+ * into their respective context. The only difference is how those contexts set the headers.
+ * In order to add a new implementation of the {@link RibbonRequestCustomizer} it's
+ * necessary only to provide the {@link RibbonRequestCustomizer#accepts(Class)} method
+ * with the context class name and {@link SpanInjectingRibbonRequestCustomizer#setHeader(Object, String, String)}
+ * to tell Sleuth how to set a header using the particular library.
*
* @author Marcin Grzejszczak
- *
- * @since 1.0.0
+ * @since 1.1.0
*/
-class RequestBuilderContextInjector implements SpanInjector {
+abstract class SpanInjectingRibbonRequestCustomizer implements RibbonRequestCustomizer,
+ SpanInjector {
+
+ private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());
+
+ private final Tracer tracer;
+
+ SpanInjectingRibbonRequestCustomizer(Tracer tracer) {
+ this.tracer = tracer;
+ }
@Override
- public void inject(Span span, Builder carrier) {
+ public void customize(T context) {
+ Span span = getCurrentSpan();
+ inject(span, context);
+ span.logEvent(Span.CLIENT_SEND);
+ if (log.isDebugEnabled()) {
+ log.debug("Span in the RibbonRequestCustomizer is" + span);
+ }
+ }
+
+ @Override
+ public void inject(Span span, T carrier) {
if (span == null) {
setHeader(carrier, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED);
return;
@@ -55,9 +81,9 @@ class RequestBuilderContextInjector implements SpanInjector {
? span.getParents().get(0) : null;
}
- public void setHeader(HttpRequest.Builder builder, String name, String value) {
- if (value != null) {
- builder.header(name, value);
- }
+ private Span getCurrentSpan() {
+ return this.tracer.getCurrentSpan();
}
+
+ abstract void setHeader(T builder, String name, String value);
}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java
index 2717d5b9a..12bbf8635 100644
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java
@@ -19,6 +19,11 @@ package org.springframework.cloud.sleuth.instrument.zuul;
import java.lang.invoke.MethodHandles;
import java.net.URI;
+import com.netflix.zuul.ExecutionStatus;
+import com.netflix.zuul.ZuulFilter;
+import com.netflix.zuul.ZuulFilterResult;
+import com.netflix.zuul.context.RequestContext;
+
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.sleuth.Span;
@@ -26,11 +31,6 @@ import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
-import com.netflix.zuul.ExecutionStatus;
-import com.netflix.zuul.ZuulFilter;
-import com.netflix.zuul.ZuulFilterResult;
-import com.netflix.zuul.context.RequestContext;
-
/**
* A pre request {@link ZuulFilter} that sets tracing related headers on the request
* from the current span. We're doing so to ensure tracing propagates to the next hop.
@@ -108,5 +108,4 @@ public class TracePreZuulFilter extends ZuulFilter {
public int filterOrder() {
return 0;
}
-
}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactory.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactory.java
deleted file mode 100644
index e8923de1f..000000000
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactory.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.zuul;
-
-import java.io.InputStream;
-import java.net.URISyntaxException;
-
-import com.netflix.client.http.HttpRequest;
-import com.netflix.niws.client.http.RestClient;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
-import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommand;
-import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommandFactory;
-import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
-import org.springframework.cloud.sleuth.Span;
-import org.springframework.cloud.sleuth.SpanInjector;
-import org.springframework.cloud.sleuth.Tracer;
-import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
-import org.springframework.util.MultiValueMap;
-
-/**
- * Propagates traces downstream via http headers that contain trace metadata.
- *
- * @author Spencer Gibb
- * @since 1.0.0
- */
-public class TraceRestClientRibbonCommandFactory extends RestClientRibbonCommandFactory {
-
- private static final Log log = LogFactory.getLog(TraceRestClientRibbonCommandFactory.class);
-
- private final Tracer tracer;
- private final SpanInjector spanInjector;
- private final HttpTraceKeysInjector httpTraceKeysInjector;
-
- public TraceRestClientRibbonCommandFactory(SpringClientFactory clientFactory,
- Tracer tracer, SpanInjector spanInjector,
- HttpTraceKeysInjector httpTraceKeysInjector) {
- super(clientFactory);
- this.tracer = tracer;
- this.spanInjector = spanInjector;
- this.httpTraceKeysInjector = httpTraceKeysInjector;
- }
-
- @Override
- @SuppressWarnings("deprecation")
- public RestClientRibbonCommand create(RibbonCommandContext context) {
- RestClient restClient = getClientFactory().getClient(context.getServiceId(),
- RestClient.class);
- try {
- return new TraceRestClientRibbonCommand(context.getServiceId(), restClient,
- getVerb(context.getVerb()), context.getUri(), context.getRetryable(),
- context.getHeaders(), context.getParams(), context.getRequestEntity(),
- this.tracer, this.spanInjector, this.httpTraceKeysInjector);
- }
- catch (URISyntaxException e) {
- log.error("Exception occurred while trying to create the TraceRestClientRibbonCommand", e);
- throw new RuntimeException(e);
- }
- }
-
- class TraceRestClientRibbonCommand extends RestClientRibbonCommand {
-
- private final Tracer tracer;
- private final SpanInjector spanInjector;
- private final HttpTraceKeysInjector httpTraceKeysInjector;
-
- @SuppressWarnings("deprecation")
- public TraceRestClientRibbonCommand(String commandKey, RestClient restClient,
- HttpRequest.Verb verb, String uri, Boolean retryable,
- MultiValueMap headers,
- MultiValueMap params, InputStream requestEntity,
- Tracer tracer, SpanInjector spanInjector,
- HttpTraceKeysInjector httpTraceKeysInjector)
- throws URISyntaxException {
- super(commandKey, restClient, verb, uri, retryable, headers, params,
- requestEntity);
- this.tracer = tracer;
- this.spanInjector = spanInjector;
- this.httpTraceKeysInjector = httpTraceKeysInjector;
- }
-
- @Override
- protected void customizeRequest(HttpRequest.Builder requestBuilder) {
- Span span = getCurrentSpan();
- this.spanInjector.inject(span, requestBuilder);
- this.httpTraceKeysInjector.addRequestTags(span, getUri(), getVerb().verb());
- span.logEvent(Span.CLIENT_SEND);
- if (log.isDebugEnabled()) {
- log.debug("Span in RibbonCommandFactory is" + span);
- }
- }
-
- private Span getCurrentSpan() {
- return this.tracer.getCurrentSpan();
- }
-
- }
-}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java
new file mode 100644
index 000000000..f64e121ae
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java
@@ -0,0 +1,53 @@
+/*
+ * 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.zuul;
+
+import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand;
+import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
+import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
+
+/**
+ * Propagates traces downstream via http headers that contain trace metadata.
+ *
+ * @author Spencer Gibb
+ * @author Marcin Grzejszczak
+ * @since 1.1.0
+ */
+class TraceRibbonCommandFactory implements RibbonCommandFactory {
+
+ private final RibbonCommandFactory delegate;
+ private final Tracer tracer;
+ private final HttpTraceKeysInjector httpTraceKeysInjector;
+
+ public TraceRibbonCommandFactory(RibbonCommandFactory delegate,
+ Tracer tracer, HttpTraceKeysInjector httpTraceKeysInjector) {
+ this.delegate = delegate;
+ this.tracer = tracer;
+ this.httpTraceKeysInjector = httpTraceKeysInjector;
+ }
+
+ @Override
+ public RibbonCommand create(RibbonCommandContext context) {
+ RibbonCommand ribbonCommand = this.delegate.create(context);
+ Span span = this.tracer.getCurrentSpan();
+ this.httpTraceKeysInjector.addRequestTags(span, context.uri(), context.getMethod());
+ return ribbonCommand;
+ }
+}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java
new file mode 100644
index 000000000..a506059d5
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java
@@ -0,0 +1,72 @@
+/*
+ * 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.zuul;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
+
+/**
+ * Post processor that wraps a {@link org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory}
+ * in its trace representation.
+ *
+ * @author Marcin Grzejszczak
+ *
+ * @since 1.1.0
+ */
+final class TraceRibbonCommandFactoryBeanPostProcessor implements BeanPostProcessor {
+
+ private final BeanFactory beanFactory;
+ private Tracer tracer;
+ private HttpTraceKeysInjector httpTraceKeysInjector;
+
+ TraceRibbonCommandFactoryBeanPostProcessor(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName)
+ throws BeansException {
+ if (bean instanceof RibbonCommandFactory) {
+ return new TraceRibbonCommandFactory((RibbonCommandFactory) bean, getTracer(), getHttpTraceKeysInjector());
+ }
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName)
+ throws BeansException {
+ return bean;
+ }
+
+ Tracer getTracer() {
+ if (this.tracer == null) {
+ this.tracer = this.beanFactory.getBean(Tracer.class);
+ }
+ return this.tracer;
+ }
+
+ HttpTraceKeysInjector getHttpTraceKeysInjector() {
+ if (this.httpTraceKeysInjector == null) {
+ this.httpTraceKeysInjector = this.beanFactory.getBean(HttpTraceKeysInjector.class);
+ }
+ return this.httpTraceKeysInjector;
+ }
+}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java
index 54101331e..52556f521 100644
--- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java
@@ -15,13 +15,19 @@
*/
package org.springframework.cloud.sleuth.instrument.zuul;
+import com.netflix.client.http.HttpRequest;
+import com.netflix.zuul.ZuulFilter;
+import com.netflix.zuul.context.RequestContext;
+
+import org.apache.http.client.methods.RequestBuilder;
+import org.springframework.beans.factory.BeanFactory;
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.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
-import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
+import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer;
import org.springframework.cloud.sleuth.SpanInjector;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
@@ -30,9 +36,7 @@ import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import com.netflix.client.http.HttpRequest;
-import com.netflix.zuul.ZuulFilter;
-import com.netflix.zuul.context.RequestContext;
+import okhttp3.Request;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration}
@@ -56,13 +60,6 @@ public class TraceZuulAutoConfiguration {
return new TracePreZuulFilter(tracer, spanInjector, httpTraceKeysInjector);
}
- @Bean
- public TraceRestClientRibbonCommandFactory traceRestClientRibbonCommandFactory(SpringClientFactory factory,
- Tracer tracer, SpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) {
- return new TraceRestClientRibbonCommandFactory(factory, tracer, spanInjector,
- httpTraceKeysInjector);
- }
-
@Bean
@ConditionalOnMissingBean
public TracePostZuulFilter tracePostZuulFilter(Tracer tracer, TraceKeys traceKeys) {
@@ -75,8 +72,26 @@ public class TraceZuulAutoConfiguration {
}
@Bean
- public SpanInjector requestBuilderContextSpanInjector() {
- return new RequestBuilderContextInjector();
+ public TraceRibbonCommandFactoryBeanPostProcessor traceRibbonCommandFactoryBeanPostProcessor(BeanFactory beanFactory) {
+ return new TraceRibbonCommandFactoryBeanPostProcessor(beanFactory);
+ }
+
+ @Bean
+ @ConditionalOnClass(name = "com.netflix.client.http.HttpRequest.Builder")
+ public RibbonRequestCustomizer restClientRibbonRequestCustomizer(Tracer tracer) {
+ return new RestClientRibbonRequestCustomizer(tracer);
+ }
+
+ @Bean
+ @ConditionalOnClass(name = "org.apache.http.client.methods.RequestBuilder")
+ public RibbonRequestCustomizer apacheHttpRibbonRequestCustomizer(Tracer tracer) {
+ return new ApacheHttpClientRibbonRequestCustomizer(tracer);
+ }
+
+ @Bean
+ @ConditionalOnClass(name = "okhttp3.Request.Builder")
+ public RibbonRequestCustomizer okHttpRibbonRequestCustomizer(Tracer tracer) {
+ return new OkHttpClientRibbonRequestCustomizer(tracer);
}
}
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java
new file mode 100644
index 000000000..34e6a613e
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java
@@ -0,0 +1,79 @@
+/*
+ * 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.zuul;
+
+import org.apache.http.Header;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.Tracer;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+/**
+ * @author Marcin Grzejszczak
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ApacheHttpClientRibbonRequestCustomizerTests {
+
+ @Mock Tracer tracer;
+ @InjectMocks ApacheHttpClientRibbonRequestCustomizer customizer;
+ Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L)
+ .processId("processId").build();
+
+ @Test
+ public void should_accept_customizer_when_apache_http_client_is_passed() throws Exception {
+ then(this.customizer.accepts(String.class)).isFalse();
+ then(this.customizer.accepts(RequestBuilder.class)).isTrue();
+ }
+
+ @Test
+ public void should_set_not_sampled_on_the_context_when_there_is_no_span() throws Exception {
+ RequestBuilder requestBuilder = RequestBuilder.create("GET");
+
+ this.customizer.inject(null, requestBuilder);
+
+ HttpUriRequest request = requestBuilder.build();
+ Header header = request.getFirstHeader(Span.SAMPLED_NAME);
+ then(header.getName()).isEqualTo(Span.SAMPLED_NAME);
+ then(header.getValue()).isEqualTo(Span.SPAN_NOT_SAMPLED);
+ }
+
+ @Test
+ public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception {
+ RequestBuilder requestBuilder = RequestBuilder.create("GET");
+
+ this.customizer.inject(this.span, requestBuilder);
+
+ HttpUriRequest request = requestBuilder.build();
+ thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "1");
+ thenThereIsAHeaderWithNameAndValue(request, Span.TRACE_ID_NAME, "2");
+ thenThereIsAHeaderWithNameAndValue(request, Span.PARENT_ID_NAME, "3");
+ thenThereIsAHeaderWithNameAndValue(request, Span.PROCESS_ID_NAME, "processId");
+ }
+
+ private void thenThereIsAHeaderWithNameAndValue(HttpUriRequest request, String name, String value) {
+ Header header = request.getFirstHeader(name);
+ then(header.getName()).isEqualTo(name);
+ then(header.getValue()).isEqualTo(value);
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java
new file mode 100644
index 000000000..f3e1c0d8c
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java
@@ -0,0 +1,78 @@
+/*
+ * 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.zuul;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.Tracer;
+
+import okhttp3.Request;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+/**
+ * @author Marcin Grzejszczak
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class OkHttpClientRibbonRequestCustomizerTests {
+
+ @Mock Tracer tracer;
+ @InjectMocks OkHttpClientRibbonRequestCustomizer customizer;
+ Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L)
+ .processId("processId").build();
+
+ @Test
+ public void should_accept_customizer_when_apache_http_client_is_passed() throws Exception {
+ then(this.customizer.accepts(String.class)).isFalse();
+ then(this.customizer.accepts(Request.Builder.class)).isTrue();
+ }
+
+ @Test
+ public void should_set_not_sampled_on_the_context_when_there_is_no_span() throws Exception {
+ Request.Builder requestBuilder = requestBuilder();
+
+ this.customizer.inject(null, requestBuilder);
+
+ Request request = requestBuilder.build();
+ then(request.header(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED);
+ }
+
+ @Test
+ public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception {
+ Request.Builder requestBuilder = requestBuilder();
+
+ this.customizer.inject(this.span, requestBuilder);
+
+ Request request = requestBuilder.build();
+ thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "1");
+ thenThereIsAHeaderWithNameAndValue(request, Span.TRACE_ID_NAME, "2");
+ thenThereIsAHeaderWithNameAndValue(request, Span.PARENT_ID_NAME, "3");
+ thenThereIsAHeaderWithNameAndValue(request, Span.PROCESS_ID_NAME, "processId");
+ }
+
+ private void thenThereIsAHeaderWithNameAndValue(Request request, String name, String value) {
+ then(request.header(name)).isEqualTo(value);
+ }
+
+ private Request.Builder requestBuilder() {
+ return new Request.Builder().get().url("http://localhost:8080/");
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java
new file mode 100644
index 000000000..4fc895e21
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java
@@ -0,0 +1,78 @@
+/*
+ * 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.zuul;
+
+import com.netflix.client.http.HttpRequest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.Tracer;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+/**
+ * @author Marcin Grzejszczak
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class RestClientRibbonRequestCustomizerTests {
+
+ @Mock Tracer tracer;
+ @InjectMocks RestClientRibbonRequestCustomizer customizer;
+ Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L)
+ .processId("processId").build();
+
+ @Test
+ public void should_accept_customizer_when_apache_http_client_is_passed() throws Exception {
+ then(this.customizer.accepts(String.class)).isFalse();
+ then(this.customizer.accepts(HttpRequest.Builder.class)).isTrue();
+ }
+
+ @Test
+ public void should_set_not_sampled_on_the_context_when_there_is_no_span() throws Exception {
+ HttpRequest.Builder requestBuilder = requestBuilder();
+
+ this.customizer.inject(null, requestBuilder);
+
+ HttpRequest request = requestBuilder.build();
+ then(request.getHttpHeaders().getFirstValue(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED);
+ }
+
+ @Test
+ public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception {
+ HttpRequest.Builder requestBuilder = requestBuilder();
+
+ this.customizer.inject(this.span, requestBuilder);
+
+ HttpRequest request = requestBuilder.build();
+ thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "1");
+ thenThereIsAHeaderWithNameAndValue(request, Span.TRACE_ID_NAME, "2");
+ thenThereIsAHeaderWithNameAndValue(request, Span.PARENT_ID_NAME, "3");
+ thenThereIsAHeaderWithNameAndValue(request, Span.PROCESS_ID_NAME, "processId");
+ }
+
+ private void thenThereIsAHeaderWithNameAndValue(HttpRequest request, String name, String value) {
+ then(request.getHttpHeaders().getFirstValue(name)).isEqualTo(value);
+ }
+
+ private HttpRequest.Builder requestBuilder() {
+ return new HttpRequest.Builder().verb(HttpRequest.Verb.GET).uri("http://localhost:8080/");
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java
new file mode 100644
index 000000000..2e7ef1a4e
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java
@@ -0,0 +1,48 @@
+/*
+ * 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.zuul;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+/**
+ * @author Marcin Grzejszczak
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class TraceRibbonCommandFactoryBeanPostProcessorTests {
+
+ @Mock RibbonCommandFactory ribbonCommandFactory;
+ @Mock BeanFactory beanFactory;
+ @InjectMocks TraceRibbonCommandFactoryBeanPostProcessor postProcessor;
+
+ @Test
+ public void should_return_a_bean_as_it_is_if_its_not_a_ribbon_command_Factory() {
+ then(this.postProcessor.postProcessBeforeInitialization("", "name")).isEqualTo("");
+ }
+
+ @Test
+ public void should_wrap_ribbon_command_factory_in_a_trace_representation() {
+ then(this.postProcessor.postProcessBeforeInitialization(ribbonCommandFactory, "name")).isInstanceOf(TraceRibbonCommandFactory.class);
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java
similarity index 52%
rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java
rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java
index 955172e8f..38da7df84 100644
--- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRestClientRibbonCommandFactoryTest.java
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java
@@ -16,7 +16,6 @@
package org.springframework.cloud.sleuth.instrument.zuul;
-import com.netflix.client.http.HttpRequest;
import com.netflix.niws.client.http.RestClient;
import com.netflix.zuul.context.RequestContext;
@@ -27,43 +26,44 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
-import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommand;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
+import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
import org.springframework.cloud.sleuth.Span;
-import org.springframework.cloud.sleuth.SpanInjector;
+import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector;
import org.springframework.cloud.sleuth.trace.TestSpanContextHolder;
import org.springframework.http.HttpHeaders;
import org.springframework.util.LinkedMultiValueMap;
-import static org.assertj.core.api.BDDAssertions.then;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
+import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then;
/**
* @author Marcin Grzejszczak
*/
@RunWith(MockitoJUnitRunner.class)
-public class TraceRestClientRibbonCommandFactoryTest {
+public class TraceRibbonCommandFactoryTest {
@Mock Tracer tracer;
@Mock SpringClientFactory springClientFactory;
- SpanInjector spanInjector = new RequestBuilderContextInjector();
- @Mock HttpTraceKeysInjector httpTraceKeysInjector;
- TraceRestClientRibbonCommandFactory traceRestClientRibbonCommandFactory;
+ HttpTraceKeysInjector httpTraceKeysInjector;
+ @Mock RibbonCommandFactory ribbonCommandFactory;
+ TraceRibbonCommandFactory traceRibbonCommandFactory;
+ Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L)
+ .processId("processId").build();
@Before
@SuppressWarnings({ "deprecation", "unchecked" })
public void setup() {
- this.traceRestClientRibbonCommandFactory = new TraceRestClientRibbonCommandFactory(
- this.springClientFactory, this.tracer, this.spanInjector,
+ this.httpTraceKeysInjector = new HttpTraceKeysInjector(this.tracer, new TraceKeys());
+ this.traceRibbonCommandFactory = new TraceRibbonCommandFactory(
+ this.ribbonCommandFactory, this.tracer,
httpTraceKeysInjector);
given(this.springClientFactory.getClient(anyString(), any(Class.class)))
.willReturn(new RestClient());
- Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L)
- .processId("processId").build();
given(this.tracer.getCurrentSpan()).willReturn(span);
given(this.tracer.isTracing()).willReturn(true);
}
@@ -75,34 +75,11 @@ public class TraceRestClientRibbonCommandFactoryTest {
}
@Test
- public void should_wrap_ribbon_command_in_a_sleuth_representation() throws Exception {
- RestClientRibbonCommand restClientRibbonCommand = this.traceRestClientRibbonCommandFactory
- .create(ribbonCommandContext());
+ public void should_attach_trace_headers_to_the_span() throws Exception {
+ this.traceRibbonCommandFactory.create(ribbonCommandContext());
- then(restClientRibbonCommand).isInstanceOf(
- TraceRestClientRibbonCommandFactory.TraceRestClientRibbonCommand.class);
- }
-
- @Test
- public void should_attach_trace_headers_to_the_sent_request() throws Exception {
- RestClientRibbonCommand restClientRibbonCommand = this.traceRestClientRibbonCommandFactory
- .create(ribbonCommandContext());
- TraceRestClientRibbonCommandFactory.TraceRestClientRibbonCommand traceRestClientRibbonCommand = (TraceRestClientRibbonCommandFactory.TraceRestClientRibbonCommand) restClientRibbonCommand;
- HttpRequest.Builder builder = new HttpRequest.Builder();
-
- traceRestClientRibbonCommand.customizeRequest(builder);
-
- HttpRequest httpRequest = builder.build();
- then(httpRequest.getHttpHeaders().getFirstValue(Span.SPAN_ID_NAME))
- .isEqualTo("1");
- then(httpRequest.getHttpHeaders().getFirstValue(Span.TRACE_ID_NAME))
- .isEqualTo("2");
- then(httpRequest.getHttpHeaders().getFirstValue(Span.SPAN_NAME_NAME))
- .isEqualTo("name");
- then(httpRequest.getHttpHeaders().getFirstValue(Span.PARENT_ID_NAME))
- .isEqualTo("3");
- then(httpRequest.getHttpHeaders().getFirstValue(Span.PROCESS_ID_NAME))
- .isEqualTo("processId");
+ then(this.span).hasATag("http.method", "GET");
+ then(this.span).hasATag("http.url", "http://localhost:1234/foo");
}
private RibbonCommandContext ribbonCommandContext() {