Not using loadbalancer zipkin URI extractor when zipkin url contains a port

fixes gh-1474
This commit is contained in:
Marcin Grzejszczak
2019-10-23 13:41:24 +02:00
parent 54a65bc9f3
commit 759ac87481
7 changed files with 254 additions and 442 deletions

View File

@@ -1,4 +1,8 @@
// Do not edit this file (e.g. go instead to src/main/asciidoc)
////
DO NOT EDIT THIS FILE. IT WAS GENERATED.
Manual changes to this file will be lost when it is generated again.
Edit the files in the src/main/asciidoc/ directory instead.
////
:jdkversion: 1.8
:github-tag: master
@@ -236,83 +240,7 @@ Consider the following example of a Logback configuration file (named https://gi
[source,xml]
-----
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
<!-- Example for logging into the build folder of your project -->
<property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/>
<!-- You can override this to have a custom pattern -->
<property name="CONSOLE_LOG_PATTERN"
value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<!-- Appender to log to console -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- Minimum logging level to be presented in the console logs-->
<level>DEBUG</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- Appender to log to file -->
<appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- Appender to log to file in a JSON format -->
<appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}.json</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC</timeZone>
</timestamp>
<pattern>
<pattern>
{
"severity": "%level",
"service": "${springAppName:-}",
"trace": "%X{X-B3-TraceId:-}",
"span": "%X{X-B3-SpanId:-}",
"parent": "%X{X-B3-ParentSpanId:-}",
"exportable": "%X{X-Span-Export:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger{40}",
"rest": "%message"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
<!-- uncomment this to have also JSON logs -->
<!--<appender-ref ref="logstash"/>-->
<!--<appender-ref ref="flatfile"/>-->
</root>
</configuration>
-----
Unresolved directive in intro.adoc - include::https://raw.githubusercontent.com/spring-cloud-samples/sleuth-documentation-apps/master/service1/src/main/resources/logback-spring.xml[]
That Logback configuration file:
@@ -341,9 +269,7 @@ The following example shows setting baggage on a span:
[source,java]
----
Span initialSpan = this.tracer.nextSpan().name("span").start();
ExtraFieldPropagation.set(initialSpan.context(), "foo", "bar");
ExtraFieldPropagation.set(initialSpan.context(), "UPPER_CASE", "someValue");
Unresolved directive in intro.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/multiple/MultipleHopsIntegrationTests.java[tags=baggage,indent=0]
----
===== Baggage versus Span Tags
@@ -366,22 +292,12 @@ The following listing shows integration tests that use baggage:
.The setup
[source,yml]
----
spring.sleuth:
baggage-keys:
- baz
- bizarrecase
propagation-keys:
- foo
- upper_case
----
Unresolved directive in intro.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-core/src/test/resources/application-baggage.yml[indent=0]
.The code
[source,java]
----
initialSpan.tag("foo",
ExtraFieldPropagation.get(initialSpan.context(), "foo"));
initialSpan.tag("UPPER_CASE",
ExtraFieldPropagation.get(initialSpan.context(), "UPPER_CASE"));
Unresolved directive in intro.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/multiple/MultipleHopsIntegrationTests.java[tags=baggage_tag,indent=0]
----
[[sleuth-adding-project]]
@@ -680,95 +596,7 @@ If you do not use SLF4J, this pattern is NOT automatically applied.
== Building
:jdkversion: 1.7
=== Basic Compile and Test
To build the source you will need to install JDK {jdkversion}.
Spring Cloud uses Maven for most build-related activities, and you
should be able to get off the ground quite quickly by cloning the
project you are interested in and typing
----
$ ./mvnw install
----
NOTE: You can also install Maven (>=3.3.3) yourself and run the `mvn` command
in place of `./mvnw` in the examples below. If you do that you also
might need to add `-P spring` if your local Maven settings do not
contain repository declarations for spring pre-release artifacts.
NOTE: Be aware that you might need to increase the amount of memory
available to Maven by setting a `MAVEN_OPTS` environment variable with
a value like `-Xmx512m -XX:MaxPermSize=128m`. We try to cover this in
the `.mvn` configuration, so if you find you have to do it to make a
build succeed, please raise a ticket to get the settings added to
source control.
For hints on how to build the project look in `.travis.yml` if there
is one. There should be a "script" and maybe "install" command. Also
look at the "services" section to see if any services need to be
running locally (e.g. mongo or rabbit). Ignore the git-related bits
that you might find in "before_install" since they're related to setting git
credentials and you already have those.
The projects that require middleware generally include a
`docker-compose.yml`, so consider using
https://compose.docker.io/[Docker Compose] to run the middeware servers
in Docker containers. See the README in the
https://github.com/spring-cloud-samples/scripts[scripts demo
repository] for specific instructions about the common cases of mongo,
rabbit and redis.
NOTE: If all else fails, build with the command from `.travis.yml` (usually
`./mvnw install`).
=== Documentation
The spring-cloud-build module has a "docs" profile, and if you switch
that on it will try to build asciidoc sources from
`src/main/asciidoc`. As part of that process it will look for a
`README.adoc` and process it by loading all the includes, but not
parsing or rendering it, just copying it to `${main.basedir}`
(defaults to `${basedir}`, i.e. the root of the project). If there are
any changes in the README it will then show up after a Maven build as
a modified file in the correct place. Just commit it and push the change.
=== Working with the code
If you don't have an IDE preference we would recommend that you use
https://www.springsource.com/developer/sts[Spring Tools Suite] or
https://eclipse.org[Eclipse] when working with the code. We use the
https://eclipse.org/m2e/[m2eclipse] eclipse plugin for maven support. Other IDEs and tools
should also work without issue as long as they use Maven 3.3.3 or better.
==== Importing into eclipse with m2eclipse
We recommend the https://eclipse.org/m2e/[m2eclipse] eclipse plugin when working with
eclipse. If you don't already have m2eclipse installed it is available from the "eclipse
marketplace".
NOTE: Older versions of m2e do not support Maven 3.3, so once the
projects are imported into Eclipse you will also need to tell
m2eclipse to use the right profile for the projects. If you
see many different errors related to the POMs in the projects, check
that you have an up to date installation. If you can't upgrade m2e,
add the "spring" profile to your `settings.xml`. Alternatively you can
copy the repository settings from the "spring" profile of the parent
pom into your `settings.xml`.
==== Importing into eclipse without m2eclipse
If you prefer not to use m2eclipse you can generate eclipse project metadata using the
following command:
[indent=0]
----
$ ./mvnw eclipse:eclipse
----
The generated eclipse projects can be imported by selecting `import existing projects`
from the `file` menu.
Unresolved directive in README.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/building.adoc[]
IMPORTANT: Spring Cloud Sleuth uses two different versions of language level. Java 1.7 is used for main sources, and
Java 1.8 is used for tests. When importing your project to an IDE, you should activate the `ide` Maven profile to turn on
Java 1.8 for both main and test sources. You MUST NOT use Java 1.8 features in the main sources. If you do
@@ -776,181 +604,4 @@ so, your app breaks during the Maven build.
== Contributing
:spring-cloud-build-branch: master
Spring Cloud is released under the non-restrictive Apache 2.0 license,
and follows a very standard Github development process, using Github
tracker for issues and merging pull requests into master. If you want
to contribute even something trivial please do not hesitate, but
follow the guidelines below.
=== Sign the Contributor License Agreement
Before we accept a non-trivial patch or pull request we will need you to sign the
https://cla.pivotal.io/sign/spring[Contributor License Agreement].
Signing the contributor's agreement does not grant anyone commit rights to the main
repository, but it does mean that we can accept your contributions, and you will get an
author credit if we do. Active contributors might be asked to join the core team, and
given the ability to merge pull requests.
=== Code of Conduct
This project adheres to the Contributor Covenant https://github.com/spring-cloud/spring-cloud-build/blob/master/docs/src/main/asciidoc/code-of-conduct.adoc[code of
conduct]. By participating, you are expected to uphold this code. Please report
unacceptable behavior to spring-code-of-conduct@pivotal.io.
=== Code Conventions and Housekeeping
None of these is essential for a pull request, but they will all help. They can also be
added after the original pull request but before a merge.
* Use the Spring Framework code format conventions. If you use Eclipse
you can import formatter settings using the
`eclipse-code-formatter.xml` file from the
https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-dependencies-parent/eclipse-code-formatter.xml[Spring
Cloud Build] project. If using IntelliJ, you can use the
https://plugins.jetbrains.com/plugin/6546[Eclipse Code Formatter
Plugin] to import the same file.
* Make sure all new `.java` files to have a simple Javadoc class comment with at least an
`@author` tag identifying you, and preferably at least a paragraph on what the class is
for.
* Add the ASF license header comment to all new `.java` files (copy from existing files
in the project)
* Add yourself as an `@author` to the .java files that you modify substantially (more
than cosmetic changes).
* Add some Javadocs and, if you change the namespace, some XSD doc elements.
* A few unit tests would help a lot as well -- someone has to do it.
* If no-one else is using your branch, please rebase it against the current master (or
other target branch in the main project).
* When writing a commit message please follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions],
if you are fixing an existing issue please add `Fixes gh-XXXX` at the end of the commit
message (where XXXX is the issue number).
=== Checkstyle
Spring Cloud Build comes with a set of checkstyle rules. You can find them in the `spring-cloud-build-tools` module. The most notable files under the module are:
.spring-cloud-build-tools/
----
└── src
   ├── checkstyle
   │   └── checkstyle-suppressions.xml <3>
   └── main
   └── resources
   ├── checkstyle-header.txt <2>
   └── checkstyle.xml <1>
----
<1> Default Checkstyle rules
<2> File header setup
<3> Default suppression rules
==== Checkstyle configuration
Checkstyle rules are *disabled by default*. To add checkstyle to your project just define the following properties and plugins.
.pom.xml
----
<properties>
<maven-checkstyle-plugin.failsOnError>true</maven-checkstyle-plugin.failsOnError> <1>
<maven-checkstyle-plugin.failsOnViolation>true
</maven-checkstyle-plugin.failsOnViolation> <2>
<maven-checkstyle-plugin.includeTestSourceDirectory>true
</maven-checkstyle-plugin.includeTestSourceDirectory> <3>
</properties>
<build>
<plugins>
<plugin> <4>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
</plugin>
<plugin> <5>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
</plugins>
<reporting>
<plugins>
<plugin> <5>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
</plugins>
</reporting>
</build>
----
<1> Fails the build upon Checkstyle errors
<2> Fails the build upon Checkstyle violations
<3> Checkstyle analyzes also the test sources
<4> Add the Spring Java Format plugin that will reformat your code to pass most of the Checkstyle formatting rules
<5> Add checkstyle plugin to your build and reporting phases
If you need to suppress some rules (e.g. line length needs to be longer), then it's enough for you to define a file under `${project.root}/src/checkstyle/checkstyle-suppressions.xml` with your suppressions. Example:
.projectRoot/src/checkstyle/checkstyle-suppresions.xml
----
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
"-//Puppy Crawl//DTD Suppressions 1.1//EN"
"https://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
<suppressions>
<suppress files=".*ConfigServerApplication\.java" checks="HideUtilityClassConstructor"/>
<suppress files=".*ConfigClientWatch\.java" checks="LineLengthCheck"/>
</suppressions>
----
It's advisable to copy the `${spring-cloud-build.rootFolder}/.editorconfig` and `${spring-cloud-build.rootFolder}/.springformat` to your project. That way, some default formatting rules will be applied. You can do so by running this script:
```bash
$ curl https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/.editorconfig -o .editorconfig
$ touch .springformat
```
=== IDE setup
==== Intellij IDEA
In order to setup Intellij you should import our coding conventions, inspection profiles and set up the checkstyle plugin.
.spring-cloud-build-tools/
----
└── src
   ├── checkstyle
   │   └── checkstyle-suppressions.xml <3>
   └── main
   └── resources
   ├── checkstyle-header.txt <2>
   ├── checkstyle.xml <1>
   └── intellij
      ├── Intellij_Project_Defaults.xml <4>
      └── Intellij_Spring_Boot_Java_Conventions.xml <5>
----
<1> Default Checkstyle rules
<2> File header setup
<3> Default suppression rules
<4> Project defaults for Intellij that apply most of Checkstyle rules
<5> Project style conventions for Intellij that apply most of Checkstyle rules
.Code style
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/{spring-cloud-build-branch}/docs/src/main/asciidoc/images/intellij-code-style.png[Code style]
Go to `File` -> `Settings` -> `Editor` -> `Code style`. There click on the icon next to the `Scheme` section. There, click on the `Import Scheme` value and pick the `Intellij IDEA code style XML` option. Import the `spring-cloud-build-tools/src/main/resources/intellij/Intellij_Spring_Boot_Java_Conventions.xml` file.
.Inspection profiles
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/{spring-cloud-build-branch}/docs/src/main/asciidoc/images/intellij-inspections.png[Code style]
Go to `File` -> `Settings` -> `Editor` -> `Inspections`. There click on the icon next to the `Profile` section. There, click on the `Import Profile` and import the `spring-cloud-build-tools/src/main/resources/intellij/Intellij_Project_Defaults.xml` file.
.Checkstyle
To have Intellij work with Checkstyle, you have to install the `Checkstyle` plugin. It's advisable to also install the `Assertions2Assertj` to automatically convert the JUnit assertions
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/{spring-cloud-build-branch}/docs/src/main/asciidoc/images/intellij-checkstyle.png[Checkstyle]
Go to `File` -> `Settings` -> `Other settings` -> `Checkstyle`. There click on the `+` icon in the `Configuration file` section. There, you'll have to define where the checkstyle rules should be picked from. In the image above, we've picked the rules from the cloned Spring Cloud Build repository. However, you can point to the Spring Cloud Build's GitHub repository (e.g. for the `checkstyle.xml` : `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-build-tools/src/main/resources/checkstyle.xml`). We need to provide the following variables:
- `checkstyle.header.file` - please point it to the Spring Cloud Build's, `spring-cloud-build-tools/src/main/resources/checkstyle/checkstyle-header.txt` file either in your cloned repo or via the `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-build-tools/src/main/resources/checkstyle-header.txt` URL.
- `checkstyle.suppressions.file` - default suppressions. Please point it to the Spring Cloud Build's, `spring-cloud-build-tools/src/checkstyle/checkstyle-suppressions.xml` file either in your cloned repo or via the `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-build-tools/src/checkstyle/checkstyle-suppressions.xml` URL.
- `checkstyle.additional.suppressions.file` - this variable corresponds to suppressions in your local project. E.g. you're working on `spring-cloud-contract`. Then point to the `project-root/src/checkstyle/checkstyle-suppressions.xml` folder. Example for `spring-cloud-contract` would be: `/home/username/spring-cloud-contract/src/checkstyle/checkstyle-suppressions.xml`.
IMPORTANT: Remember to set the `Scan Scope` to `All sources` since we apply checkstyle rules for production and test sources.
Unresolved directive in README.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/contributing.adoc[]

View File

@@ -63,12 +63,14 @@ class ExecutorBeanPostProcessor implements BeanPostProcessor {
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
boolean alreadyTraced = alreadyTraced(bean);
if (bean instanceof ThreadPoolTaskExecutor && !alreadyTraced) {
if (isProxyNeeded(beanName)) {
@@ -102,7 +104,8 @@ class ExecutorBeanPostProcessor implements BeanPostProcessor {
private boolean alreadyTraced(Object bean) {
return bean instanceof LazyTraceThreadPoolTaskExecutor
|| bean instanceof TraceableExecutorService || bean instanceof LazyTraceAsyncTaskExecutor
|| bean instanceof TraceableExecutorService
|| bean instanceof LazyTraceAsyncTaskExecutor
|| bean instanceof LazyTraceExecutor;
}
@@ -112,14 +115,18 @@ class ExecutorBeanPostProcessor implements BeanPostProcessor {
boolean classFinal = Modifier.isFinal(bean.getClass().getModifiers());
boolean cglibProxy = !methodFinal && !classFinal;
try {
return createProxy(bean, cglibProxy, new ExecutorMethodInterceptor<>(executor, this.beanFactory));
return createProxy(bean, cglibProxy,
new ExecutorMethodInterceptor<>(executor, this.beanFactory));
}
catch (AopConfigException ex) {
if (cglibProxy) {
if (log.isDebugEnabled()) {
log.debug("Exception occurred while trying to create a proxy, falling back to JDK proxy", ex);
log.debug(
"Exception occurred while trying to create a proxy, falling back to JDK proxy",
ex);
}
return createProxy(bean, false, new ExecutorMethodInterceptor<>(executor, this.beanFactory));
return createProxy(bean, false,
new ExecutorMethodInterceptor<>(executor, this.beanFactory));
}
throw ex;
}
@@ -154,7 +161,8 @@ class ExecutorBeanPostProcessor implements BeanPostProcessor {
return !sleuthAsyncProperties.getIgnoredBeans().contains(beanName);
}
Object createThreadPoolTaskExecutorProxy(Object bean, boolean cglibProxy, ThreadPoolTaskExecutor executor) {
Object createThreadPoolTaskExecutorProxy(Object bean, boolean cglibProxy,
ThreadPoolTaskExecutor executor) {
if (!cglibProxy) {
return new LazyTraceThreadPoolTaskExecutor(this.beanFactory, executor);
}
@@ -162,37 +170,45 @@ class ExecutorBeanPostProcessor implements BeanPostProcessor {
() -> new LazyTraceThreadPoolTaskExecutor(this.beanFactory, executor));
}
Supplier<Executor> createThreadPoolTaskSchedulerProxy(ThreadPoolTaskScheduler executor) {
Supplier<Executor> createThreadPoolTaskSchedulerProxy(
ThreadPoolTaskScheduler executor) {
return () -> new LazyTraceThreadPoolTaskScheduler(this.beanFactory, executor);
}
Supplier<Executor> createScheduledThreadPoolExecutorProxy(ScheduledThreadPoolExecutor executor) {
return () -> new LazyTraceScheduledThreadPoolExecutor(executor.getCorePoolSize(), executor.getThreadFactory(),
executor.getRejectedExecutionHandler(), this.beanFactory, executor);
Supplier<Executor> createScheduledThreadPoolExecutorProxy(
ScheduledThreadPoolExecutor executor) {
return () -> new LazyTraceScheduledThreadPoolExecutor(executor.getCorePoolSize(),
executor.getThreadFactory(), executor.getRejectedExecutionHandler(),
this.beanFactory, executor);
}
Object createExecutorServiceProxy(Object bean, boolean cglibProxy, ExecutorService executor) {
Object createExecutorServiceProxy(Object bean, boolean cglibProxy,
ExecutorService executor) {
return getProxiedObject(bean, cglibProxy, executor,
() -> new TraceableExecutorService(this.beanFactory, executor));
}
Object createAsyncTaskExecutorProxy(Object bean, boolean cglibProxy, AsyncTaskExecutor executor) {
Object createAsyncTaskExecutorProxy(Object bean, boolean cglibProxy,
AsyncTaskExecutor executor) {
return getProxiedObject(bean, cglibProxy, executor, () -> {
if (bean instanceof ThreadPoolTaskScheduler) {
return new LazyTraceThreadPoolTaskScheduler(this.beanFactory, (ThreadPoolTaskScheduler) executor);
return new LazyTraceThreadPoolTaskScheduler(this.beanFactory,
(ThreadPoolTaskScheduler) executor);
}
return new LazyTraceAsyncTaskExecutor(this.beanFactory, executor);
});
}
private Object getProxiedObject(Object bean, boolean cglibProxy, Executor executor, Supplier<Executor> supplier) {
private Object getProxiedObject(Object bean, boolean cglibProxy, Executor executor,
Supplier<Executor> supplier) {
ProxyFactoryBean factory = proxyFactoryBean(bean, cglibProxy, executor, supplier);
try {
return getObject(factory);
}
catch (Exception ex) {
if (log.isDebugEnabled()) {
log.debug("Exception occurred while trying to get a proxy. Will fallback to a different implementation",
log.debug(
"Exception occurred while trying to get a proxy. Will fallback to a different implementation",
ex);
}
try {
@@ -201,35 +217,40 @@ class ExecutorBeanPostProcessor implements BeanPostProcessor {
log.debug(
"Will wrap ThreadPoolTaskScheduler in its tracing representation due to previous errors");
}
return createThreadPoolTaskSchedulerProxy((ThreadPoolTaskScheduler) bean).get();
return createThreadPoolTaskSchedulerProxy(
(ThreadPoolTaskScheduler) bean).get();
}
else if (bean instanceof ScheduledThreadPoolExecutor) {
if (log.isDebugEnabled()) {
log.debug(
"Will wrap ScheduledThreadPoolExecutor in its tracing representation due to previous errors");
}
return createScheduledThreadPoolExecutorProxy((ScheduledThreadPoolExecutor) bean).get();
return createScheduledThreadPoolExecutorProxy(
(ScheduledThreadPoolExecutor) bean).get();
}
}
catch (Exception ex2) {
if (log.isDebugEnabled()) {
log.debug("Fallback for special wrappers failed, will try the tracing representation instead", ex2);
log.debug(
"Fallback for special wrappers failed, will try the tracing representation instead",
ex2);
}
}
return supplier.get();
}
}
private ProxyFactoryBean proxyFactoryBean(Object bean, boolean cglibProxy, Executor executor,
Supplier<Executor> supplier) {
private ProxyFactoryBean proxyFactoryBean(Object bean, boolean cglibProxy,
Executor executor, Supplier<Executor> supplier) {
ProxyFactoryBean factory = new ProxyFactoryBean();
factory.setProxyTargetClass(cglibProxy);
factory.addAdvice(new ExecutorMethodInterceptor<Executor>(executor, this.beanFactory) {
@Override
Executor executor(BeanFactory beanFactory, Executor executor) {
return supplier.get();
}
});
factory.addAdvice(
new ExecutorMethodInterceptor<Executor>(executor, this.beanFactory) {
@Override
Executor executor(BeanFactory beanFactory, Executor executor) {
return supplier.get();
}
});
factory.setTarget(bean);
return factory;
}
@@ -249,17 +270,21 @@ class ExecutorBeanPostProcessor implements BeanPostProcessor {
private SleuthAsyncProperties asyncConfigurationProperties() {
if (this.sleuthAsyncProperties == null) {
this.sleuthAsyncProperties = this.beanFactory.getBean(SleuthAsyncProperties.class);
this.sleuthAsyncProperties = this.beanFactory
.getBean(SleuthAsyncProperties.class);
}
return this.sleuthAsyncProperties;
}
private static <T> boolean anyFinalMethods(T object, Class<T> iface) {
AtomicBoolean finalMethodPresent = new AtomicBoolean();
ReflectionUtils.doWithMethods(iface, method -> finalMethodPresent.set(true), method -> {
Method m = ReflectionUtils.findMethod(object.getClass(), method.getName(), method.getParameterTypes());
return m != null && !ReflectionUtils.isObjectMethod(m) && Modifier.isFinal(m.getModifiers());
});
ReflectionUtils.doWithMethods(iface, method -> finalMethodPresent.set(true),
method -> {
Method m = ReflectionUtils.findMethod(object.getClass(),
method.getName(), method.getParameterTypes());
return m != null && !ReflectionUtils.isObjectMethod(m)
&& Modifier.isFinal(m.getModifiers());
});
return finalMethodPresent.get();
}
@@ -301,7 +326,8 @@ class ExecutorMethodInterceptor<T extends Executor> implements MethodInterceptor
private Method getMethod(MethodInvocation invocation, Object object) {
Method method = invocation.getMethod();
return ReflectionUtils.findMethod(object.getClass(), method.getName(), method.getParameterTypes());
return ReflectionUtils.findMethod(object.getClass(), method.getName(),
method.getParameterTypes());
}
T executor(BeanFactory beanFactory, T executor) {

View File

@@ -52,8 +52,9 @@ final class TraceRequestHttpHeadersFilter extends AbstractHttpHeadersFilter {
TraceCarrier carrier = new TraceCarrier(exchange.getRequest(), input);
Span span = this.handler.handleSend(this.injector, carrier);
if (log.isDebugEnabled()) {
log.debug("Client span " + span + " created for the request. New headers are "
+ carrier.filteredHeaders.toSingleValueMap());
log.debug(
"Client span " + span + " created for the request. New headers are "
+ carrier.filteredHeaders.toSingleValueMap());
}
exchange.getAttributes().put(SPAN_ATTRIBUTE, span);
HttpHeaders headersWithInput = new HttpHeaders();
@@ -75,7 +76,8 @@ class TraceCarrier {
final HttpHeaders filteredHeaders;
TraceCarrier(@NonNull ServerHttpRequest originalRequest, @NonNull HttpHeaders filteredHeaders) {
TraceCarrier(@NonNull ServerHttpRequest originalRequest,
@NonNull HttpHeaders filteredHeaders) {
this.originalRequest = originalRequest;
this.filteredHeaders = filteredHeaders;
}
@@ -84,7 +86,8 @@ class TraceCarrier {
final class TraceResponseHttpHeadersFilter extends AbstractHttpHeadersFilter {
private static final Log log = LogFactory.getLog(TraceResponseHttpHeadersFilter.class);
private static final Log log = LogFactory
.getLog(TraceResponseHttpHeadersFilter.class);
private TraceResponseHttpHeadersFilter(HttpTracing httpTracing) {
super(httpTracing);
@@ -148,7 +151,8 @@ abstract class AbstractHttpHeadersFilter implements HttpHeadersFilter {
this.httpTracing = httpTracing;
}
private static class ServerHttpAdapter extends brave.http.HttpClientAdapter<TraceCarrier, ServerHttpResponse> {
private static class ServerHttpAdapter
extends brave.http.HttpClientAdapter<TraceCarrier, ServerHttpResponse> {
@Override
public String method(TraceCarrier request) {
@@ -168,7 +172,8 @@ abstract class AbstractHttpHeadersFilter implements HttpHeadersFilter {
@Override
public Integer statusCode(ServerHttpResponse response) {
return response.getStatusCode() != null ? response.getStatusCode().value() : null;
return response.getStatusCode() != null ? response.getStatusCode().value()
: null;
}
}

View File

@@ -72,7 +72,8 @@ public class ExecutorBeanPostProcessorTests {
@Before
public void setup() {
this.sleuthAsyncProperties = new SleuthAsyncProperties();
Mockito.when(this.beanFactory.getBean(SleuthAsyncProperties.class)).thenReturn(this.sleuthAsyncProperties);
Mockito.when(this.beanFactory.getBean(SleuthAsyncProperties.class))
.thenReturn(this.sleuthAsyncProperties);
}
@After
@@ -82,24 +83,28 @@ public class ExecutorBeanPostProcessorTests {
@Test
public void should_create_a_cglib_proxy_by_default() throws Exception {
Object o = new ExecutorBeanPostProcessor(this.beanFactory).postProcessAfterInitialization(new Foo(), "foo");
Object o = new ExecutorBeanPostProcessor(this.beanFactory)
.postProcessAfterInitialization(new Foo(), "foo");
then(o).isInstanceOf(Foo.class);
then(AopUtils.isCglibProxy(o)).isTrue();
}
@Test
public void should_fallback_to_sleuth_implementation_when_cglib_cannot_be_created() throws Exception {
public void should_fallback_to_sleuth_implementation_when_cglib_cannot_be_created()
throws Exception {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
Object o = new ExecutorBeanPostProcessor(this.beanFactory).postProcessAfterInitialization(service, "foo");
Object o = new ExecutorBeanPostProcessor(this.beanFactory)
.postProcessAfterInitialization(service, "foo");
then(o).isInstanceOf(TraceableExecutorService.class);
service.shutdown();
}
@Test
public void should_fallback_to_default_implementation_when_exception_thrown() throws Exception {
public void should_fallback_to_default_implementation_when_exception_thrown()
throws Exception {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
ExecutorBeanPostProcessor bpp = new ExecutorBeanPostProcessor(this.beanFactory) {
@@ -117,7 +122,8 @@ public class ExecutorBeanPostProcessorTests {
}
@Test
public void should_create_a_cglib_proxy_by_default_for_ThreadPoolTaskExecutor() throws Exception {
public void should_create_a_cglib_proxy_by_default_for_ThreadPoolTaskExecutor()
throws Exception {
Object o = new ExecutorBeanPostProcessor(this.beanFactory)
.postProcessAfterInitialization(new FooThreadPoolTaskExecutor(), "foo");
@@ -131,7 +137,8 @@ public class ExecutorBeanPostProcessorTests {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
ExecutorBeanPostProcessor bpp = new ExecutorBeanPostProcessor(this.beanFactory) {
@Override
Object createThreadPoolTaskExecutorProxy(Object bean, boolean cglibProxy, ThreadPoolTaskExecutor executor) {
Object createThreadPoolTaskExecutorProxy(Object bean, boolean cglibProxy,
ThreadPoolTaskExecutor executor) {
throw new AopConfigException("foo");
}
};
@@ -161,7 +168,8 @@ public class ExecutorBeanPostProcessorTests {
ExecutorService service = exceptionThrowingExecutorService();
ExecutorBeanPostProcessor bpp = new ExecutorBeanPostProcessor(this.beanFactory);
ExecutorService o = (ExecutorService) bpp.postProcessAfterInitialization(service, "foo");
ExecutorService o = (ExecutorService) bpp.postProcessAfterInitialization(service,
"foo");
thenThrownBy(() -> o.submit((Callable<Object>) () -> "hello")).hasMessage("foo")
.isInstanceOf(IllegalStateException.class);
@@ -195,7 +203,8 @@ public class ExecutorBeanPostProcessorTests {
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
return false;
}
@@ -215,13 +224,14 @@ public class ExecutorBeanPostProcessorTests {
}
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
return null;
}
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException {
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit) throws InterruptedException {
return null;
}
@@ -232,7 +242,8 @@ public class ExecutorBeanPostProcessorTests {
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout,
TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return null;
}
@@ -241,9 +252,11 @@ public class ExecutorBeanPostProcessorTests {
@Test
public void should_use_jdk_proxy_when_executor_has_final_methods() {
ExecutorBeanPostProcessor beanPostProcessor = new ExecutorBeanPostProcessor(this.beanFactory);
ExecutorBeanPostProcessor beanPostProcessor = new ExecutorBeanPostProcessor(
this.beanFactory);
Executor executor = Runnable::run;
Executor wrappedExecutor = (Executor) beanPostProcessor.postProcessAfterInitialization(executor, "executor");
Executor wrappedExecutor = (Executor) beanPostProcessor
.postProcessAfterInitialization(executor, "executor");
then(AopUtils.isJdkDynamicProxy(wrappedExecutor)).isTrue();
then(AopUtils.isCglibProxy(wrappedExecutor)).isFalse();
@@ -256,8 +269,10 @@ public class ExecutorBeanPostProcessorTests {
}
@Test
public void should_use_jdk_proxy_when_executor_service_has_final_methods() throws Exception {
ExecutorBeanPostProcessor beanPostProcessor = new ExecutorBeanPostProcessor(this.beanFactory);
public void should_use_jdk_proxy_when_executor_service_has_final_methods()
throws Exception {
ExecutorBeanPostProcessor beanPostProcessor = new ExecutorBeanPostProcessor(
this.beanFactory);
ExecutorService executorService = new DelegatingSecurityContextExecutorService(
Executors.newSingleThreadExecutor());
ExecutorService wrappedExecutor = (ExecutorService) beanPostProcessor
@@ -270,8 +285,10 @@ public class ExecutorBeanPostProcessorTests {
}
@Test
public void should_use_jdk_proxy_when_async_task_executor_has_final_methods() throws Exception {
ExecutorBeanPostProcessor beanPostProcessor = new ExecutorBeanPostProcessor(this.beanFactory);
public void should_use_jdk_proxy_when_async_task_executor_has_final_methods()
throws Exception {
ExecutorBeanPostProcessor beanPostProcessor = new ExecutorBeanPostProcessor(
this.beanFactory);
AsyncTaskExecutor wrappedExecutor = (AsyncTaskExecutor) beanPostProcessor
.postProcessAfterInitialization(new DirectTaskExecutor(), "taskExecutor");
@@ -283,11 +300,13 @@ public class ExecutorBeanPostProcessorTests {
@Test
public void should_fallback_to_sleuth_impl_when_thread_pool_task_executor_has_final_methods() {
ExecutorBeanPostProcessor postProcessor = new ExecutorBeanPostProcessor(this.beanFactory);
ExecutorBeanPostProcessor postProcessor = new ExecutorBeanPostProcessor(
this.beanFactory);
ThreadPoolTaskExecutor threadPoolTaskExecutor = new PoolTaskExecutor();
ThreadPoolTaskExecutor wrappedTaskExecutor = (ThreadPoolTaskExecutor) postProcessor
.postProcessAfterInitialization(threadPoolTaskExecutor, "threadPoolTaskExecutor");
.postProcessAfterInitialization(threadPoolTaskExecutor,
"threadPoolTaskExecutor");
then(wrappedTaskExecutor).isInstanceOf(LazyTraceThreadPoolTaskExecutor.class);
then(AopUtils.isCglibProxy(wrappedTaskExecutor)).isFalse();
@@ -297,26 +316,31 @@ public class ExecutorBeanPostProcessorTests {
@Test
public void proxy_is_not_needed() throws Exception {
this.sleuthAsyncProperties.setIgnoredBeans(Collections.singletonList("fooExecutor"));
this.sleuthAsyncProperties
.setIgnoredBeans(Collections.singletonList("fooExecutor"));
boolean isProxyNeeded = new ExecutorBeanPostProcessor(this.beanFactory).isProxyNeeded("fooExecutor");
boolean isProxyNeeded = new ExecutorBeanPostProcessor(this.beanFactory)
.isProxyNeeded("fooExecutor");
then(isProxyNeeded).isFalse();
}
@Test
public void proxy_is_needed() throws Exception {
boolean isProxyNeeded = new ExecutorBeanPostProcessor(this.beanFactory).isProxyNeeded("fooExecutor");
boolean isProxyNeeded = new ExecutorBeanPostProcessor(this.beanFactory)
.isProxyNeeded("fooExecutor");
then(isProxyNeeded).isTrue();
}
@Test
public void should_not_create_proxy() throws Exception {
this.sleuthAsyncProperties.setIgnoredBeans(Collections.singletonList("fooExecutor"));
this.sleuthAsyncProperties
.setIgnoredBeans(Collections.singletonList("fooExecutor"));
Object o = new ExecutorBeanPostProcessor(this.beanFactory)
.postProcessAfterInitialization(new ThreadPoolTaskExecutor(), "fooExecutor");
.postProcessAfterInitialization(new ThreadPoolTaskExecutor(),
"fooExecutor");
then(o).isInstanceOf(ThreadPoolTaskExecutor.class);
then(AopUtils.isCglibProxy(o)).isFalse();
@@ -325,7 +349,8 @@ public class ExecutorBeanPostProcessorTests {
@Test
public void should_throw_real_exception_when_using_proxy() throws Exception {
Object o = new ExecutorBeanPostProcessor(this.beanFactory)
.postProcessAfterInitialization(new RejectedExecutionExecutor(), "fooExecutor");
.postProcessAfterInitialization(new RejectedExecutionExecutor(),
"fooExecutor");
then(o).isInstanceOf(RejectedExecutionExecutor.class);
then(AopUtils.isCglibProxy(o)).isTrue();
@@ -339,22 +364,25 @@ public class ExecutorBeanPostProcessorTests {
LazyTraceThreadPoolTaskExecutor lazyTraceThreadPoolTaskExecutor = BDDMockito
.mock(LazyTraceThreadPoolTaskExecutor.class);
Object o = new ExecutorBeanPostProcessor(this.beanFactory)
.postProcessAfterInitialization(lazyTraceThreadPoolTaskExecutor, "executor");
.postProcessAfterInitialization(lazyTraceThreadPoolTaskExecutor,
"executor");
BDDAssertions.then(o).isSameAs(lazyTraceThreadPoolTaskExecutor);
TraceableExecutorService traceableExecutorService = BDDMockito.mock(TraceableExecutorService.class);
o = new ExecutorBeanPostProcessor(this.beanFactory).postProcessAfterInitialization(traceableExecutorService,
"executor");
TraceableExecutorService traceableExecutorService = BDDMockito
.mock(TraceableExecutorService.class);
o = new ExecutorBeanPostProcessor(this.beanFactory)
.postProcessAfterInitialization(traceableExecutorService, "executor");
BDDAssertions.then(o).isSameAs(traceableExecutorService);
LazyTraceAsyncTaskExecutor lazyTraceAsyncTaskExecutor = BDDMockito.mock(LazyTraceAsyncTaskExecutor.class);
o = new ExecutorBeanPostProcessor(this.beanFactory).postProcessAfterInitialization(lazyTraceAsyncTaskExecutor,
"executor");
LazyTraceAsyncTaskExecutor lazyTraceAsyncTaskExecutor = BDDMockito
.mock(LazyTraceAsyncTaskExecutor.class);
o = new ExecutorBeanPostProcessor(this.beanFactory)
.postProcessAfterInitialization(lazyTraceAsyncTaskExecutor, "executor");
BDDAssertions.then(o).isSameAs(lazyTraceAsyncTaskExecutor);
LazyTraceExecutor lazyTraceExecutor = BDDMockito.mock(LazyTraceExecutor.class);
o = new ExecutorBeanPostProcessor(this.beanFactory).postProcessAfterInitialization(lazyTraceExecutor,
"executor");
o = new ExecutorBeanPostProcessor(this.beanFactory)
.postProcessAfterInitialization(lazyTraceExecutor, "executor");
BDDAssertions.then(o).isSameAs(lazyTraceExecutor);
}

View File

@@ -54,7 +54,8 @@ public class TraceRequestHttpHeadersFilterTests {
.headers(httpHeaders).build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
HttpHeaders filteredHeaders = filter.filter(requestHeaders(httpHeaders), exchange);
HttpHeaders filteredHeaders = filter.filter(requestHeaders(httpHeaders),
exchange);
BDDAssertions.then(filteredHeaders.get("X-B3-TraceId"))
.isNotEqualTo(httpHeaders.get("X-B3-TraceId"));
@@ -80,7 +81,8 @@ public class TraceRequestHttpHeadersFilterTests {
.headers(httpHeaders).build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
HttpHeaders filteredHeaders = filter.filter(requestHeaders(httpHeaders), exchange);
HttpHeaders filteredHeaders = filter.filter(requestHeaders(httpHeaders),
exchange);
BDDAssertions.then(filteredHeaders.get("X-B3-TraceId")).isNotEmpty();
BDDAssertions.then(filteredHeaders.get("X-B3-SpanId")).isNotEmpty();

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.zipkin2.sender;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -49,6 +50,9 @@ import org.springframework.web.client.RestTemplate;
@EnableConfigurationProperties(ZipkinSenderProperties.class)
class ZipkinRestTemplateSenderConfiguration {
private static final Log log = LogFactory
.getLog(ZipkinRestTemplateSenderConfiguration.class);
@Autowired
ZipkinUrlExtractor extractor;
@@ -63,12 +67,60 @@ class ZipkinRestTemplateSenderConfiguration {
@Bean
ZipkinUrlExtractor zipkinUrlExtractor(final ZipkinLoadBalancer zipkinLoadBalancer) {
return new ZipkinUrlExtractor() {
@Override
public URI zipkinUrl(ZipkinProperties zipkinProperties) {
return zipkinLoadBalancer.instance();
return new CachingZipkinUrlExtractor(zipkinLoadBalancer);
}
static class CachingZipkinUrlExtractor implements ZipkinUrlExtractor {
final AtomicInteger zipkinPort = new AtomicInteger();
private final ZipkinLoadBalancer zipkinLoadBalancer;
CachingZipkinUrlExtractor(ZipkinLoadBalancer zipkinLoadBalancer) {
this.zipkinLoadBalancer = zipkinLoadBalancer;
}
@Override
public URI zipkinUrl(ZipkinProperties zipkinProperties) {
int cachedZipkinPort = zipkinPort(zipkinProperties);
if (cachedZipkinPort == -1) {
if (log.isDebugEnabled()) {
log.debug("The port in Zipkin's URL [" + zipkinProperties.getBaseUrl()
+ "] wasn't provided - that means that load balancing might take place");
}
return this.zipkinLoadBalancer.instance();
}
};
if (log.isDebugEnabled()) {
log.debug("The port in Zipkin's URL [" + zipkinProperties.getBaseUrl()
+ "] is provided - that means that load balancing will not take place");
}
return noOpZipkinLoadBalancer(zipkinProperties).instance();
}
NoOpZipkinLoadBalancer noOpZipkinLoadBalancer(ZipkinProperties zipkinProperties) {
return new NoOpZipkinLoadBalancer(zipkinProperties);
}
private int zipkinPort(ZipkinProperties zipkinProperties) {
int cachedZipkinPort = this.zipkinPort.get();
if (cachedZipkinPort != 0) {
return cachedZipkinPort;
}
return calculatePort(zipkinProperties);
}
int calculatePort(ZipkinProperties zipkinProperties) {
String baseUrl = zipkinProperties.getBaseUrl();
URI uri = createUri(baseUrl);
int zipkinPort = uri.getPort();
this.zipkinPort.set(zipkinPort);
return zipkinPort;
}
URI createUri(String baseUrl) {
return URI.create(baseUrl);
}
}
@Configuration

View File

@@ -16,6 +16,9 @@
package org.springframework.cloud.sleuth.zipkin2.sender;
import java.net.URI;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -59,6 +62,51 @@ public class ZipkinRestTemplateSenderConfigurationTest {
ctxt.close();
}
@Test
public void shouldReturnCachedPortValueIfPresent() {
final AtomicBoolean portCalculated = new AtomicBoolean();
ZipkinProperties zipkinProperties = new ZipkinProperties();
ZipkinRestTemplateSenderConfiguration.CachingZipkinUrlExtractor extractor = new ZipkinRestTemplateSenderConfiguration.CachingZipkinUrlExtractor(
new NoOpZipkinLoadBalancer(zipkinProperties)) {
@Override
int calculatePort(ZipkinProperties zipkinProperties) {
portCalculated.set(true);
return super.calculatePort(zipkinProperties);
}
};
extractor.zipkinPort.set(9411);
URI uri = extractor.zipkinUrl(zipkinProperties);
assertThat(uri.toString())
.isEqualTo(URI.create(zipkinProperties.getBaseUrl()).toString());
assertThat(portCalculated).isFalse();
}
@Test
public void shouldDelegateToLoadBalancingWhenNoPortPresent() {
ZipkinProperties zipkinProperties = new ZipkinProperties();
zipkinProperties.setBaseUrl("http://somehostnamewithnoport/endpoint");
ZipkinRestTemplateSenderConfiguration.CachingZipkinUrlExtractor extractor = new ZipkinRestTemplateSenderConfiguration.CachingZipkinUrlExtractor(
() -> URI.create("http://example.com"));
URI uri = extractor.zipkinUrl(zipkinProperties);
assertThat(uri.toString()).isEqualTo(URI.create("http://example.com").toString());
}
@Test
public void shouldDelegateToNonLoadBalancingWhenPortPresent() {
ZipkinProperties zipkinProperties = new ZipkinProperties();
ZipkinRestTemplateSenderConfiguration.CachingZipkinUrlExtractor extractor = new ZipkinRestTemplateSenderConfiguration.CachingZipkinUrlExtractor(
() -> URI.create("http://example.com"));
URI uri = extractor.zipkinUrl(zipkinProperties);
assertThat(uri.toString())
.isEqualTo(URI.create(zipkinProperties.getBaseUrl()).toString());
}
@Configuration
@ConditionalOnClass(LoadBalancerClient.class)
static class MyDiscoveryClientZipkinUrlExtractorConfiguration {