diff --git a/docs/opentracing-logo.png b/docs/opentracing-logo.png
new file mode 100644
index 000000000..768f8f066
Binary files /dev/null and b/docs/opentracing-logo.png differ
diff --git a/docs/spider-web.jpg b/docs/spider-web.jpg
new file mode 100644
index 000000000..3d9e5fe3d
Binary files /dev/null and b/docs/spider-web.jpg differ
diff --git a/pom.xml b/pom.xml
index 38b2cdd0d..5630cc8d0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,6 +58,7 @@
riptide-httpclient
riptide-idempotency
riptide-metrics
+ riptide-opentracing
riptide-problem
riptide-soap
riptide-spring-boot-autoconfigure
@@ -127,6 +128,11 @@
riptide-metrics
${project.version}
+
+ org.zalando
+ riptide-opentracing
+ ${project.version}
+
org.zalando
riptide-problem
diff --git a/riptide-bom/pom.xml b/riptide-bom/pom.xml
index e7a203a22..41527896c 100644
--- a/riptide-bom/pom.xml
+++ b/riptide-bom/pom.xml
@@ -62,6 +62,10 @@
riptide-metrics
3.0.0-SNAPSHOT
+
+ org.zalando
+ riptide-opentracing
+
org.zalando
riptide-problem
diff --git a/riptide-failsafe/src/main/java/org/zalando/riptide/failsafe/CompositeRetryListener.java b/riptide-failsafe/src/main/java/org/zalando/riptide/failsafe/CompositeRetryListener.java
index f7e24d250..39b25ad51 100644
--- a/riptide-failsafe/src/main/java/org/zalando/riptide/failsafe/CompositeRetryListener.java
+++ b/riptide-failsafe/src/main/java/org/zalando/riptide/failsafe/CompositeRetryListener.java
@@ -10,6 +10,7 @@
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
+// TODO package private?
@API(status = EXPERIMENTAL)
public final class CompositeRetryListener implements RetryListener {
diff --git a/riptide-failsafe/src/main/java/org/zalando/riptide/failsafe/FailsafePlugin.java b/riptide-failsafe/src/main/java/org/zalando/riptide/failsafe/FailsafePlugin.java
index 28ed4266a..a2c9a3c58 100644
--- a/riptide-failsafe/src/main/java/org/zalando/riptide/failsafe/FailsafePlugin.java
+++ b/riptide-failsafe/src/main/java/org/zalando/riptide/failsafe/FailsafePlugin.java
@@ -10,12 +10,12 @@
import net.jodah.failsafe.function.CheckedConsumer;
import org.apiguardian.api.API;
import org.springframework.http.client.ClientHttpResponse;
+import org.zalando.riptide.Attribute;
import org.zalando.riptide.Plugin;
import org.zalando.riptide.RequestArguments;
import org.zalando.riptide.RequestExecution;
import org.zalando.riptide.idempotency.IdempotencyPredicate;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
@@ -23,13 +23,13 @@
import static lombok.AccessLevel.PRIVATE;
import static org.apiguardian.api.API.Status.MAINTAINED;
-import static org.zalando.riptide.CancelableCompletableFuture.forwardTo;
-import static org.zalando.riptide.CancelableCompletableFuture.preserveCancelability;
@API(status = MAINTAINED)
@AllArgsConstructor(access = PRIVATE)
public final class FailsafePlugin implements Plugin {
+ public static final Attribute ATTEMPTS = Attribute.generate();
+
private final ImmutableList extends Policy> policies;
private final ScheduledExecutorService scheduler;
private final Predicate predicate;
@@ -57,24 +57,19 @@ public RequestExecution aroundDispatch(final RequestExecution execution) {
return execution.execute(arguments);
}
- final CompletableFuture original = Failsafe.with(select(arguments))
+ return Failsafe.with(policies)
.with(scheduler)
- .getStageAsync(() -> execution.execute(arguments));
-
- final CompletableFuture cancelable = preserveCancelability(original);
- original.whenComplete(forwardTo(cancelable));
- return cancelable;
+ .getStageAsync(context -> execution
+ .execute(withAttempts(arguments, context.getAttemptCount())));
};
}
-
private Policy[] select(final RequestArguments arguments) {
final Stream> stream = policies.stream()
.filter(skipRetriesIfNeeded(arguments))
.map(withRetryListener(arguments));
- @SuppressWarnings("unchecked")
- final Policy[] policies = stream.toArray(Policy[]::new);
+ @SuppressWarnings("unchecked") final Policy[] policies = stream.toArray(Policy[]::new);
return policies;
}
@@ -91,13 +86,21 @@ private UnaryOperator> withRetryListener(final Reques
if (policy instanceof RetryPolicy) {
final RetryPolicy retryPolicy = (RetryPolicy) policy;
return retryPolicy.copy()
- .onRetry(new RetryListenerAdapter(listener, arguments));
+ .onFailedAttempt(new RetryListenerAdapter(listener, arguments));
} else {
return policy;
}
};
}
+ private RequestArguments withAttempts(final RequestArguments arguments, final int attempts) {
+ if (attempts == 0) {
+ return arguments;
+ }
+
+ return arguments.withAttribute(ATTEMPTS, attempts);
+ }
+
@VisibleForTesting
@AllArgsConstructor
static final class RetryListenerAdapter implements CheckedConsumer> {
diff --git a/riptide-faults/src/test/java/org/zalando/riptide/faults/TransientFaultExceptionTest.java b/riptide-faults/src/test/java/org/zalando/riptide/faults/TransientFaultExceptionTest.java
index 27a3006a0..2f8335d45 100644
--- a/riptide-faults/src/test/java/org/zalando/riptide/faults/TransientFaultExceptionTest.java
+++ b/riptide-faults/src/test/java/org/zalando/riptide/faults/TransientFaultExceptionTest.java
@@ -5,10 +5,10 @@
import java.io.IOException;
import java.util.concurrent.TimeoutException;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItemInArray;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
final class TransientFaultExceptionTest {
diff --git a/riptide-metrics/pom.xml b/riptide-metrics/pom.xml
index 8174e2f22..1a4a0090b 100644
--- a/riptide-metrics/pom.xml
+++ b/riptide-metrics/pom.xml
@@ -37,18 +37,6 @@
com.github.rest-driver
rest-client-driver
-
- org.apache.httpcomponents
- httpclient
- 4.5.7
- test
-
-
- commons-logging
- commons-logging
-
-
-
diff --git a/riptide-metrics/src/test/java/org/zalando/riptide/metrics/MetricsPluginTest.java b/riptide-metrics/src/test/java/org/zalando/riptide/metrics/MetricsPluginTest.java
index efdf64282..088a5decd 100644
--- a/riptide-metrics/src/test/java/org/zalando/riptide/metrics/MetricsPluginTest.java
+++ b/riptide-metrics/src/test/java/org/zalando/riptide/metrics/MetricsPluginTest.java
@@ -11,7 +11,6 @@
import org.junit.jupiter.api.Test;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
-import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.zalando.riptide.Http;
import javax.annotation.Nullable;
@@ -65,7 +64,6 @@ private static ObjectMapper createObjectMapper() {
MetricsPluginTest() {
this.factory.setReadTimeout(500);
- this.factory.setTaskExecutor(new ConcurrentTaskExecutor());
}
@Test
diff --git a/riptide-opentracing/README.md b/riptide-opentracing/README.md
new file mode 100644
index 000000000..50dfe8134
--- /dev/null
+++ b/riptide-opentracing/README.md
@@ -0,0 +1,185 @@
+# Riptide: OpenTracing
+
+[![Spider web](../docs/spider-web.jpg)](https://pixabay.com/photos/cobweb-drip-water-mirroring-blue-3725540/)
+
+[![Build Status](https://img.shields.io/travis/zalando/riptide/master.svg)](https://travis-ci.org/zalando/riptide)
+[![Coverage Status](https://img.shields.io/coveralls/zalando/riptide/master.svg)](https://coveralls.io/r/zalando/riptide)
+[![Code Quality](https://img.shields.io/codacy/grade/1fbe3d16ca544c0c8589692632d114de/master.svg)](https://www.codacy.com/app/whiskeysierra/riptide)
+[![Javadoc](https://www.javadoc.io/badge/org.zalando/riptide-metrics.svg)](http://www.javadoc.io/doc/org.zalando/riptide-metrics)
+[![Release](https://img.shields.io/github/release/zalando/riptide.svg)](https://github.com/zalando/riptide/releases)
+[![Maven Central](https://img.shields.io/maven-central/v/org.zalando/riptide-metrics.svg)](https://maven-badges.herokuapp.com/maven-central/org.zalando/riptide-metrics)
+[![OpenTracing](https://img.shields.io/badge/OpenTracing-enabled-blue.svg)](http://opentracing.io)
+[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/zalando/riptide/master/LICENSE)
+
+*Riptide: OpenTracing* adds sophisticated [OpenTracing](https://opentracing.io/) support to *Riptide*.
+
+## Example
+
+```java
+Http.builder()
+ .plugin(new OpenTracingPlugin(tracer))
+ .build();
+```
+
+## Features
+
+- Client span lifecycle management
+- Span context injection into HTTP headers of requests
+- Extensible span decorators for tags and logs
+- Seamless integration with [Riptide: Failsafe](../riptide-failsafe)
+
+## Dependencies
+
+- Java 8
+- Riptide Core
+- [OpenTracing Java API](https://opentracing.io/guides/java/)
+- [Riptide: Failsafe](../riptide-failsafe) (optional)
+
+## Installation
+
+Add the following dependency to your project:
+
+```xml
+
+ org.zalando
+ riptide-opentracing
+ ${riptide.version}
+
+```
+
+## Configuration
+
+```java
+Http.builder()
+ .baseUrl("https://www.example.com")
+ .plugin(new OpenTracingPlugin(tracer))
+ .build();
+```
+
+The following tags/logs are supported out of the box:
+
+| Tag/Log Field | Decorator | Example |
+|----------------------|--------------------------------|-----------------------------------|
+| `component` | `ComponentSpanDecorator` | `Riptide` |
+| `span.kind` | `SpanKindSpanDecorator` | `client` |
+| `peer.hostname` | `PeerSpanDecorator` | `www.github.com` |
+| `peer.port` | `PeerSpanDecorator` | `80` |
+| `http.method` | `HttpMethodSpanDecorator` | `GET` |
+| `http.url` | `HttpUrlSpanDecorator` | `https://www.github.com/users/me` |
+| `http.path` | `HttpPathSpanDecorator` | `/users/{user_id}` |
+| `http.status_code` | `HttpStatusCodeSpanDecorator` | `200` |
+| `error` | `ErrorSpanDecorator` | `false` |
+| `error.kind` (log) | `ErrorSpanDecorator` | `SocketTimeoutException` |
+| `error.object` (log) | `ErrorSpanDecorator` | (exception instance) |
+| `retry` | `RetrySpanDecorator` | `true` |
+| `retry_number` (log) | `RetrySpanDecorator` | `3` |
+| `*` | `CallSiteSpanDecorator` | `admin=true` |
+| `*` | `StaticTagSpanDecorator` | `aws.region=eu-central-1` |
+| `*` | `UriVariablesTagSpanDecorator` | user_id=me |
+
+### Notice
+
+**Be aware**: The `http.url` tag is disabled by default because the full request URI may contain
+sensitive, [*personal data*](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation).
+As an alternative we introduced the `http.path` tag which favors the URI template over the
+already expanded version. That has the additional benefit of a significant lower cardinality
+compared to what `http.url` would provide.
+
+If you still want to enable it, you can do so by just registering the missing span decorator:
+
+```java
+new OpenTracingPlugin(tracer)
+ .withAdditionalSpanDecorators(new HttpUrlSpanDecorator())
+```
+
+### Span Decorators
+
+Span decorators are a simple, yet powerful tool to manipulate the span, i.e. they allow you to
+add tags, logs and baggage to spans. The default set of decorators can be extended by using
+`OpenTracingPlugin#withAdditionalSpanDecorators(..)`:
+
+```java
+new OpenTracingPlugin(tracer)
+ .withAdditionalSpanDecorators(new StaticSpanDecorator(singletonMap(
+ "environment", "local"
+ )))
+```
+
+If the default span decorators are not desired you can replace them completely using
+`OpenTracingPlugin#withSpanDecorators(..)`:
+
+```java
+new OpenTracingPlugin(tracer)
+ .withSpanDecorators(
+ new ComponentSpanDecorator("MSIE"),
+ new SpanKindSpanDecorator(Tags.SPAN_KIND_CONSUMER),
+ new PeerSpanDecorator(),
+ new HttpMethodSpanDecorator(),
+ new HttpPathSpanDecorator(),
+ new HttpUrlSpanDecorator(),
+ new HttpStatusCodeSpanDecorator(),
+ new ErrorSpanDecorator(),
+ new CallSiteSpanDecorator())
+```
+
+## Usage
+
+Typically you won't need to do anything at the call-site regarding OpenTracing, i.e.
+your usages of Riptide should work exactly as before:
+
+```java
+http.get("/users/{id}", userId)
+ .dispatch(series(),
+ on(SUCCESSFUL).call(User.class, this::greet),
+ anySeries().call(problemHandling()))
+```
+
+### Operation Name
+
+By default the HTTP method will be used as the operation name, which might not fit your needs.
+Since deriving a meaningful operation name from request arguments alone is unreliable, you can
+specify the `OpenTracingPlugin.OPERATION_NAME` request attribute to override the default:
+
+```java
+http.get("/users/{id}", userId)
+ .attribute(OpenTracingPlugin.OPERATION_NAME, "get_user")
+ .dispatch(series(),
+ on(SUCCESSFUL).call(User.class, this::greet),
+ anySeries().call(problemHandling()))
+```
+
+### Call-Site Tags
+
+Assuming you have the [`CallSiteSpanDecorator`](#span-decorators) registered (it is by default), you can also
+specify custom tags based on context information which wouldn't be available within the plugin
+anymore:
+
+```java
+http.get("/users/{id}", userId)
+ .attribute(OpenTracingPlugin.TAGS, singletonMap("retry", "true"))
+ .dispatch(series(),
+ on(SUCCESSFUL).call(User.class, this::greet),
+ anySeries().call(problemHandling()))
+```
+
+### URI Variables as Tags
+
+URI templates are not just safer to use (see [Configuration](#notice)), they can also be used to
+generate tags from URI variables. Given you have the `UriVariablesTagSpanDecorator` registered
+then the following will produce a `user_id=123` tag:
+
+```java
+http.get("/users/{user_id}", 123)
+```
+
+The same warning applies as mentioned before regarding [`http.url`](#notice). This feature may
+expose *personal data* and should be used with care.
+
+## Getting Help
+
+If you have questions, concerns, bug reports, etc., please file an issue in this repository's [Issue Tracker](../../../../issues).
+
+## Getting Involved/Contributing
+
+To contribute, simply open a pull request and add a brief description (1-2 sentences) of your addition or change. For
+more details, check the [contribution guidelines](../.github/CONTRIBUTING.md).
diff --git a/riptide-opentracing/pom.xml b/riptide-opentracing/pom.xml
new file mode 100644
index 000000000..da182e94c
--- /dev/null
+++ b/riptide-opentracing/pom.xml
@@ -0,0 +1,68 @@
+
+
+ 4.0.0
+
+
+ org.zalando
+ riptide-parent
+ 3.0.0-SNAPSHOT
+
+
+ riptide-opentracing
+
+ Riptide: OpenTracing
+ Client side response routing
+
+
+ 0.32.0-RC2
+
+
+
+
+ org.zalando
+ riptide-core
+
+
+ io.opentracing
+ opentracing-api
+ ${opentracing.version}
+
+
+ org.zalando
+ riptide-failsafe
+
+ true
+
+
+ io.opentracing
+ opentracing-mock
+ ${opentracing.version}
+ test
+
+
+ com.github.rest-driver
+ rest-client-driver
+
+
+ io.opentracing.contrib
+ opentracing-concurrent
+ 0.2.0
+ test
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.7
+ test
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/ExtensionFields.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/ExtensionFields.java
new file mode 100644
index 000000000..6249fc534
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/ExtensionFields.java
@@ -0,0 +1,14 @@
+package org.zalando.riptide.opentracing;
+
+public final class ExtensionFields {
+
+ /**
+ * In combination with {@link ExtensionTags#RETRY retry tag}, this field holds the number of the retry attempt.
+ */
+ public static final String RETRY_NUMBER = "retry_number";
+
+ private ExtensionFields() {
+
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/ExtensionTags.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/ExtensionTags.java
new file mode 100644
index 000000000..5bbeeaa9d
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/ExtensionTags.java
@@ -0,0 +1,61 @@
+package org.zalando.riptide.opentracing;
+
+import io.opentracing.tag.BooleanTag;
+import io.opentracing.tag.StringTag;
+import io.opentracing.tag.Tag;
+
+public final class ExtensionTags {
+
+ public static final Tag HTTP_PATH = new StringTag("http.path");
+
+ /**
+ * When present on a client span, they represent a span that wraps a retried RPC. If missing no interpretation can
+ * be made. An explicit value of false would explicitly mean it is a first RPC attempt.
+ */
+ public static final Tag RETRY = new BooleanTag("retry");
+
+ /**
+ * The tag should contain an alias or name that allows users to identify the logical location (infrastructure account)
+ * where the operation took place. This can be the AWS account, or any other cloud provider account.
+ * E.g., {@code account=aws:zalando-zmon}, {@code account=gcp:zalando-foobar}
+ */
+ public static final Tag ACCOUNT = new StringTag("account");
+
+ /**
+ * The tag should contain some information that allows users to associate the physical location of the system where
+ * the operation took place (i.e. the datacenter).
+ * E.g., {@code zone=aws:eu-central-1a}, {@code zone=gcp:europe-west3-b}, {@code zone=dc:gth}.
+ */
+ public static final Tag ZONE = new StringTag("zone");
+
+ /**
+ * Oauth2 client ids have a certain cardinality but are well known or possible to get using different means. It
+ * could be helpful for server spans to identify the client making the call. E.g., {@code client_id=cognac}
+ */
+ public static final Tag CLIENT_ID = new StringTag("client_id");
+
+ /**
+ * The flow_id tag should contain the request flow ID, typically found in the ingress requests HTTP header X-Flow-ID.
+ *
+ * X-Flow-ID Guidelines
+ */
+ public static final Tag FLOW_ID = new StringTag("flow_id");
+
+ /**
+ * The tag should contain the artifact version of the running application generating the spans.
+ * This is, usually, the docker image tag.
+ */
+ public static final Tag ARTIFACT_VERSION = new StringTag("artifact_version");
+
+ /**
+ * The tag should contain the unique identifier of the deployment that resulted in the operation of the running
+ * application generating the spans. This is, usually, the STUPS stack version or the Kubernetes deployment id.
+ * A deployment is the combination of a given artifact_version and the environment, usually its configuration.
+ */
+ public static final Tag DEPLOYMENT_ID = new StringTag("deployment_id");
+
+ private ExtensionTags() {
+
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/OpenTracingPlugin.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/OpenTracingPlugin.java
new file mode 100644
index 000000000..ed6beaf05
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/OpenTracingPlugin.java
@@ -0,0 +1,175 @@
+package org.zalando.riptide.opentracing;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Multimaps;
+import io.opentracing.Scope;
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.Tracer.SpanBuilder;
+import io.opentracing.propagation.TextMapAdapter;
+import lombok.AllArgsConstructor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.zalando.fauxpas.ThrowingBiConsumer;
+import org.zalando.riptide.Attribute;
+import org.zalando.riptide.AttributeStage;
+import org.zalando.riptide.Plugin;
+import org.zalando.riptide.RequestArguments;
+import org.zalando.riptide.RequestExecution;
+import org.zalando.riptide.opentracing.span.CallSiteSpanDecorator;
+import org.zalando.riptide.opentracing.span.ComponentSpanDecorator;
+import org.zalando.riptide.opentracing.span.ErrorSpanDecorator;
+import org.zalando.riptide.opentracing.span.HttpMethodSpanDecorator;
+import org.zalando.riptide.opentracing.span.HttpPathSpanDecorator;
+import org.zalando.riptide.opentracing.span.HttpStatusCodeSpanDecorator;
+import org.zalando.riptide.opentracing.span.PeerSpanDecorator;
+import org.zalando.riptide.opentracing.span.SpanDecorator;
+import org.zalando.riptide.opentracing.span.SpanKindSpanDecorator;
+
+import javax.annotation.CheckReturnValue;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletionException;
+import java.util.function.BiConsumer;
+
+import static io.opentracing.propagation.Format.Builtin.HTTP_HEADERS;
+import static java.util.Objects.nonNull;
+import static lombok.AccessLevel.PRIVATE;
+
+@AllArgsConstructor(access = PRIVATE)
+public final class OpenTracingPlugin implements Plugin {
+
+ /**
+ * Allows to pass a customized {@link Tracer#buildSpan(String) operation name} directly from
+ * a call site. Defaults to the {@link RequestArguments#getMethod() HTTP method}.
+ *
+ * @see AttributeStage#attribute(Attribute, Object)
+ */
+ public static final Attribute OPERATION_NAME = Attribute.generate();
+
+ /**
+ * Allows to pass arbitrary span tags directly from a call site.
+ *
+ * @see AttributeStage#attribute(Attribute, Object)
+ */
+ public static final Attribute> TAGS = Attribute.generate();
+
+ /**
+ * Allows to pass arbitrary span logs directly from a call site.
+ *
+ * @see AttributeStage#attribute(Attribute, Object)
+ */
+ public static final Attribute> LOGS = Attribute.generate();
+
+ private final Tracer tracer;
+ private final SpanDecorator decorator;
+
+ public OpenTracingPlugin(final Tracer tracer) {
+ this(tracer, SpanDecorator.composite(
+ new CallSiteSpanDecorator(),
+ new ComponentSpanDecorator(),
+ new ErrorSpanDecorator(),
+ new HttpMethodSpanDecorator(),
+ new HttpPathSpanDecorator(),
+ new HttpStatusCodeSpanDecorator(),
+ new PeerSpanDecorator(),
+ new SpanKindSpanDecorator()
+ ));
+ }
+
+ /**
+ * Creates a new {@link OpenTracingPlugin plugin} by combining the {@link SpanDecorator decorator(s)} of
+ * {@code this} plugin with the supplied ones.
+ *
+ * @param first first decorator
+ * @param decorators optional, remaining decorators
+ * @return a new {@link OpenTracingPlugin}
+ */
+ @CheckReturnValue
+ public OpenTracingPlugin withAdditionalSpanDecorators(final SpanDecorator first,
+ final SpanDecorator... decorators) {
+ return withSpanDecorators(decorator, SpanDecorator.composite(first, decorators));
+ }
+
+ /**
+ * Creates a new {@link OpenTracingPlugin plugin} by replacing the {@link SpanDecorator decorator(s)} of
+ * {@code this} plugin with the supplied ones.
+ *
+ * @param decorator first decorator
+ * @param decorators optional, remaining decorators
+ * @return a new {@link OpenTracingPlugin}
+ */
+ @CheckReturnValue
+ public OpenTracingPlugin withSpanDecorators(final SpanDecorator decorator, final SpanDecorator... decorators) {
+ return new OpenTracingPlugin(tracer, SpanDecorator.composite(decorator, decorators));
+ }
+
+ @Override
+ public RequestExecution aroundDispatch(final RequestExecution execution) {
+ return arguments -> {
+ final Span span = startSpan(arguments);
+ final Scope scope = tracer.activateSpan(span);
+
+ return execution.execute(arguments)
+ .whenComplete(perform(scope::close))
+ .whenComplete(perform(span::finish));
+ };
+ }
+
+ @Override
+ public RequestExecution aroundNetwork(final RequestExecution execution) {
+ return arguments -> {
+ final Span span = tracer.activeSpan();
+
+ return execution.execute(inject(arguments, span.context()))
+ .whenComplete(onResponse(span, arguments))
+ .whenComplete(onError(span, arguments));
+ };
+ }
+
+ private Span startSpan(final RequestArguments arguments) {
+ final String operationName = arguments.getAttribute(OPERATION_NAME)
+ .orElse(arguments.getMethod().name());
+
+ final SpanBuilder builder = tracer.buildSpan(operationName);
+ decorator.onStart(builder, arguments);
+ final Span span = builder.start();
+ decorator.onStarted(span, arguments);
+ return span;
+ }
+
+ private RequestArguments inject(final RequestArguments arguments, final SpanContext context) {
+ final Map headers = new HashMap<>();
+ tracer.inject(context, HTTP_HEADERS, new TextMapAdapter(headers));
+ return arguments.withHeaders(Multimaps.forMap(headers).asMap());
+ }
+
+ private ThrowingBiConsumer onResponse(final Span span,
+ final RequestArguments arguments) {
+
+ return (response, error) -> {
+ if (nonNull(response)) {
+ decorator.onResponse(span, arguments, response);
+ }
+ };
+ }
+
+ private BiConsumer onError(final Span span, final RequestArguments arguments) {
+ return (response, error) -> {
+ if (nonNull(error)) {
+ decorator.onError(span, arguments, unpack(error));
+ }
+ };
+ }
+
+ private static BiConsumer perform(final Runnable runnable) {
+ return (t, u) -> runnable.run();
+ }
+
+ @VisibleForTesting
+ static Throwable unpack(final Throwable error) {
+ return error instanceof CompletionException ? error.getCause() : error;
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/package-info.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/package-info.java
new file mode 100644
index 000000000..76c7c1377
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/package-info.java
@@ -0,0 +1,4 @@
+@ParametersAreNonnullByDefault
+package org.zalando.riptide.opentracing;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/CallSiteSpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/CallSiteSpanDecorator.java
new file mode 100644
index 000000000..1143555ad
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/CallSiteSpanDecorator.java
@@ -0,0 +1,25 @@
+package org.zalando.riptide.opentracing.span;
+
+import io.opentracing.Span;
+import io.opentracing.Tracer;
+import org.zalando.riptide.RequestArguments;
+import org.zalando.riptide.opentracing.OpenTracingPlugin;
+
+import java.util.Collections;
+
+public final class CallSiteSpanDecorator implements SpanDecorator {
+
+ @Override
+ public void onStart(final Tracer.SpanBuilder builder, final RequestArguments arguments) {
+ arguments.getAttribute(OpenTracingPlugin.TAGS)
+ .orElseGet(Collections::emptyMap)
+ .forEach(builder::withTag);
+ }
+
+ @Override
+ public void onStarted(final Span span, final RequestArguments arguments) {
+ arguments.getAttribute(OpenTracingPlugin.LOGS)
+ .ifPresent(span::log);
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/ComponentSpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/ComponentSpanDecorator.java
new file mode 100644
index 000000000..4faf283a1
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/ComponentSpanDecorator.java
@@ -0,0 +1,29 @@
+package org.zalando.riptide.opentracing.span;
+
+import io.opentracing.Tracer;
+import io.opentracing.tag.Tags;
+import org.zalando.riptide.RequestArguments;
+
+/**
+ * Sets the component
span tag, defaults to Riptide
.
+ *
+ * @see Standard Span Tags
+ */
+public final class ComponentSpanDecorator implements SpanDecorator {
+
+ private final String component;
+
+ public ComponentSpanDecorator() {
+ this("Riptide");
+ }
+
+ public ComponentSpanDecorator(final String component) {
+ this.component = component;
+ }
+
+ @Override
+ public void onStart(final Tracer.SpanBuilder builder, final RequestArguments arguments) {
+ builder.withTag(Tags.COMPONENT, component);
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/CompositeSpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/CompositeSpanDecorator.java
new file mode 100644
index 000000000..0743f41ae
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/CompositeSpanDecorator.java
@@ -0,0 +1,45 @@
+package org.zalando.riptide.opentracing.span;
+
+import io.opentracing.Span;
+import io.opentracing.Tracer.SpanBuilder;
+import lombok.Getter;
+import org.springframework.http.client.ClientHttpResponse;
+import org.zalando.riptide.RequestArguments;
+
+import java.util.Collection;
+
+import static org.zalando.fauxpas.FauxPas.throwingConsumer;
+
+final class CompositeSpanDecorator implements SpanDecorator {
+
+ @Getter
+ private final Collection decorators;
+
+ CompositeSpanDecorator(final Collection decorators) {
+ this.decorators = decorators;
+ }
+
+ @Override
+ public void onStart(final SpanBuilder builder, final RequestArguments arguments) {
+ decorators.forEach(decorator -> decorator.onStart(builder, arguments));
+ }
+
+ @Override
+ public void onStarted(final Span span, final RequestArguments arguments) {
+ decorators.forEach(decorator -> decorator.onStarted(span, arguments));
+
+ }
+
+ @Override
+ public void onResponse(final Span span, final RequestArguments arguments, final ClientHttpResponse response) {
+ decorators.forEach(throwingConsumer(decorator -> {
+ decorator.onResponse(span, arguments, response);
+ }));
+ }
+
+ @Override
+ public void onError(final Span span, final RequestArguments arguments, final Throwable error) {
+ decorators.forEach(decorator -> decorator.onError(span, arguments, error));
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/ErrorSpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/ErrorSpanDecorator.java
new file mode 100644
index 000000000..d656921df
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/ErrorSpanDecorator.java
@@ -0,0 +1,36 @@
+package org.zalando.riptide.opentracing.span;
+
+import com.google.common.collect.ImmutableMap;
+import io.opentracing.Span;
+import io.opentracing.log.Fields;
+import io.opentracing.tag.Tags;
+import org.springframework.http.client.ClientHttpResponse;
+import org.zalando.riptide.RequestArguments;
+
+import java.io.IOException;
+
+/**
+ * Sets the error
span tag as well as the error.kind
and error.object
span logs.
+ *
+ * @see Standard Span Tags
+ * @see Standard Log Fields
+ */
+public final class ErrorSpanDecorator implements SpanDecorator {
+
+ @Override
+ public void onResponse(final Span span, final RequestArguments arguments, final ClientHttpResponse response) throws IOException {
+ if (response.getStatusCode().is5xxServerError()) {
+ span.setTag(Tags.ERROR, true);
+ }
+ }
+
+ @Override
+ public void onError(final Span span, final RequestArguments arguments, final Throwable error) {
+ span.setTag(Tags.ERROR, true);
+ span.log(ImmutableMap.of(
+ Fields.ERROR_KIND, error.getClass().getSimpleName(),
+ Fields.ERROR_OBJECT, error
+ ));
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/HttpMethodSpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/HttpMethodSpanDecorator.java
new file mode 100644
index 000000000..106c6ee10
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/HttpMethodSpanDecorator.java
@@ -0,0 +1,19 @@
+package org.zalando.riptide.opentracing.span;
+
+import io.opentracing.Tracer.SpanBuilder;
+import io.opentracing.tag.Tags;
+import org.zalando.riptide.RequestArguments;
+
+/**
+ * Sets the http.method
span tag.
+ *
+ * @see Standard Span Tags
+ */
+public final class HttpMethodSpanDecorator implements SpanDecorator {
+
+ @Override
+ public void onStart(final SpanBuilder builder, final RequestArguments arguments) {
+ builder.withTag(Tags.HTTP_METHOD, arguments.getMethod().name());
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/HttpPathSpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/HttpPathSpanDecorator.java
new file mode 100644
index 000000000..71bbad091
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/HttpPathSpanDecorator.java
@@ -0,0 +1,25 @@
+package org.zalando.riptide.opentracing.span;
+
+import io.opentracing.Tracer.SpanBuilder;
+import org.zalando.riptide.RequestArguments;
+import org.zalando.riptide.opentracing.ExtensionTags;
+
+import static java.util.Objects.nonNull;
+
+/**
+ * Sets the http.path
span tag, based on {@link RequestArguments#getUriTemplate()}.
+ *
+ * @see ExtensionTags#HTTP_PATH
+ */
+public final class HttpPathSpanDecorator implements SpanDecorator {
+
+ @Override
+ public void onStart(final SpanBuilder builder, final RequestArguments arguments) {
+ final String uriTemplate = arguments.getUriTemplate();
+
+ if (nonNull(uriTemplate)) {
+ builder.withTag(ExtensionTags.HTTP_PATH, uriTemplate);
+ }
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/HttpStatusCodeSpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/HttpStatusCodeSpanDecorator.java
new file mode 100644
index 000000000..eefabc853
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/HttpStatusCodeSpanDecorator.java
@@ -0,0 +1,21 @@
+package org.zalando.riptide.opentracing.span;
+
+import io.opentracing.Span;
+import io.opentracing.tag.Tags;
+import org.springframework.http.client.ClientHttpResponse;
+import org.zalando.riptide.RequestArguments;
+
+import java.io.IOException;
+
+/**
+ * Sets the http.status_code
span tag.
+ *
+ * @see Standard Span Tags
+ */
+public final class HttpStatusCodeSpanDecorator implements SpanDecorator {
+
+ @Override
+ public void onResponse(final Span span, final RequestArguments arguments, final ClientHttpResponse response) throws IOException {
+ span.setTag(Tags.HTTP_STATUS, response.getRawStatusCode());
+ }
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/HttpUrlSpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/HttpUrlSpanDecorator.java
new file mode 100644
index 000000000..98b954cc9
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/HttpUrlSpanDecorator.java
@@ -0,0 +1,19 @@
+package org.zalando.riptide.opentracing.span;
+
+import io.opentracing.Tracer.SpanBuilder;
+import io.opentracing.tag.Tags;
+import org.zalando.riptide.RequestArguments;
+
+/**
+ * Sets the http.url
span tag.
+ *
+ * @see Standard Span Tags
+ */
+public final class HttpUrlSpanDecorator implements SpanDecorator {
+
+ @Override
+ public void onStart(final SpanBuilder builder, final RequestArguments arguments) {
+ builder.withTag(Tags.HTTP_URL, arguments.getRequestUri().toString());
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/PeerSpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/PeerSpanDecorator.java
new file mode 100644
index 000000000..45ad2f4f6
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/PeerSpanDecorator.java
@@ -0,0 +1,23 @@
+package org.zalando.riptide.opentracing.span;
+
+import io.opentracing.Tracer.SpanBuilder;
+import io.opentracing.tag.Tags;
+import org.zalando.riptide.RequestArguments;
+
+import java.net.URI;
+
+/**
+ * Sets the peer.hostname
and peer.port
span tags.
+ *
+ * @see Standard Span Tags
+ */
+public final class PeerSpanDecorator implements SpanDecorator {
+
+ @Override
+ public void onStart(final SpanBuilder builder, final RequestArguments arguments) {
+ final URI requestUri = arguments.getRequestUri();
+ builder.withTag(Tags.PEER_HOSTNAME, requestUri.getHost());
+ builder.withTag(Tags.PEER_PORT, requestUri.getPort());
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/RetrySpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/RetrySpanDecorator.java
new file mode 100644
index 000000000..b52e119cd
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/RetrySpanDecorator.java
@@ -0,0 +1,26 @@
+package org.zalando.riptide.opentracing.span;
+
+import io.opentracing.Span;
+import org.zalando.riptide.RequestArguments;
+import org.zalando.riptide.failsafe.FailsafePlugin;
+import org.zalando.riptide.opentracing.ExtensionFields;
+import org.zalando.riptide.opentracing.ExtensionTags;
+
+import static java.util.Collections.singletonMap;
+
+/**
+ * @see FailsafePlugin#ATTEMPTS
+ * @see ExtensionTags#RETRY
+ * @see ExtensionFields#RETRY_NUMBER
+ */
+public final class RetrySpanDecorator implements SpanDecorator {
+
+ @Override
+ public void onStarted(final Span span, final RequestArguments arguments) {
+ arguments.getAttribute(FailsafePlugin.ATTEMPTS).ifPresent(retries -> {
+ span.setTag(ExtensionTags.RETRY, true);
+ span.log(singletonMap(ExtensionFields.RETRY_NUMBER, retries));
+ });
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/SpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/SpanDecorator.java
new file mode 100644
index 000000000..7aab4d158
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/SpanDecorator.java
@@ -0,0 +1,48 @@
+package org.zalando.riptide.opentracing.span;
+
+import com.google.common.collect.Lists;
+import io.opentracing.Span;
+import io.opentracing.Tracer.SpanBuilder;
+import org.springframework.http.client.ClientHttpResponse;
+import org.zalando.riptide.RequestArguments;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.toList;
+
+public interface SpanDecorator {
+
+ default void onStart(final SpanBuilder builder, final RequestArguments arguments) {
+ // nothing to do
+ }
+
+ default void onStarted(final Span span, final RequestArguments arguments) {
+ // nothing to do
+ }
+
+ default void onResponse(final Span span, final RequestArguments arguments, final ClientHttpResponse response)
+ throws IOException {
+ // nothing to do
+ }
+
+ default void onError(final Span span, final RequestArguments arguments, final Throwable error) {
+ // nothing to do
+ }
+
+ static SpanDecorator composite(final SpanDecorator decorator, final SpanDecorator... decorators) {
+ return composite(Lists.asList(decorator, decorators));
+ }
+
+ static SpanDecorator composite(final Collection decorators) {
+ // we flatten first level of nested composite decorators
+ return decorators.stream()
+ .flatMap(decorator -> decorator instanceof CompositeSpanDecorator ?
+ CompositeSpanDecorator.class.cast(decorator).getDecorators().stream() :
+ Stream.of(decorator))
+ .collect(collectingAndThen(toList(), CompositeSpanDecorator::new));
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/SpanKindSpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/SpanKindSpanDecorator.java
new file mode 100644
index 000000000..826842e9a
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/SpanKindSpanDecorator.java
@@ -0,0 +1,29 @@
+package org.zalando.riptide.opentracing.span;
+
+import io.opentracing.Tracer.SpanBuilder;
+import io.opentracing.tag.Tags;
+import org.zalando.riptide.RequestArguments;
+
+/**
+ * Sets the span.kind
span tag.
+ *
+ * @see Standard Span Tags
+ */
+public final class SpanKindSpanDecorator implements SpanDecorator {
+
+ private final String kind;
+
+ public SpanKindSpanDecorator() {
+ this(Tags.SPAN_KIND_CLIENT);
+ }
+
+ public SpanKindSpanDecorator(final String kind) {
+ this.kind = kind;
+ }
+
+ @Override
+ public void onStart(final SpanBuilder builder, final RequestArguments arguments) {
+ builder.withTag(Tags.SPAN_KIND, kind);
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/StaticSpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/StaticSpanDecorator.java
new file mode 100644
index 000000000..f281246cc
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/StaticSpanDecorator.java
@@ -0,0 +1,24 @@
+package org.zalando.riptide.opentracing.span;
+
+import io.opentracing.Tracer.SpanBuilder;
+import org.zalando.riptide.RequestArguments;
+
+import java.util.Map;
+
+/**
+ * Sets arbitrary, static span tags.
+ */
+public final class StaticSpanDecorator implements SpanDecorator {
+
+ private final Map tags;
+
+ public StaticSpanDecorator(final Map tags) {
+ this.tags = tags;
+ }
+
+ @Override
+ public void onStart(final SpanBuilder builder, final RequestArguments arguments) {
+ tags.forEach(builder::withTag);
+ }
+
+}
diff --git a/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/UriVariablesTagSpanDecorator.java b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/UriVariablesTagSpanDecorator.java
new file mode 100644
index 000000000..2f2833223
--- /dev/null
+++ b/riptide-opentracing/src/main/java/org/zalando/riptide/opentracing/span/UriVariablesTagSpanDecorator.java
@@ -0,0 +1,56 @@
+package org.zalando.riptide.opentracing.span;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gag.annotation.remark.Hack;
+import io.opentracing.Tracer.SpanBuilder;
+import org.springframework.web.util.UriComponentsBuilder;
+import org.zalando.riptide.RequestArguments;
+
+import javax.annotation.Nullable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@link SpanDecorator decorator} that extracts contextual tags from the used
+ * {@link RequestArguments#getUriTemplate() URI template} and {@link RequestArguments#getUriVariables() URI variables}.
+ *
+ * Using this decorator in conjunction with {@code http.get("/accounts/{account_id}", 792)}
+ * will produce the tag {@code account_id=792}.
+ *
+ * The OpenTracing Semantic Specification: Start a new Span
+ */
+public final class UriVariablesTagSpanDecorator implements SpanDecorator {
+
+ @Override
+ public void onStart(final SpanBuilder builder, final RequestArguments arguments) {
+ final Map variables = extract(arguments);
+ variables.forEach(builder::withTag);
+ }
+
+ private Map extract(final RequestArguments arguments) {
+ @Nullable final String template = arguments.getUriTemplate();
+
+ if (template == null) {
+ return ImmutableMap.of();
+ }
+
+ return extract(template, arguments.getUriVariables());
+ }
+
+ @Hack("Pretty dirty, but I couldn't find any other way...")
+ private Map extract(final String template, final List values) {
+ final Map variables = new HashMap<>(values.size());
+ final Iterator iterator = values.iterator();
+
+ UriComponentsBuilder.fromUriString(template).build().expand(name -> {
+ final Object value = iterator.next();
+ variables.put(name, String.valueOf(value));
+ return value;
+ });
+
+ return variables;
+ }
+
+}
diff --git a/riptide-opentracing/src/test/java/org/zalando/riptide/opentracing/OpenTracingPluginRetryTest.java b/riptide-opentracing/src/test/java/org/zalando/riptide/opentracing/OpenTracingPluginRetryTest.java
new file mode 100644
index 000000000..8afcac800
--- /dev/null
+++ b/riptide-opentracing/src/test/java/org/zalando/riptide/opentracing/OpenTracingPluginRetryTest.java
@@ -0,0 +1,105 @@
+package org.zalando.riptide.opentracing;
+
+import com.github.restdriver.clientdriver.ClientDriver;
+import com.github.restdriver.clientdriver.ClientDriverFactory;
+import com.google.common.collect.ImmutableList;
+import io.opentracing.contrib.concurrent.TracedExecutorService;
+import io.opentracing.contrib.concurrent.TracedScheduledExecutorService;
+import io.opentracing.mock.MockSpan;
+import io.opentracing.mock.MockTracer;
+import net.jodah.failsafe.RetryPolicy;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.zalando.riptide.Http;
+import org.zalando.riptide.Plugin;
+import org.zalando.riptide.failsafe.FailsafePlugin;
+import org.zalando.riptide.opentracing.span.HttpUrlSpanDecorator;
+import org.zalando.riptide.opentracing.span.RetrySpanDecorator;
+
+import java.util.List;
+
+import static com.github.restdriver.clientdriver.RestClientDriver.giveEmptyResponse;
+import static com.github.restdriver.clientdriver.RestClientDriver.onRequestTo;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static java.util.stream.Collectors.toList;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasKey;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.zalando.riptide.PassRoute.pass;
+
+final class OpenTracingPluginRetryTest {
+
+ private final ClientDriver driver = new ClientDriverFactory().createClientDriver();
+ private final MockTracer tracer = new MockTracer();
+
+ private final Plugin unit = new OpenTracingPlugin(tracer)
+ .withAdditionalSpanDecorators(new HttpUrlSpanDecorator())
+ .withAdditionalSpanDecorators(new RetrySpanDecorator());
+
+ private final Http http = Http.builder()
+ .executor(new TracedExecutorService(newSingleThreadExecutor(), tracer))
+ .requestFactory(new HttpComponentsClientHttpRequestFactory())
+ .baseUrl(driver.getBaseUrl())
+ .plugin(unit)
+ .plugin(new FailsafePlugin(
+ ImmutableList.of(new RetryPolicy()
+ .withMaxRetries(1)
+ .handleResultIf(response -> true)),
+ new TracedScheduledExecutorService(newSingleThreadScheduledExecutor(), tracer)))
+ .plugin(unit)
+ .build();
+
+ @Test
+ void shouldTagRetries() {
+ driver.addExpectation(onRequestTo("/"), giveEmptyResponse().withStatus(200));
+ driver.addExpectation(onRequestTo("/"), giveEmptyResponse().withStatus(200));
+
+ http.get("/").call(pass()).join();
+
+ final List spans = tracer.finishedSpans();
+
+ assertThat(spans, hasSize(3));
+
+ spans.forEach(span -> {
+ assertThat(span.generatedErrors(), is(empty()));
+ assertThat(span.tags(), hasKey("http.url"));
+ });
+
+ final List roots = spans.stream()
+ .filter(span -> span.parentId() == 0)
+ .collect(toList());
+
+ assertThat(roots, hasSize(1));
+
+ roots.forEach(root ->
+ assertThat(root.tags(), not(hasKey("http.status_code"))));
+
+ final List children = spans.stream()
+ .filter(span -> span.parentId() > 0)
+ .collect(toList());
+
+ assertThat(children, hasSize(2));
+
+ children.forEach(child ->
+ assertThat(child.tags(), hasKey("http.status_code")));
+
+ final List retries = spans.stream()
+ .filter(span -> span.tags().containsKey("retry"))
+ .collect(toList());
+
+ assertThat(retries, hasSize(1));
+ }
+
+ @AfterEach
+ void tearDown() {
+ driver.verify();
+ driver.shutdown();
+ }
+
+}
diff --git a/riptide-opentracing/src/test/java/org/zalando/riptide/opentracing/OpenTracingPluginTest.java b/riptide-opentracing/src/test/java/org/zalando/riptide/opentracing/OpenTracingPluginTest.java
new file mode 100644
index 000000000..c228564be
--- /dev/null
+++ b/riptide-opentracing/src/test/java/org/zalando/riptide/opentracing/OpenTracingPluginTest.java
@@ -0,0 +1,241 @@
+package org.zalando.riptide.opentracing;
+
+import com.github.restdriver.clientdriver.ClientDriver;
+import com.github.restdriver.clientdriver.ClientDriverFactory;
+import io.opentracing.Scope;
+import io.opentracing.contrib.concurrent.TracedExecutorService;
+import io.opentracing.mock.MockSpan;
+import io.opentracing.mock.MockSpan.LogEntry;
+import io.opentracing.mock.MockTracer;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.zalando.riptide.Http;
+import org.zalando.riptide.UnexpectedResponseException;
+import org.zalando.riptide.opentracing.span.StaticSpanDecorator;
+import org.zalando.riptide.opentracing.span.UriVariablesTagSpanDecorator;
+
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+import static com.github.restdriver.clientdriver.RestClientDriver.giveEmptyResponse;
+import static com.github.restdriver.clientdriver.RestClientDriver.onRequestTo;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static java.util.Collections.singletonMap;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.hasKey;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.zalando.riptide.NoRoute.noRoute;
+import static org.zalando.riptide.PassRoute.pass;
+
+final class OpenTracingPluginTest {
+
+ private final ClientDriver driver = new ClientDriverFactory().createClientDriver();
+ private final MockTracer tracer = new MockTracer();
+
+ private final Http unit = Http.builder()
+ .executor(new TracedExecutorService(newSingleThreadExecutor(), tracer))
+ .requestFactory(new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create()
+ .setDefaultRequestConfig(RequestConfig.custom()
+ .setSocketTimeout(500)
+ .build())
+ .build()))
+ .baseUrl(driver.getBaseUrl())
+ .plugin(new OpenTracingPlugin(tracer)
+ .withAdditionalSpanDecorators(
+ new StaticSpanDecorator(singletonMap("test.environment", "JUnit")),
+ new UriVariablesTagSpanDecorator()))
+ .build();
+
+ // TODO set socket timeout and test network error
+
+ @Test
+ void shouldTraceRequestAndResponse() {
+ driver.addExpectation(onRequestTo("/users/me")
+ .withHeader("traceid", notNullValue(String.class))
+ .withHeader("spanid", notNullValue(String.class)),
+ giveEmptyResponse().withStatus(200));
+
+ final MockSpan parent = tracer.buildSpan("test").start();
+
+ try (final Scope ignored = tracer.activateSpan(parent)) {
+ unit.get("/users/{user}", "me")
+ .attribute(OpenTracingPlugin.TAGS, singletonMap("test", "true"))
+ .attribute(OpenTracingPlugin.LOGS, singletonMap("retry_number", 1))
+ .call(pass())
+ .join();
+ } finally {
+ parent.finish();
+ }
+
+ final List spans = tracer.finishedSpans();
+ assertThat(spans, hasSize(2));
+
+ assertThat(spans.get(1), is(parent));
+
+ final MockSpan child = spans.get(0);
+ assertThat(child.parentId(), is(parent.context().spanId()));
+
+ assertThat(child.tags(), hasEntry("component", "Riptide"));
+ assertThat(child.tags(), hasEntry("span.kind", "client"));
+ assertThat(child.tags(), hasEntry("peer.hostname", "localhost"));
+ assertThat(child.tags(), hasEntry("peer.port", driver.getPort()));
+ assertThat(child.tags(), hasEntry("http.method", "GET"));
+ assertThat(child.tags(), hasEntry("http.path", "/users/{user}"));
+ assertThat(child.tags(), hasEntry("http.status_code", 200));
+ assertThat(child.tags(), hasEntry("test", "true"));
+ assertThat(child.tags(), hasEntry("test.environment", "JUnit"));
+ assertThat(child.tags(), hasEntry("user", "me"));
+
+ // not active by default
+ assertThat(child.tags(), not(hasKey("http.url")));
+
+ final LogEntry log = getOnlyElement(child.logEntries());
+
+ assertThat(log.fields(), hasEntry("retry_number", 1));
+ }
+
+ @Test
+ void shouldTraceRequestAndServerError() {
+ driver.addExpectation(onRequestTo("/"), giveEmptyResponse().withStatus(500));
+
+ final MockSpan parent = tracer.buildSpan("test").start();
+
+ try (final Scope ignored = tracer.activateSpan(parent)) {
+ final CompletableFuture future = unit.get(URI.create(driver.getBaseUrl()))
+ .attribute(OpenTracingPlugin.TAGS, singletonMap("test", "true"))
+ .attribute(OpenTracingPlugin.LOGS, singletonMap("retry_number", 2))
+ .call(noRoute());
+
+ final CompletionException error = assertThrows(CompletionException.class, future::join);
+ assertThat(error.getCause(), is(instanceOf(UnexpectedResponseException.class)));
+ } finally {
+ parent.finish();
+ }
+
+ final List spans = tracer.finishedSpans();
+ assertThat(spans, hasSize(2));
+
+ assertThat(spans.get(1), is(parent));
+
+ final MockSpan child = spans.get(0);
+ assertThat(child.parentId(), is(parent.context().spanId()));
+
+ assertThat(child.tags(), hasEntry("component", "Riptide"));
+ assertThat(child.tags(), hasEntry("span.kind", "client"));
+ assertThat(child.tags(), hasEntry("peer.hostname", "localhost"));
+ assertThat(child.tags(), hasEntry("peer.port", driver.getPort()));
+ assertThat(child.tags(), hasEntry("http.method", "GET"));
+ assertThat(child.tags(), hasEntry("http.status_code", 500));
+ assertThat(child.tags(), hasEntry("error", true));
+ assertThat(child.tags(), hasEntry("test", "true"));
+ assertThat(child.tags(), hasEntry("test.environment", "JUnit"));
+
+ // since we didn't use a uri template
+ assertThat(child.tags(), not(hasKey("http.path")));
+
+ final LogEntry log = getOnlyElement(child.logEntries());
+ assertThat(log.fields(), hasEntry("retry_number", 2));
+ }
+
+ @Test
+ void shouldTraceRequestAndNetworkError() {
+ driver.addExpectation(onRequestTo("/"), giveEmptyResponse().after(1, SECONDS));
+
+ final MockSpan parent = tracer.buildSpan("test").start();
+
+ try (final Scope ignored = tracer.activateSpan(parent)) {
+ final CompletableFuture future = unit.get(URI.create(driver.getBaseUrl()))
+ .call(noRoute());
+
+ final CompletionException error = assertThrows(CompletionException.class, future::join);
+ assertThat(error.getCause(), is(instanceOf(SocketTimeoutException.class)));
+ } finally {
+ parent.finish();
+ }
+
+ final List spans = tracer.finishedSpans();
+ assertThat(spans, hasSize(2));
+
+ assertThat(spans.get(1), is(parent));
+
+ final MockSpan child = spans.get(0);
+ assertThat(child.parentId(), is(parent.context().spanId()));
+
+ assertThat(child.tags(), hasEntry("component", "Riptide"));
+ assertThat(child.tags(), hasEntry("span.kind", "client"));
+ assertThat(child.tags(), hasEntry("peer.hostname", "localhost"));
+ assertThat(child.tags(), hasEntry("peer.port", driver.getPort()));
+ assertThat(child.tags(), hasEntry("http.method", "GET"));
+ assertThat(child.tags(), hasEntry("error", true));
+
+ // since we didn't use a uri template
+ assertThat(child.tags(), not(hasKey("http.path")));
+
+ // since we didn't get any response
+ assertThat(child.tags(), not(hasKey("http.status_code")));
+
+ final LogEntry log = getOnlyElement(child.logEntries());
+ assertThat(log.fields(), hasEntry("error.kind", "SocketTimeoutException"));
+ assertThat(log.fields(), hasEntry(is("error.object"), is(instanceOf(SocketTimeoutException.class))));
+ }
+
+ @Test
+ void shouldTraceRequestAndIgnoreClientError() {
+ driver.addExpectation(onRequestTo("/"), giveEmptyResponse().withStatus(400));
+
+ final MockSpan parent = tracer.buildSpan("test").start();
+
+ try (final Scope ignored = tracer.activateSpan(parent)) {
+ final CompletableFuture future = unit.get(URI.create(driver.getBaseUrl()))
+ .call(noRoute());
+
+ final CompletionException error = assertThrows(CompletionException.class, future::join);
+ assertThat(error.getCause(), is(instanceOf(UnexpectedResponseException.class)));
+ } finally {
+ parent.finish();
+ }
+
+ final List spans = tracer.finishedSpans();
+ assertThat(spans, hasSize(2));
+
+ assertThat(spans.get(1), is(parent));
+
+ final MockSpan child = spans.get(0);
+ assertThat(child.parentId(), is(parent.context().spanId()));
+
+ assertThat(child.tags(), hasEntry("component", "Riptide"));
+ assertThat(child.tags(), hasEntry("span.kind", "client"));
+ assertThat(child.tags(), hasEntry("peer.hostname", "localhost"));
+ assertThat(child.tags(), hasEntry("peer.port", driver.getPort()));
+ assertThat(child.tags(), hasEntry("http.method", "GET"));
+ assertThat(child.tags(), hasEntry("http.status_code", 400));
+
+ // since we didn't use a uri template
+ assertThat(child.tags(), not(hasKey("error")));
+
+ assertThat(child.logEntries(), is(empty()));
+ }
+
+ @AfterEach
+ void tearDown() {
+ driver.verify();
+ driver.shutdown();
+ }
+
+}
diff --git a/riptide-opentracing/src/test/java/org/zalando/riptide/opentracing/UnpackTest.java b/riptide-opentracing/src/test/java/org/zalando/riptide/opentracing/UnpackTest.java
new file mode 100644
index 000000000..abefcc614
--- /dev/null
+++ b/riptide-opentracing/src/test/java/org/zalando/riptide/opentracing/UnpackTest.java
@@ -0,0 +1,26 @@
+package org.zalando.riptide.opentracing;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.concurrent.CompletionException;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.zalando.riptide.opentracing.OpenTracingPlugin.unpack;
+
+final class UnpackTest {
+
+ @Test
+ void shouldUnpackCompletionException() {
+ final IOException cause = new IOException();
+ assertThat(unpack(new CompletionException(cause)), is(cause));
+ }
+
+ @Test
+ void shouldNotUnpackNonCompletionException() {
+ final RuntimeException exception = new RuntimeException(new IOException());
+ assertThat(unpack(exception), is(exception));
+ }
+
+}
diff --git a/riptide-spring-boot-autoconfigure/README.md b/riptide-spring-boot-autoconfigure/README.md
index 522801411..4373e11f9 100644
--- a/riptide-spring-boot-autoconfigure/README.md
+++ b/riptide-spring-boot-autoconfigure/README.md
@@ -23,19 +23,28 @@ together whenever interaction with a remote service is required. Spinning up new
riptide.clients:
example:
base-url: http://example.com
- connect-timeout: 150 milliseconds
- socket-timeout: 100 milliseconds
- connection-time-to-live: 30 seconds
- max-connections-per-route: 16
+ connections:
+ connect-timeout: 150 milliseconds
+ socket-timeout: 100 milliseconds
+ time-to-live: 30 seconds
+ max-per-route: 16
retry:
+ enabled: true
fixed-delay: 50 milliseconds
max-retries: 5
circuit-breaker:
+ enabled: true
failure-threshold: 3 out of 5
delay: 30 seconds
success-threshold: 5 out of 5
caching:
+ enabled: true
+ shared: false
max-cache-entries: 1000
+ tracing:
+ enabled: true
+ tags:
+ peer.service: example
```
```java
@@ -111,7 +120,7 @@ Required for `retry` and `circuit-breaker` support.
#### [Transient Fault](../riptide-faults) detection
-Required when `detect-transient-faults` is enabled.
+Required when `transient-fault-detection` is enabled.
```xml
@@ -196,7 +205,7 @@ OAuth2 tokens as files in a mounted directory. See
#### [Metrics](../riptide-metrics) integration
-Required when `record-metrics` is enabled.
+Required when `metrics` is enabled.
Will activate `micrometer` metrics support for:
@@ -239,6 +248,23 @@ Required when `caching` is configured:
```
+#### Tracing
+
+Required when `tracing` is configured:
+
+```xml
+
+ org.zalando
+ riptide-opentracing
+ ${riptide.version}
+
+
+ io.opentracing.contrib
+ opentracing-concurrent
+ ${opentracing-concurrent.version}
+
+```
+
## Configuration
You can now define new clients and override default configuration in your `application.yml`:
@@ -248,21 +274,30 @@ riptide:
defaults:
oauth:
credentials-directory: /secrets
+ tracing:
+ enabled: true
+ tags:
+ account: ${CDP_TARGET_INFRASTRUCTURE_ACCOUNT}
+ zone: ${CDP_TARGET_REGION}
+ artifact_version: ${CDP_BUILD_VERSION}
+ deployment_id: ${CDP_DEPLOYMENT_ID}
clients:
example:
base-url: http://example.com
connections:
- connect-timeout: 150 milliseconds
- socket-timeout: 100 milliseconds
- time-to-live: 30 seconds
- max-per-route: 16
+ connect-timeout: 150 milliseconds
+ socket-timeout: 100 milliseconds
+ time-to-live: 30 seconds
+ max-per-route: 16
threads:
min-size: 4
max-size: 16
keep-alive: 1 minnute
queue-size: 0
+ oauth:
+ enabled: true
transient-fault-detection.enabled: true
- stack-trace-preservation: true
+ stack-trace-preservation.enabled: true
retry:
enabled: true
fixed-delay: 50 milliseconds
@@ -290,8 +325,10 @@ riptide:
enabled: true
coefficient: 0.1
default-life-time: 10 minutes
- oauth:
- enabled: true
+ tracing:
+ tags:
+ peer.service: example
+ propagate-flow-id: true
```
Clients are identified by a *Client ID*, for instance `example` in the sample above. You can have as many clients as you want.
@@ -300,141 +337,149 @@ Clients are identified by a *Client ID*, for instance `example` in the sample ab
For a complete overview of available properties, they type and default value please refer to the following table:
-| Configuration | Data type | Default / Comment |
-|-----------------------------------------|----------------|--------------------------------------------------|
-| `riptide` | | |
-| `├── defaults` | | |
-| `│ ├── url-resolution` | `String` | `rfc`, not applicable to Async/RestTemplate |
-| `│ ├── connections` | | |
-| `│ │ ├── connect-timeout` | `TimeSpan` | `5 seconds` |
-| `│ │ ├── socket-timeout` | `TimeSpan` | `5 seconds` |
-| `│ │ ├── time-to-live` | `TimeSpan` | `30 seconds` |
-| `│ │ ├── max-per-route` | `int` | `20` |
-| `│ │ └── max-total` | `int` | `20` (or at least `max-per-route`) |
-| `│ ├── threads` | | |
-| `│ │ ├── min-size` | `int` | `1` |
-| `│ │ ├── max-size` | `int` | same as `connections.max-total` |
-| `│ │ ├── keep-alive` | `TimeSpan` | `1 minute` |
-| `│ │ └── queue-size` | `int` | `0` |
-| `│ ├── oauth` | | |
-| `│ │ ├── enabled` | `boolean` | `false` |
-| `│ │ └── credentials-directory` | `Path` | `/meta/credentials` |
-| `│ ├── transient-fault-detection` | | |
-| `│ │ └── enabled` | `boolean` | `false` |
-| `│ ├── stack-trace-preservation` | | |
-| `│ │ └── enabled` | `boolean` | `false` |
-| `│ ├── metrics` | | |
-| `│ │ └── enabled` | `boolean` | `false` |
-| `│ ├── retry` | | |
-| `│ │ ├── enabled` | `boolean` | `false` |
-| `│ │ ├── fixed-delay` | `TimeSpan` | none, mutually exclusive to `backoff` |
-| `│ │ ├── backoff` | | none, mutually exclusive to `fixed-delay` |
-| `│ │ │ ├── enabled` | `boolean` | `false` |
-| `│ │ │ ├── delay` | `TimeSpan` | none, requires `backoff.max-delay` |
-| `│ │ │ ├── max-delay` | `TimeSpan` | none, requires `backoff.delay` |
-| `│ │ │ └── delay-factor` | `double` | `2.0` |
-| `│ │ ├── max-retries` | `int` | none |
-| `│ │ ├── max-duration` | `TimeSpan` | none |
-| `│ │ ├── jitter-factor` | `double` | none, mutually exclusive to `jitter` |
-| `│ │ └── jitter` | `TimeSpan` | none, mutually exclusive to `jitter-factor` |
-| `│ ├── circuit-breaker` | | |
-| `│ │ ├── enabled` | `boolean` | `false` |
-| `│ │ ├── failure-threshold` | `Ratio` | none |
-| `│ │ ├── delay` | `TimeSpan` | no delay |
-| `│ │ └── success-threshold` | `Ratio` | `failure-threshold` |
-| `│ ├── backup-request` | | |
-| `│ │ ├── enabled` | `boolean` | `false` |
-| `│ │ └── delay` | `TimeSpan` | no delay |
-| `│ ├── timeouts` | | |
-| `│ │ ├── enabled` | `boolean` | `false` |
-| `│ │ └── global` | `TimeSpan` | none |
-| `│ ├── request-compression` | | |
-| `│ │ └── enabled` | `boolean` | `false` |
-| `│ ├── certificate-pinning` | | |
-| `│ │ ├── enabled` | `boolean` | `false` |
-| `│ │ └── keystore` | | |
-| `│ │ ├── path` | `Path` | none |
-| `│ │ └── password` | `String` | none |
-| `│ ├── caching` | | |
-| `│ │ ├── enabled` | `boolean` | `false` |
-| `│ │ ├── shared` | `boolean` | `false` |
-| `│ │ ├── directory` | `String` | none, *in-memory* caching by default |
-| `│ │ ├── max-object-size` | `int` | `8192` |
-| `│ │ ├── max-cache-entries` | `int` | `1000` |
-| `│ │ └── heuristic` | | If max age was not specified by the server |
-| `│ │ ├── enabled` | `boolean` | `false` |
-| `│ │ ├── coefficient` | `double` | `0.1` |
-| `│ │ └── default-life-time` | `TimeSpan` | `0 seconds`, disabled |
-| `│ └── soap` | | |
-| `│ ├── enabled` | `boolean` | `false` |
-| `│ └── protocol` | `String` | `1.1` (possible other value: `1.2`) |
-| `└── clients` | | |
-| ` └── ` | `String` | |
-| ` ├── base-url` | `URI` | none |
-| ` ├── url-resolution` | `String` | see `defaults` |
-| ` ├── connections` | | |
-| ` │ ├── connect-timeout` | `TimeSpan` | see `defaults` |
-| ` │ ├── socket-timeout` | `TimeSpan` | see `defaults` |
-| ` │ ├── time-to-live` | `TimeSpan` | see `defaults` |
-| ` │ ├── max-per-route` | `int` | see `defaults` |
-| ` │ └── max-total` | `int` | see `defaults` |
-| ` └── threads` | | |
-| ` ├── min-size` | `int` | see `defaults` |
-| ` ├── max-size` | `int` | see `defaults` |
-| ` ├── keep-alive` | `TimeSpan` | see `defaults` |
-| ` └── queue-size` | `int` | see `defaults` |
-| ` ├── oauth` | | |
-| ` │ ├── enabled` | `boolean` | see `defaults` |
-| ` │ └── credentials-directory` | `Path` | see `defaults` |
-| ` ├── transient-fault-detection` | | |
-| ` │ └── enabled` | `boolean` | see `defaults` |
-| ` ├── stack-trace-preservation` | | |
-| ` │ └── enabled` | `boolean` | see `defaults` |
-| ` ├── metrics` | | |
-| ` │ └── enabled` | `boolean` | see `defaults` |
-| ` ├── retry` | | |
-| ` │ ├── enabled` | `boolean` | see `defaults` |
-| ` │ ├── fixed-delay` | `TimeSpan` | see `defaults` |
-| ` │ ├── backoff` | | |
-| ` │ │ ├── enabled` | `boolean` | see `defaults` |
-| ` │ │ ├── delay` | `TimeSpan` | see `defaults` |
-| ` │ │ ├── max-delay` | `TimeSpan` | see `defaults` |
-| ` │ │ └── delay-factor` | `double` | see `defaults` |
-| ` │ ├── max-retries` | `int` | see `defaults` |
-| ` │ ├── max-duration` | `TimeSpan` | see `defaults` |
-| ` │ ├── jitter-factor` | `double` | see `defaults` |
-| ` │ └── jitter` | `TimeSpan` | see `defaults` |
-| ` ├── circuit-breaker` | | |
-| ` │ ├── enabled` | `boolean` | see `defaults` |
-| ` │ ├── failure-threshold` | `Ratio` | see `defaults` |
-| ` │ ├── delay` | `TimeSpan` | see `defaults` |
-| ` │ └── success-threshold` | `Ratio` | see `defaults` |
-| ` ├── backup-request` | | |
-| ` │ ├── enabled` | `boolean` | see `defaults` |
-| ` │ └── delay` | `TimeSpan` | see `defaults` |
-| ` ├── timeouts` | | |
-| ` │ ├── enabled` | `boolean` | see `defaults` |
-| ` │ └── global` | `TimeSpan` | see `defaults` |
-| ` ├── request-compression` | | |
-| ` │ └── enabled` | `boolean` | see `defaults` |
-| ` ├── certificate-pinning` | | |
-| ` │ ├── enabled` | `boolean` | see `defaults` |
-| ` │ └── keystore` | | |
-| ` │ ├── path` | `Path` | see `defaults` |
-| ` │ └── password` | `String` | see `defaults` |
-| ` ├── caching` | | see `defaults` |
-| ` │ ├── enabled` | `boolean` | see `defaults` |
-| ` │ ├── shared` | `boolean` | see `defaults` |
-| ` │ ├── directory` | `String` | see `defaults` |
-| ` │ ├── max-object-size` | `int` | see `defaults` |
-| ` │ ├── max-cache-entries` | `int` | see `defaults` |
-| ` │ └── heuristic` | | |
-| ` │ ├── enabled` | `boolean` | see `defaults` |
-| ` │ ├── coefficient` | `double` | see `defaults` |
-| ` │ └── default-life-time` | `TimeSpan` | see `defaults` |
-| ` └── soap` | | |
-| ` ├── enabled` | `boolean` | see `defaults` |
-| ` └── protocol` | `String` | see `defaults` |
+| Configuration | Data type | Default / Comment |
+|-----------------------------------------|-----------------------|--------------------------------------------------|
+| `riptide` | | |
+| `├── defaults` | | |
+| `│ ├── url-resolution` | `String` | `rfc`, not applicable to Async/RestTemplate |
+| `│ ├── connections` | | |
+| `│ │ ├── connect-timeout` | `TimeSpan` | `5 seconds` |
+| `│ │ ├── socket-timeout` | `TimeSpan` | `5 seconds` |
+| `│ │ ├── time-to-live` | `TimeSpan` | `30 seconds` |
+| `│ │ ├── max-per-route` | `int` | `20` |
+| `│ │ └── max-total` | `int` | `20` (or at least `max-per-route`) |
+| `│ ├── threads` | | |
+| `│ │ ├── min-size` | `int` | `1` |
+| `│ │ ├── max-size` | `int` | same as `connections.max-total` |
+| `│ │ ├── keep-alive` | `TimeSpan` | `1 minute` |
+| `│ │ └── queue-size` | `int` | `0` |
+| `│ ├── oauth` | | |
+| `│ │ ├── enabled` | `boolean` | `false` |
+| `│ │ └── credentials-directory` | `Path` | `/meta/credentials` |
+| `│ ├── transient-fault-detection` | | |
+| `│ │ └── enabled` | `boolean` | `false` |
+| `│ ├── stack-trace-preservation` | | |
+| `│ │ └── enabled` | `boolean` | `false` |
+| `│ ├── metrics` | | |
+| `│ │ └── enabled` | `boolean` | `false` |
+| `│ ├── retry` | | |
+| `│ │ ├── enabled` | `boolean` | `false` |
+| `│ │ ├── fixed-delay` | `TimeSpan` | none, mutually exclusive to `backoff` |
+| `│ │ ├── backoff` | | none, mutually exclusive to `fixed-delay` |
+| `│ │ │ ├── enabled` | `boolean` | `false` |
+| `│ │ │ ├── delay` | `TimeSpan` | none, requires `backoff.max-delay` |
+| `│ │ │ ├── max-delay` | `TimeSpan` | none, requires `backoff.delay` |
+| `│ │ │ └── delay-factor` | `double` | `2.0` |
+| `│ │ ├── max-retries` | `int` | none |
+| `│ │ ├── max-duration` | `TimeSpan` | none |
+| `│ │ ├── jitter-factor` | `double` | none, mutually exclusive to `jitter` |
+| `│ │ └── jitter` | `TimeSpan` | none, mutually exclusive to `jitter-factor` |
+| `│ ├── circuit-breaker` | | |
+| `│ │ ├── enabled` | `boolean` | `false` |
+| `│ │ ├── failure-threshold` | `Ratio` | none |
+| `│ │ ├── delay` | `TimeSpan` | no delay |
+| `│ │ └── success-threshold` | `Ratio` | `failure-threshold` |
+| `│ ├── backup-request` | | |
+| `│ │ ├── enabled` | `boolean` | `false` |
+| `│ │ └── delay` | `TimeSpan` | no delay |
+| `│ ├── timeouts` | | |
+| `│ │ ├── enabled` | `boolean` | `false` |
+| `│ │ └── global` | `TimeSpan` | none |
+| `│ ├── request-compression` | | |
+| `│ │ └── enabled` | `boolean` | `false` |
+| `│ ├── certificate-pinning` | | |
+| `│ │ ├── enabled` | `boolean` | `false` |
+| `│ │ └── keystore` | | |
+| `│ │ ├── path` | `Path` | none |
+| `│ │ └── password` | `String` | none |
+| `│ ├── caching` | | |
+| `│ │ ├── enabled` | `boolean` | `false` |
+| `│ │ ├── shared` | `boolean` | `true` |
+| `│ │ ├── directory` | `String` | none, *in-memory* caching by default |
+| `│ │ ├── max-object-size` | `int` | `8192` |
+| `│ │ ├── max-cache-entries` | `int` | `1000` |
+| `│ │ └── heuristic` | | If max age was not specified by the server |
+| `│ │ ├── enabled` | `boolean` | `false` |
+| `│ │ ├── coefficient` | `double` | `0.1` |
+| `│ | └── default-life-time` | `TimeSpan` | `0 seconds`, disabled |
+| `│ ├── tracing` | | |
+| `│ | ├── enabled` | `boolean` | `false` |
+| `│ | ├── tags` | `Map` | none |
+| `│ | └── propagate-flow-id` | `boolean` | `false` |
+| `│ └── soap` | | |
+| `│ ├── enabled` | `boolean` | `false` |
+| `│ └── protocol` | `String` | `1.1` (possible other value: `1.2`) |
+| `└── clients` | | |
+| ` └── ` | `String` | |
+| ` ├── base-url` | `URI` | none |
+| ` ├── url-resolution` | `String` | see `defaults` |
+| ` ├── connections` | | |
+| ` │ ├── connect-timeout` | `TimeSpan` | see `defaults` |
+| ` │ ├── socket-timeout` | `TimeSpan` | see `defaults` |
+| ` │ ├── time-to-live` | `TimeSpan` | see `defaults` |
+| ` │ ├── max-per-route` | `int` | see `defaults` |
+| ` │ └── max-total` | `int` | see `defaults` |
+| ` ├── threads` | | |
+| ` │ ├── min-size` | `int` | see `defaults` |
+| ` │ ├── max-size` | `int` | see `defaults` |
+| ` │ ├── keep-alive` | `TimeSpan` | see `defaults` |
+| ` │ └── queue-size` | `int` | see `defaults` |
+| ` ├── oauth` | | |
+| ` │ ├── enabled` | `boolean` | see `defaults` |
+| ` │ └── credentials-directory` | `Path` | see `defaults` |
+| ` ├── transient-fault-detection` | | |
+| ` │ └── enabled` | `boolean` | see `defaults` |
+| ` ├── stack-trace-preservation` | | |
+| ` │ └── enabled` | `boolean` | see `defaults` |
+| ` ├── metrics` | | |
+| ` │ └── enabled` | `boolean` | see `defaults` |
+| ` ├── retry` | | |
+| ` │ ├── enabled` | `boolean` | see `defaults` |
+| ` │ ├── fixed-delay` | `TimeSpan` | see `defaults` |
+| ` │ ├── backoff` | | |
+| ` │ │ ├── enabled` | `boolean` | see `defaults` |
+| ` │ │ ├── delay` | `TimeSpan` | see `defaults` |
+| ` │ │ ├── max-delay` | `TimeSpan` | see `defaults` |
+| ` │ │ └── delay-factor` | `double` | see `defaults` |
+| ` │ ├── max-retries` | `int` | see `defaults` |
+| ` │ ├── max-duration` | `TimeSpan` | see `defaults` |
+| ` │ ├── jitter-factor` | `double` | see `defaults` |
+| ` │ └── jitter` | `TimeSpan` | see `defaults` |
+| ` ├── circuit-breaker` | | |
+| ` │ ├── enabled` | `boolean` | see `defaults` |
+| ` │ ├── failure-threshold` | `Ratio` | see `defaults` |
+| ` │ ├── delay` | `TimeSpan` | see `defaults` |
+| ` │ └── success-threshold` | `Ratio` | see `defaults` |
+| ` ├── backup-request` | | |
+| ` │ ├── enabled` | `boolean` | see `defaults` |
+| ` │ └── delay` | `TimeSpan` | see `defaults` |
+| ` ├── timeouts` | | |
+| ` │ ├── enabled` | `boolean` | see `defaults` |
+| ` │ └── global` | `TimeSpan` | see `defaults` |
+| ` ├── request-compression` | | |
+| ` │ └── enabled` | `boolean` | see `defaults` |
+| ` ├── certificate-pinning` | | |
+| ` │ ├── enabled` | `boolean` | see `defaults` |
+| ` │ └── keystore` | | |
+| ` │ ├── path` | `Path` | see `defaults` |
+| ` │ └── password` | `String` | see `defaults` |
+| ` ├── caching` | | see `defaults` |
+| ` │ ├── enabled` | `boolean` | see `defaults` |
+| ` │ ├── shared` | `boolean` | see `defaults` |
+| ` │ ├── directory` | `String` | see `defaults` |
+| ` │ ├── max-object-size` | `int` | see `defaults` |
+| ` │ ├── max-cache-entries` | `int` | see `defaults` |
+| ` │ └── heuristic` | | |
+| ` │ ├── enabled` | `boolean` | see `defaults` |
+| ` │ ├── coefficient` | `double` | see `defaults` |
+| ` │ └── default-life-time` | `TimeSpan` | see `defaults` |
+| ` ├── tracing` | | |
+| ` │ ├── enabled` | `boolean` | see `defaults` |
+| ` │ ├── tags` | `Map` | see `defaults` |
+| ` │ └── propagate-flow-id` | `boolean` | see `defaults` |
+| ` └── soap` | | |
+| ` ├── enabled` | `boolean` | see `defaults` |
+| ` └── protocol` | `String` | see `defaults` |
**Beware** that starting with Spring Boot 1.5.x the property resolution for environment variables changes and
properties like `REST_CLIENTS_EXAMPLE_BASEURL` no longer work. As an alternative applications can use the
@@ -467,10 +512,10 @@ Besides `Http`, you can also alternatively inject any of the following types per
- `ClientHttpMessageConverters`
- `AsyncListenableTaskExecutor`
-### Trusted Keystore
+### Certificate Pinning
A client can be configured to only connect to trusted hosts (see
-[Certificate Pinning](https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning)) by configuring the `keystore` key. Use
+[Certificate Pinning](https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning)) by configuring the `certificate-pinning` key. Use
`keystore.path` to refer to a *JKS* keystore on the classpath/filesystem and (optionally) specify the passphrase via `keystore.password`.
You can generate a keystore using the [JDK's keytool](http://docs.oracle.com/javase/7/docs/technotes/tools/#security):
@@ -577,9 +622,9 @@ final class RiptideTest {
private MockRestServiceServer server;
@Test
- public void shouldAutowireMockedHttp() throws Exception {
+ public void shouldAutowireMockedHttp() {
server.expect(requestTo("https://example.com/bar")).andRespond(withSuccess());
- client.remoteCall()
+ client.remoteCall() ;
server.verify();
}
}
diff --git a/riptide-spring-boot-autoconfigure/pom.xml b/riptide-spring-boot-autoconfigure/pom.xml
index ec9159643..7af2e230f 100644
--- a/riptide-spring-boot-autoconfigure/pom.xml
+++ b/riptide-spring-boot-autoconfigure/pom.xml
@@ -17,7 +17,7 @@
1.13.0
- 0.17.0
+ 2.0.0-SNAPSHOT
@@ -94,6 +94,19 @@
true
+
+ org.zalando
+ riptide-opentracing
+
+ true
+
+
+ io.opentracing.contrib
+ opentracing-concurrent
+ 0.2.0
+
+ true
+
org.zalando
riptide-soap
@@ -219,6 +232,12 @@
2.3.1
test
+
+ io.opentracing
+ opentracing-mock
+ 0.32.0-RC2
+ test
+
diff --git a/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/DefaultRiptideRegistrar.java b/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/DefaultRiptideRegistrar.java
index 81e40988a..9beec8896 100644
--- a/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/DefaultRiptideRegistrar.java
+++ b/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/DefaultRiptideRegistrar.java
@@ -5,6 +5,8 @@
import com.google.common.collect.ImmutableMap;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics;
+import io.opentracing.contrib.concurrent.TracedExecutorService;
+import io.opentracing.contrib.concurrent.TracedScheduledExecutorService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.jodah.failsafe.CircuitBreaker;
@@ -45,11 +47,12 @@
import org.zalando.riptide.httpclient.metrics.HttpConnectionPoolMetrics;
import org.zalando.riptide.idempotency.IdempotencyPredicate;
import org.zalando.riptide.metrics.MetricsPlugin;
+import org.zalando.riptide.opentracing.OpenTracingPlugin;
+import org.zalando.riptide.opentracing.span.SpanDecorator;
import org.zalando.riptide.soap.SOAPFaultHttpMessageConverter;
import org.zalando.riptide.soap.SOAPHttpMessageConverter;
import org.zalando.riptide.stream.Streams;
import org.zalando.riptide.timeout.TimeoutPlugin;
-import org.zalando.tracer.concurrent.TracingExecutors;
import javax.annotation.Nullable;
import javax.xml.soap.SOAPConstants;
@@ -101,7 +104,7 @@ private String registerAsyncClientHttpRequestFactory(final String id, final Clie
});
}
- private BeanMetadataElement registerExecutor(final String id, final Client client) {
+ private String registerExecutor(final String id, final Client client) {
final String name = "http-" + id;
final String executorId = registry.registerIfAbsent(id, ExecutorService.class, () -> {
@@ -127,7 +130,14 @@ private BeanMetadataElement registerExecutor(final String id, final Client clien
.addConstructorArgValue(ImmutableList.of(clientId(id))));
}
- return trace(executorId);
+ if (client.getTracing().getEnabled()) {
+ return registry.registerIfAbsent(id, TracedExecutorService.class, () ->
+ genericBeanDefinition(TracedExecutorService.class)
+ .addConstructorArgReference(executorId)
+ .addConstructorArgReference("tracer"));
+ }
+
+ return executorId;
}
private static final class HttpMessageConverters {
@@ -198,7 +208,7 @@ private void registerHttp(final String id, final Client client, final String fac
return genericBeanDefinition(HttpFactory.class)
.setFactoryMethod("create")
- .addConstructorArgValue(registerExecutor(id, client))
+ .addConstructorArgReference(registerExecutor(id, client))
.addConstructorArgReference(factoryId)
.addConstructorArgValue(client.getBaseUrl())
.addConstructorArgValue(client.getUrlResolution())
@@ -232,7 +242,7 @@ private void registerAsyncRestTemplate(final String id, final String factoryId,
genericBeanDefinition(ConcurrentClientHttpRequestFactory.class)
.addConstructorArgReference(factoryId)
.addConstructorArgValue(genericBeanDefinition(ConcurrentTaskExecutor.class)
- .addConstructorArgValue(registerExecutor(id, client))
+ .addConstructorArgReference(registerExecutor(id, client))
.getBeanDefinition())));
template.addConstructorArgReference(factoryId);
configureTemplate(template, client.getBaseUrl(), converters, plugins);
@@ -266,9 +276,10 @@ private List registerPlugins(final String id, final Client client) {
final Stream> plugins = Stream.of(
registerMetricsPlugin(id, client),
registerTransientFaultPlugin(id, client),
+ registerOpenTracingPlugin(id, client),
registerFailsafePlugin(id, client),
- registerBackupPlugin(id, client),
registerAuthorizationPlugin(id, client),
+ registerBackupPlugin(id, client),
registerTimeoutPlugin(id, client),
registerOriginalStackTracePlugin(id, client),
registerCustomPlugin(id));
@@ -281,12 +292,13 @@ private List registerPlugins(final String id, final Client client) {
private Optional registerMetricsPlugin(final String id, final Client client) {
if (client.getMetrics().getEnabled()) {
- log.debug("Client [{}]: Registering [{}]", id, MetricsPlugin.class.getSimpleName());
- final String pluginId = registry.registerIfAbsent(id, MetricsPlugin.class, () ->
- genericBeanDefinition(MetricsPluginFactory.class)
- .setFactoryMethod("createMetricsPlugin")
- .addConstructorArgReference("meterRegistry")
- .addConstructorArgValue(ImmutableList.of(clientId(id))));
+ final String pluginId = registry.registerIfAbsent(id, MetricsPlugin.class, () -> {
+ log.debug("Client [{}]: Registering [{}]", id, MetricsPlugin.class.getSimpleName());
+ return genericBeanDefinition(MetricsPluginFactory.class)
+ .setFactoryMethod("createMetricsPlugin")
+ .addConstructorArgReference("meterRegistry")
+ .addConstructorArgValue(ImmutableList.of(clientId(id)));
+ });
return Optional.of(pluginId);
}
@@ -295,25 +307,55 @@ private Optional registerMetricsPlugin(final String id, final Client cli
private Optional registerTransientFaultPlugin(final String id, final Client client) {
if (client.getTransientFaultDetection().getEnabled()) {
- log.debug("Client [{}]: Registering [{}]", id, TransientFaultPlugin.class.getSimpleName());
- final String pluginId = registry.registerIfAbsent(id, TransientFaultPlugin.class, () ->
- genericBeanDefinition(TransientFaultPlugin.class)
- .addConstructorArgReference(findFaultClassifier(id)));
+ final String pluginId = registry.registerIfAbsent(id, TransientFaultPlugin.class, () -> {
+ log.debug("Client [{}]: Registering [{}]", id, TransientFaultPlugin.class.getSimpleName());
+ return genericBeanDefinition(TransientFaultPlugin.class)
+ .addConstructorArgReference(findFaultClassifier(id));
+ });
return Optional.of(pluginId);
}
return Optional.empty();
}
+ private Optional registerOpenTracingPlugin(final String id, final Client client) {
+ if (client.getTracing().getEnabled()) {
+ registry.registerIfAbsent(id, OpenTracingPlugin.class, () -> {
+ log.debug("Client [{}]: Registering [{}]", id, OpenTracingPlugin.class.getSimpleName());
+
+ final String decorator = generateBeanName(id, SpanDecorator.class);
+ return genericBeanDefinition(OpenTracingPluginFactory.class)
+ .setFactoryMethod("create")
+ .addConstructorArgReference("tracer")
+ .addConstructorArgValue(client)
+ .addConstructorArgValue(registry.isRegistered(decorator) ? ref(decorator) : null);
+ });
+ }
+ return Optional.empty();
+ }
+
private Optional registerFailsafePlugin(final String id, final Client client) {
if (client.getRetry().getEnabled() || client.getCircuitBreaker().getEnabled()) {
- log.debug("Client [{}]: Registering [{}]", id, FailsafePlugin.class.getSimpleName());
- final String pluginId = registry.registerIfAbsent(id, FailsafePlugin.class, () ->
- genericBeanDefinition(FailsafePluginFactory.class)
- .setFactoryMethod("createFailsafePlugin")
- .addConstructorArgValue(registerScheduler(id, client))
- .addConstructorArgValue(registerRetryPolicy(id, client))
- .addConstructorArgValue(registerCircuitBreaker(id, client))
- .addConstructorArgReference(registerRetryListener(id, client)));
+ final String pluginId = registry.registerIfAbsent(id, FailsafePlugin.class, () -> {
+ log.debug("Client [{}]: Registering [{}]", id, FailsafePlugin.class.getSimpleName());
+ return genericBeanDefinition(FailsafePluginFactory.class)
+ .setFactoryMethod("create")
+ .addConstructorArgReference(registerScheduler(id, client))
+ .addConstructorArgValue(registerRetryPolicy(id, client))
+ .addConstructorArgValue(registerCircuitBreaker(id, client))
+ .addConstructorArgReference(registerRetryListener(id, client));
+ });
+ return Optional.of(pluginId);
+ }
+ return Optional.empty();
+ }
+
+ private Optional registerAuthorizationPlugin(final String id, final Client client) {
+ if (client.getOauth().getEnabled()) {
+ final String pluginId = registry.registerIfAbsent(id, AuthorizationPlugin.class, () -> {
+ log.debug("Client [{}]: Registering [{}]", id, AuthorizationPlugin.class.getSimpleName());
+ return genericBeanDefinition(AuthorizationPlugin.class)
+ .addConstructorArgReference(registerAuthorizationProvider(id, client.getOauth()));
+ });
return Optional.of(pluginId);
}
return Optional.empty();
@@ -324,22 +366,11 @@ private Optional registerBackupPlugin(final String id, final Client clie
log.debug("Client [{}]: Registering [{}]", id, BackupRequestPlugin.class.getSimpleName());
final String pluginId = registry.registerIfAbsent(id, BackupRequestPlugin.class, () ->
genericBeanDefinition(BackupRequestPlugin.class)
- .addConstructorArgValue(registerScheduler(id, client))
+ .addConstructorArgReference(registerScheduler(id, client))
.addConstructorArgValue(client.getBackupRequest().getDelay().getAmount())
.addConstructorArgValue(client.getBackupRequest().getDelay().getUnit())
.addConstructorArgValue(new IdempotencyPredicate())
- .addConstructorArgValue(registerExecutor(id, client)));
- return Optional.of(pluginId);
- }
- return Optional.empty();
- }
-
- private Optional registerAuthorizationPlugin(final String id, final Client client) {
- if (client.getOauth().getEnabled()) {
- log.debug("Client [{}]: Registering [{}]", id, AuthorizationPlugin.class.getSimpleName());
- final String pluginId = registry.registerIfAbsent(id, AuthorizationPlugin.class, () ->
- genericBeanDefinition(AuthorizationPlugin.class)
- .addConstructorArgReference(registerAuthorizationProvider(id, client.getOauth())));
+ .addConstructorArgReference(registerExecutor(id, client)));
return Optional.of(pluginId);
}
return Optional.empty();
@@ -347,14 +378,15 @@ private Optional registerAuthorizationPlugin(final String id, final Clie
private Optional registerTimeoutPlugin(final String id, final Client client) {
if (client.getTimeouts().getEnabled()) {
- log.debug("Client [{}]: Registering [{}]", id, TimeoutPlugin.class.getSimpleName());
- final TimeSpan timeout = client.getTimeouts().getGlobal();
- final String pluginId = registry.registerIfAbsent(id, TimeoutPlugin.class, () ->
- genericBeanDefinition(TimeoutPlugin.class)
- .addConstructorArgValue(registerScheduler(id, client))
- .addConstructorArgValue(timeout.getAmount())
- .addConstructorArgValue(timeout.getUnit())
- .addConstructorArgValue(registerExecutor(id, client)));
+ final String pluginId = registry.registerIfAbsent(id, TimeoutPlugin.class, () -> {
+ log.debug("Client [{}]: Registering [{}]", id, TimeoutPlugin.class.getSimpleName());
+ final TimeSpan timeout = client.getTimeouts().getGlobal();
+ return genericBeanDefinition(TimeoutPlugin.class)
+ .addConstructorArgReference(registerScheduler(id, client))
+ .addConstructorArgValue(timeout.getAmount())
+ .addConstructorArgValue(timeout.getUnit())
+ .addConstructorArgReference(registerExecutor(id, client));
+ });
return Optional.of(pluginId);
}
return Optional.empty();
@@ -362,8 +394,10 @@ private Optional registerTimeoutPlugin(final String id, final Client cli
private Optional registerOriginalStackTracePlugin(final String id, final Client client) {
if (client.getStackTracePreservation().getEnabled()) {
- log.debug("Client [{}]: Registering [{}]", id, OriginalStackTracePlugin.class.getSimpleName());
- final String pluginId = registry.registerIfAbsent(id, OriginalStackTracePlugin.class);
+ final String pluginId = registry.registerIfAbsent(id, OriginalStackTracePlugin.class, () -> {
+ log.debug("Client [{}]: Registering [{}]", id, OriginalStackTracePlugin.class.getSimpleName());
+ return genericBeanDefinition(OriginalStackTracePlugin.class);
+ });
return Optional.of(pluginId);
}
return Optional.empty();
@@ -387,7 +421,7 @@ private String findFaultClassifier(final String id) {
}
}
- private BeanMetadataElement registerScheduler(final String id, final Client client) {
+ private String registerScheduler(final String id, final Client client) {
// we allow users to use their own ScheduledExecutorService, but they don't have to configure tracing
final String name = "http-" + id + "-scheduler";
@@ -412,7 +446,14 @@ private BeanMetadataElement registerScheduler(final String id, final Client clie
.addConstructorArgValue(ImmutableList.of(clientId(id))));
}
- return trace(executorId);
+ if (client.getTracing().getEnabled()) {
+ return registry.registerIfAbsent(id, TracedScheduledExecutorService.class, () ->
+ genericBeanDefinition(TracedScheduledExecutorService.class)
+ .addConstructorArgReference(executorId)
+ .addConstructorArgReference("tracer"));
+ }
+
+ return executorId;
}
private BeanMetadataElement registerRetryPolicy(final String id, final Client client) {
@@ -420,7 +461,7 @@ private BeanMetadataElement registerRetryPolicy(final String id, final Client cl
return ref(registry.registerIfAbsent(id, RetryPolicy.class, () ->
genericBeanDefinition(FailsafePluginFactory.class)
.setFactoryMethod("createRetryPolicy")
- .addConstructorArgValue(client.getRetry())));
+ .addConstructorArgValue(client)));
}
return null;
@@ -486,23 +527,6 @@ private String registerAuthorizationProvider(final String id, final OAuth oauth)
.addConstructorArgValue(id));
}
- private BeanMetadataElement trace(final String executor) {
- final Optional result = ifPresent("org.zalando.tracer.concurrent.TracingExecutors",
- () -> {
- if (registry.isRegistered("tracer")) {
- return genericBeanDefinition(TracingExecutors.class)
- .setFactoryMethod("preserve")
- .addConstructorArgReference(executor)
- .addConstructorArgReference("tracer")
- .getBeanDefinition();
- } else {
- return null;
- }
- });
-
- return result.orElseGet(() -> ref(executor));
- }
-
private String registerHttpClient(final String id, final Client client) {
return registry.registerIfAbsent(id, HttpClient.class, () -> {
log.debug("Client [{}]: Registering HttpClient", id);
@@ -538,9 +562,10 @@ private String registerHttpClient(final String id, final Client client) {
private List configureFirstRequestInterceptors(final String id, final Client client) {
final List interceptors = list();
- if (registry.isRegistered("tracerHttpRequestInterceptor")) {
- log.debug("Client [{}]: Registering TracerHttpRequestInterceptor", id);
- interceptors.add(ref("tracerHttpRequestInterceptor"));
+ // TODO theoretically tracing could still be disabled...
+ if (client.getTracing().getPropagateFlowId()) {
+ log.debug("Client [{}]: Registering FlowHttpRequestInterceptor", id);
+ interceptors.add(ref("flowHttpRequestInterceptor"));
}
return interceptors;
diff --git a/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/Defaulting.java b/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/Defaulting.java
index d81dba761..015d21d56 100644
--- a/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/Defaulting.java
+++ b/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/Defaulting.java
@@ -14,10 +14,13 @@
import org.zalando.riptide.autoconfigure.RiptideProperties.Soap;
import org.zalando.riptide.autoconfigure.RiptideProperties.StackTracePreservation;
import org.zalando.riptide.autoconfigure.RiptideProperties.Timeouts;
+import org.zalando.riptide.autoconfigure.RiptideProperties.Tracing;
import org.zalando.riptide.autoconfigure.RiptideProperties.TransientFaultDetection;
import javax.annotation.Nullable;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Objects;
import java.util.function.BinaryOperator;
@@ -63,6 +66,7 @@ private static Defaults merge(final Defaults defaults) {
defaults.getRequestCompression(),
defaults.getCertificatePinning(),
defaults.getCaching(),
+ defaults.getTracing(),
defaults.getSoap()
);
}
@@ -96,6 +100,7 @@ private static Client merge(final Client base, final Defaults defaults) {
merge(base.getRequestCompression(), defaults.getRequestCompression(), Defaulting::merge),
merge(base.getCertificatePinning(), defaults.getCertificatePinning(), Defaulting::merge),
merge(base.getCaching(), defaults.getCaching(), Defaulting::merge),
+ merge(base.getTracing(), defaults.getTracing(), Defaulting::merge),
merge(base.getSoap(), defaults.getSoap(), Defaulting::merge)
);
}
@@ -233,6 +238,24 @@ private static Heuristic merge(final Heuristic base, final Heuristic defaults) {
);
}
+ private static Tracing merge(final Tracing base, final Tracing defaults) {
+ final boolean enabled = either(base.getEnabled(), defaults.getEnabled());
+ final boolean propagateFlowId = either(base.getPropagateFlowId(), defaults.getPropagateFlowId());
+
+ return new Tracing(
+ enabled,
+ merge(base.getTags(), defaults.getTags(), Defaulting::merge),
+ enabled && propagateFlowId
+ );
+ }
+
+ private static Map merge(final Map base, final Map defaults) {
+ final Map map = new HashMap<>();
+ map.putAll(defaults);
+ map.putAll(base);
+ return map;
+ }
+
private static Soap merge(final Soap base, final Soap defaults) {
return new Soap(
either(base.getEnabled(), defaults.getEnabled()),
diff --git a/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/FailsafePluginFactory.java b/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/FailsafePluginFactory.java
index fefcf4baa..1264242f5 100644
--- a/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/FailsafePluginFactory.java
+++ b/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/FailsafePluginFactory.java
@@ -6,6 +6,8 @@
import net.jodah.failsafe.RetryPolicy;
import org.springframework.http.client.ClientHttpResponse;
import org.zalando.riptide.Plugin;
+import org.zalando.riptide.autoconfigure.RiptideProperties.Client;
+import org.zalando.riptide.autoconfigure.RiptideProperties.Retry;
import org.zalando.riptide.autoconfigure.RiptideProperties.Retry.Backoff;
import org.zalando.riptide.failsafe.CircuitBreakerListener;
import org.zalando.riptide.failsafe.FailsafePlugin;
@@ -30,7 +32,7 @@ private FailsafePluginFactory() {
}
- public static Plugin createFailsafePlugin(
+ public static Plugin create(
final ScheduledExecutorService scheduler,
@Nullable final RetryPolicy retryPolicy,
@Nullable final CircuitBreaker circuitBreaker,
@@ -50,9 +52,11 @@ public static Plugin createFailsafePlugin(
.withListener(listener);
}
- public static RetryPolicy createRetryPolicy(final RiptideProperties.Retry config) {
+ public static RetryPolicy createRetryPolicy(final Client client) {
final RetryPolicy policy = new RetryPolicy<>();
+ final Retry config = client.getRetry();
+
Optional.ofNullable(config.getFixedDelay())
.ifPresent(delay -> delay.applyTo(policy::withDelay));
@@ -84,14 +88,17 @@ public static RetryPolicy createRetryPolicy(final RiptidePro
Optional.ofNullable(config.getJitter())
.ifPresent(jitter -> jitter.applyTo(policy::withJitter));
- policy.handle(TransientFaultException.class);
+ if (client.getTransientFaultDetection().getEnabled()) {
+ policy.handle(TransientFaultException.class);
+ }
+
policy.handle(RetryException.class);
policy.withDelay(new RetryAfterDelayFunction(systemUTC()));
return policy;
}
- public static CircuitBreaker createCircuitBreaker(final RiptideProperties.Client client,
+ public static CircuitBreaker createCircuitBreaker(final Client client,
final CircuitBreakerListener listener) {
final CircuitBreaker breaker = new CircuitBreaker<>();
diff --git a/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/OpenTracingPluginFactory.java b/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/OpenTracingPluginFactory.java
new file mode 100644
index 000000000..77d5605a1
--- /dev/null
+++ b/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/OpenTracingPluginFactory.java
@@ -0,0 +1,43 @@
+package org.zalando.riptide.autoconfigure;
+
+import io.opentracing.Tracer;
+import org.zalando.riptide.Plugin;
+import org.zalando.riptide.autoconfigure.RiptideProperties.Client;
+import org.zalando.riptide.opentracing.OpenTracingPlugin;
+import org.zalando.riptide.opentracing.span.RetrySpanDecorator;
+import org.zalando.riptide.opentracing.span.SpanDecorator;
+import org.zalando.riptide.opentracing.span.StaticSpanDecorator;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.zalando.riptide.opentracing.span.SpanDecorator.composite;
+
+@SuppressWarnings("unused")
+final class OpenTracingPluginFactory {
+
+ private OpenTracingPluginFactory() {
+
+ }
+
+ public static Plugin create(final Tracer tracer, final Client client, @Nullable final SpanDecorator decorator) {
+ final List decorators = new ArrayList<>();
+ decorators.add(new StaticSpanDecorator(client.getTracing().getTags()));
+
+ if (client.getRetry().getEnabled()) {
+ decorators.add(new RetrySpanDecorator());
+ }
+
+ return create(tracer, decorator)
+ .withAdditionalSpanDecorators(composite(decorators));
+ }
+
+ private static OpenTracingPlugin create(final Tracer tracer,
+ @Nullable final SpanDecorator decorator) {
+ return decorator == null ?
+ new OpenTracingPlugin(tracer) :
+ new OpenTracingPlugin(tracer).withSpanDecorators(decorator);
+ }
+
+}
diff --git a/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/Registry.java b/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/Registry.java
index 38c0a9abb..95e8a0fe8 100644
--- a/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/Registry.java
+++ b/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/Registry.java
@@ -18,7 +18,6 @@
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.LOWER_HYPHEN;
import static com.google.common.base.CaseFormat.UPPER_CAMEL;
-import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
final class Registry {
@@ -51,10 +50,6 @@ public String registerIfAbsent(final Class type, final Supplier String registerIfAbsent(final String id, final Class type) {
- return registerIfAbsent(id, type, () -> genericBeanDefinition(type));
- }
-
public String registerIfAbsent(final String id, final Class type,
final Supplier factory) {
return registerIfAbsent(id, generateBeanName(id, type), factory);
diff --git a/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/RiptideProperties.java b/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/RiptideProperties.java
index e0ddee2ad..96f57961f 100644
--- a/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/RiptideProperties.java
+++ b/riptide-spring-boot-autoconfigure/src/main/java/org/zalando/riptide/autoconfigure/RiptideProperties.java
@@ -18,6 +18,7 @@
import java.util.LinkedHashMap;
import java.util.Map;
+import static java.util.Collections.emptyMap;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apiguardian.api.API.Status.INTERNAL;
@@ -97,6 +98,9 @@ public static final class Defaults {
)
);
+ @NestedConfigurationProperty
+ private Tracing tracing = new Tracing(false, emptyMap(), false);
+
@NestedConfigurationProperty
private Soap soap = new Soap(false, "1.1");
@@ -150,6 +154,9 @@ public static final class Client {
@NestedConfigurationProperty
private Caching caching;
+ @NestedConfigurationProperty
+ private Tracing tracing;
+
@NestedConfigurationProperty
private Soap soap;
@@ -318,6 +325,17 @@ public static final class Heuristic {
}
}
+ @Getter
+ @Setter
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static final class Tracing {
+ private Boolean enabled;
+ private Map tags;
+ private Boolean propagateFlowId;
+ }
+
+
@Getter
@Setter
@NoArgsConstructor
diff --git a/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/DefaultTestConfiguration.java b/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/DefaultTestConfiguration.java
index 31d05bf00..e959e9e70 100644
--- a/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/DefaultTestConfiguration.java
+++ b/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/DefaultTestConfiguration.java
@@ -13,6 +13,7 @@
JacksonAutoConfiguration.class,
LogbookAutoConfiguration.class,
TracerAutoConfiguration.class,
+ OpenTracingTestAutoConfiguration.class,
MetricsTestAutoConfiguration.class,
})
@ActiveProfiles("default")
diff --git a/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/ManualConfiguration.java b/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/ManualConfiguration.java
index b8b97c0d9..4de85a3e0 100644
--- a/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/ManualConfiguration.java
+++ b/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/ManualConfiguration.java
@@ -4,6 +4,9 @@
import com.google.common.collect.ImmutableList;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
+import io.opentracing.Tracer;
+import io.opentracing.contrib.concurrent.TracedExecutorService;
+import io.opentracing.contrib.concurrent.TracedScheduledExecutorService;
import net.jodah.failsafe.CircuitBreaker;
import net.jodah.failsafe.RetryPolicy;
import org.apache.http.client.config.RequestConfig;
@@ -47,11 +50,11 @@
import org.zalando.riptide.httpclient.ApacheClientHttpRequestFactory;
import org.zalando.riptide.httpclient.GzipHttpRequestInterceptor;
import org.zalando.riptide.metrics.MetricsPlugin;
+import org.zalando.riptide.opentracing.OpenTracingPlugin;
import org.zalando.riptide.stream.Streams;
import org.zalando.riptide.timeout.TimeoutPlugin;
-import org.zalando.tracer.Tracer;
-import org.zalando.tracer.concurrent.TracingExecutors;
-import org.zalando.tracer.httpclient.TracerHttpRequestInterceptor;
+import org.zalando.tracer.Flow;
+import org.zalando.tracer.httpclient.FlowHttpRequestInterceptor;
import org.zalando.tracer.spring.TracerAutoConfiguration;
import java.time.Duration;
@@ -100,7 +103,7 @@ public Http exampleHttp(final Executor executor, final ClientHttpRequestFactory
}
@Bean
- public List examplePlugins(final MeterRegistry meterRegistry,
+ public List examplePlugins(final MeterRegistry meterRegistry, final Tracer tracer,
final ScheduledExecutorService scheduler) {
final CircuitBreakerListener listener = new MetricsCircuitBreakerListener(meterRegistry)
@@ -110,6 +113,7 @@ public List examplePlugins(final MeterRegistry meterRegistry,
new MetricsPlugin(meterRegistry)
.withDefaultTags(Tag.of("clientId", "example")),
new TransientFaultPlugin(),
+ new OpenTracingPlugin(tracer),
new FailsafePlugin(
ImmutableList.of(
new RetryPolicy()
@@ -169,7 +173,7 @@ public AsyncRestTemplate exampleAsyncRestTemplate(final ClientHttpRequestFactory
@Bean
public ApacheClientHttpRequestFactory exampleAsyncClientHttpRequestFactory(
- final Tracer tracer, final Logbook logbook) throws Exception {
+ final Flow flow, final Logbook logbook) throws Exception {
return new ApacheClientHttpRequestFactory(
HttpClientBuilder.create()
.setDefaultRequestConfig(RequestConfig.custom()
@@ -179,7 +183,7 @@ public ApacheClientHttpRequestFactory exampleAsyncClientHttpRequestFactory(
.setConnectionTimeToLive(30, SECONDS)
.setMaxConnPerRoute(2)
.setMaxConnTotal(20)
- .addInterceptorFirst(new TracerHttpRequestInterceptor(tracer))
+ .addInterceptorFirst(new FlowHttpRequestInterceptor(flow))
.addInterceptorLast(new LogbookHttpRequestInterceptor(logbook))
.addInterceptorLast(new GzipHttpRequestInterceptor())
.addInterceptorLast(new LogbookHttpResponseInterceptor())
@@ -195,7 +199,7 @@ public ApacheClientHttpRequestFactory exampleAsyncClientHttpRequestFactory(
@Bean(destroyMethod = "shutdown")
public ExecutorService executor(final Tracer tracer) {
- return TracingExecutors.preserve(
+ return new TracedExecutorService(
new ThreadPoolExecutor(
1, 20, 1, MINUTES,
new ArrayBlockingQueue<>(0),
@@ -206,7 +210,7 @@ public ExecutorService executor(final Tracer tracer) {
@Bean(destroyMethod = "shutdown")
public ScheduledExecutorService scheduler(final Tracer tracer) {
- return TracingExecutors.preserve(
+ return new TracedScheduledExecutorService(
Executors.newScheduledThreadPool(
20, // TODO max-connections-total?
new CustomizableThreadFactory("http-example-scheduler-")),
diff --git a/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/OpenTracingTestAutoConfiguration.java b/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/OpenTracingTestAutoConfiguration.java
new file mode 100644
index 000000000..5707faa0d
--- /dev/null
+++ b/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/OpenTracingTestAutoConfiguration.java
@@ -0,0 +1,19 @@
+package org.zalando.riptide.autoconfigure;
+
+import io.opentracing.Tracer;
+import io.opentracing.mock.MockTracer;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.zalando.tracer.spring.TracerAutoConfiguration;
+
+@Configuration
+@AutoConfigureBefore(TracerAutoConfiguration.class)
+public class OpenTracingTestAutoConfiguration {
+
+ @Bean
+ public Tracer tracer() {
+ return new MockTracer();
+ }
+
+}
diff --git a/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/metrics/MetricsTest.java b/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/metrics/MetricsTest.java
index 7b587f37f..bcb2eea5f 100644
--- a/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/metrics/MetricsTest.java
+++ b/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/metrics/MetricsTest.java
@@ -14,6 +14,7 @@
import org.zalando.logbook.spring.LogbookAutoConfiguration;
import org.zalando.riptide.Http;
import org.zalando.riptide.autoconfigure.MetricsTestAutoConfiguration;
+import org.zalando.riptide.autoconfigure.OpenTracingTestAutoConfiguration;
import org.zalando.riptide.autoconfigure.RiptideClientTest;
import org.zalando.riptide.faults.TransientFaultException;
import org.zalando.tracer.spring.TracerAutoConfiguration;
@@ -43,6 +44,7 @@ final class MetricsTest {
JacksonAutoConfiguration.class,
LogbookAutoConfiguration.class,
TracerAutoConfiguration.class,
+ OpenTracingTestAutoConfiguration.class,
MetricsTestAutoConfiguration.class,
})
static class ContextConfiguration {
diff --git a/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/url/UrlResolutionTest.java b/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/url/UrlResolutionTest.java
index c992f19ab..4f850912d 100644
--- a/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/url/UrlResolutionTest.java
+++ b/riptide-spring-boot-autoconfigure/src/test/java/org/zalando/riptide/autoconfigure/url/UrlResolutionTest.java
@@ -5,13 +5,17 @@
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.client.MockRestServiceServer;
import org.zalando.logbook.spring.LogbookAutoConfiguration;
import org.zalando.riptide.Http;
import org.zalando.riptide.autoconfigure.MetricsTestAutoConfiguration;
+import org.zalando.riptide.autoconfigure.OpenTracingTestAutoConfiguration;
import org.zalando.riptide.autoconfigure.RiptideClientTest;
+import org.zalando.riptide.opentracing.span.HttpUrlSpanDecorator;
+import org.zalando.riptide.opentracing.span.SpanDecorator;
import org.zalando.tracer.spring.TracerAutoConfiguration;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
@@ -27,11 +31,17 @@ final class UrlResolutionTest {
JacksonAutoConfiguration.class,
LogbookAutoConfiguration.class,
TracerAutoConfiguration.class,
+ OpenTracingTestAutoConfiguration.class,
MetricsTestAutoConfiguration.class,
})
@ActiveProfiles("default")
static class ContextConfiguration {
+ @Bean
+ public SpanDecorator exampleSpanDecorator() {
+ return new HttpUrlSpanDecorator();
+ }
+
}
@Autowired
diff --git a/riptide-spring-boot-autoconfigure/src/test/resources/application-default.yml b/riptide-spring-boot-autoconfigure/src/test/resources/application-default.yml
index 64b0b24ad..ca2b2859e 100644
--- a/riptide-spring-boot-autoconfigure/src/test/resources/application-default.yml
+++ b/riptide-spring-boot-autoconfigure/src/test/resources/application-default.yml
@@ -3,6 +3,10 @@ riptide:
transient-fault-detection.enabled: false
stack-trace-preservation.enabled: false
metrics.enabled: true
+ tracing:
+ tags:
+ environment: test
+ propagate-flow-id: false
clients:
example:
base-url: https://example.com/foo
@@ -17,12 +21,19 @@ riptide:
queue-size: 10
stack-trace-preservation.enabled: true
metrics.enabled: true
+ tracing:
+ enabled: true
+ tags:
+ peer.service: example
+ propagate-flow-id: true
ecb:
base-url: http://www.ecb.europa.eu
request-compression.enabled: true
timeouts:
enabled: true
global: 1 seconds
+ tracing:
+ propagate-flow-id: true
soap:
enabled: true
protocol: 1.2
@@ -45,8 +56,11 @@ riptide:
retry:
enabled: true
max-retries: 3
+ tracing:
+ enabled: true
bar:
base-url: http://bar
+ transient-fault-detection.enabled: true
retry:
enabled: true
max-retries: 4