Skip to content

Commit

Permalink
Added OpenTracing plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
whiskeysierra committed Apr 3, 2019
1 parent 2cf1bd2 commit fced0a5
Show file tree
Hide file tree
Showing 45 changed files with 1,706 additions and 138 deletions.
Binary file added docs/spider-web.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
<module>riptide-httpclient</module>
<module>riptide-idempotency</module>
<module>riptide-metrics</module>
<module>riptide-opentracing</module>
<module>riptide-problem</module>
<module>riptide-soap</module>
<module>riptide-spring-boot-autoconfigure</module>
Expand Down Expand Up @@ -133,6 +134,11 @@
<artifactId>riptide-metrics</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-opentracing</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-problem</artifactId>
Expand Down
4 changes: 4 additions & 0 deletions riptide-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
<artifactId>riptide-metrics</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-opentracing</artifactId>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-problem</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

// TODO package private?
@API(status = EXPERIMENTAL)
public final class CompositeRetryListener implements RetryListener {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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;
Expand All @@ -27,6 +28,8 @@
@AllArgsConstructor(access = PRIVATE)
public final class FailsafePlugin implements Plugin {

public static final Attribute<Integer> ATTEMPTS = Attribute.generate();

private final ImmutableList<? extends Policy<ClientHttpResponse>> policies;
private final ScheduledExecutorService scheduler;
private final Predicate<RequestArguments> predicate;
Expand Down Expand Up @@ -56,18 +59,17 @@ public RequestExecution aroundDispatch(final RequestExecution execution) {

return Failsafe.with(select(arguments))
.with(scheduler)
.getStageAsync(() -> execution.execute(arguments));
.getStageAsync(context -> execution
.execute(withAttempts(arguments, context.getAttemptCount())));
};
}


private Policy<ClientHttpResponse>[] select(final RequestArguments arguments) {
final Stream<Policy<ClientHttpResponse>> stream = policies.stream()
.filter(skipRetriesIfNeeded(arguments))
.map(withRetryListener(arguments));

@SuppressWarnings("unchecked")
final Policy<ClientHttpResponse>[] policies = stream.toArray(Policy[]::new);
@SuppressWarnings("unchecked") final Policy<ClientHttpResponse>[] policies = stream.toArray(Policy[]::new);

return policies;
}
Expand All @@ -84,13 +86,21 @@ private UnaryOperator<Policy<ClientHttpResponse>> withRetryListener(final Reques
if (policy instanceof RetryPolicy) {
final RetryPolicy<ClientHttpResponse> retryPolicy = (RetryPolicy<ClientHttpResponse>) 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<ExecutionAttemptedEvent<ClientHttpResponse>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
185 changes: 185 additions & 0 deletions riptide-opentracing/README.md
Original file line number Diff line number Diff line change
@@ -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
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-opentracing</artifactId>
<version>${riptide.version}</version>
</dependency>
```

## 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).
68 changes: 68 additions & 0 deletions riptide-opentracing/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.zalando</groupId>
<artifactId>riptide-parent</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>

<artifactId>riptide-opentracing</artifactId>

<name>Riptide: OpenTracing</name>
<description>Client side response routing</description>

<properties>
<opentracing.version>0.32.0</opentracing.version>
</properties>

<dependencies>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-core</artifactId>
</dependency>
<dependency>
<groupId>io.opentracing</groupId>
<artifactId>opentracing-api</artifactId>
<version>${opentracing.version}</version>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-failsafe</artifactId>
<!-- Needed for retry tags -->
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.opentracing</groupId>
<artifactId>opentracing-mock</artifactId>
<version>${opentracing.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.rest-driver</groupId>
<artifactId>rest-client-driver</artifactId>
</dependency>
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-concurrent</artifactId>
<version>0.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -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() {

}

}
Loading

0 comments on commit fced0a5

Please sign in to comment.