Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenTracing support #595

Merged
merged 1 commit into from
Apr 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copy-pasta

[![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.
whiskeysierra marked this conversation as resolved.
Show resolved Hide resolved
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