Skip to content

Commit

Permalink
Adds Cassandra client and server integration
Browse files Browse the repository at this point in the history
This contains tracing instrumentation for [Cassandra](https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/tracing/Tracing.java) and the [DataStax Java Driver](https://github.com/datastax/java-driver).

`brave.cassandra.Tracing` extracts trace state from the custom payload
of incoming requests. How long each request takes, each suboperation,
and relevant tags like the session ID are reported to Zipkin.

`brave.datastax.TracingSession` tracks the client-side of cassandra and
adds trace context to the custom payload of outgoing requests. If
server integration is in place, cassandra will contribute data to these
RPC spans.
  • Loading branch information
Adrian Cole committed May 13, 2017
1 parent 629a7da commit e6e3cc5
Show file tree
Hide file tree
Showing 20 changed files with 1,978 additions and 0 deletions.
61 changes: 61 additions & 0 deletions instrumentation/cassandra-driver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# brave-instrumentation-cassandra-driver
This contains tracing instrumentation for the [DataStax Java Driver](https://github.com/datastax/java-driver).

`brave.cassandra.driver.TracingSession` tracks the client-side of cassandra
and adds trace context to the custom payload of outgoing requests. If
[server integration](../cassandra) is in place, cassandra will contribute
data to these RPC spans.

To set this up, wrap your session like below
```java
session = TracingSession.create(tracing, realSession);
```


## Tagging policy
By default, the following are added to cassandra client spans:
* Span.name as the simple type-name of the statement: ex "bound-statement"
* Tags/binary annotations:
* "cassandra.keyspace"
* "cassandra.query" CQL of prepared statements
* "error" when there is an error of any kind
* Remote IP and port information

To change the span and tag naming policy, you can do something like this:

```java
cassandraDriverTracing = cassandraDriverTracing.toBuilder()
.parser(new CassandraParser() {
@Override public String spanName(Statement statement) {
return "query";
}

@Override public void requestTags(Statement statement, Tagger tagger) {
super.requestTags(statement, tagger);
tagger.tag("cassandra.fetch_size", Integer.toString(statement.getFetchSize()));
}
})
.build();

tracesSession = TracingSession.create(cassandraDriverTracing.clientOf("remote-cluster"), session);
```

## Sampling Policy
The default sampling policy is to use the default (trace ID) sampler.

For example, if there's no trace already in progress, the sampler
indicated by `Tracing.Builder.sampler` decides whether or not to start a
new trace for the cassandra client request.

You can change the sampling policy by specifying it in the `CassandraDriverTracing`
component. Here's an example which only starts new traces for bound statements.

```java
cassandraDriverTracing = cassandraDriverTracing.toBuilder()
.sampler(new CassandraDriverSampler() {
@Override public Boolean trySample(Statement statement) {
return statement instanceof BoundStatement;
}
})
.build();
```
38 changes: 38 additions & 0 deletions instrumentation/cassandra-driver/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0"?>
<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">
<parent>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-parent</artifactId>
<version>4.3.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>brave-instrumentation-cassandra-driver</artifactId>
<name>Brave Instrumentation: DataStax Java Driver for Apache Cassandra</name>

<properties>
<main.basedir>${project.basedir}/../..</main.basedir>
</properties>

<dependencies>
<!-- for value types... don't worry. this dependency is compile only! -->
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.datastax.cassandra</groupId>
<artifactId>cassandra-driver-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>brave-instrumentation-cassandra-tests</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package brave.cassandra.driver;

import brave.Tagger;
import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Statement;
import com.google.common.base.CaseFormat;
import zipkin.Constants;

/**
* Provides reasonable defaults for the data contained in cassandra client spans. Subclass to
* customize, for example, to add tags based on response headers.
*/
public class CassandraDriverParser {

/** Returns the span name of the statement. Defaults to the lower-camel case type name. */
public String spanName(Statement statement) {
return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, statement.getClass().getSimpleName());
}

/**
* Adds any tags based on the statement that will be sent to the server.
*
* <p>By default, this adds the {@link CassandraTraceKeys#CASSANDRA_KEYSPACE} and the {@link
* CassandraTraceKeys#CASSANDRA_QUERY} for bound statements.
*/
public void requestTags(Statement statement, Tagger tagger) {
String keyspace = statement.getKeyspace();
if (keyspace != null) {
tagger.tag(CassandraTraceKeys.CASSANDRA_KEYSPACE, statement.getKeyspace());
}
if (statement instanceof BoundStatement) {
tagger.tag(CassandraTraceKeys.CASSANDRA_QUERY,
((BoundStatement) statement).preparedStatement().getQueryString());
}
}

/** Adds any tags based on the response received from the server. No default. */
public void responseTags(ResultSet resultSet, Tagger tagger) {
}

/**
* Adds an {@link Constants#ERROR error tag} for a failed request. Defaults to the throwable's
* message, or the simple name of the throwable's type.
*
* @see Constants#ERROR
*/
// This says error tags for consistency eventhough we only add one
public void errorTags(Throwable throwable, Tagger tagger) {
String message = throwable.getMessage();
tagger.tag(Constants.ERROR, message != null ? message : throwable.getClass().getSimpleName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package brave.cassandra.driver;

import brave.internal.Nullable;
import com.datastax.driver.core.Statement;

/**
* Decides whether to start a new trace based on the cassandra statement.
*
* <p>Ex. Here's a sampler that only starts traces for bound statements
* <pre>{@code
* cassandraDriverTracingBuilder.serverSampler(new CassandraDriverSampler() {
* @Override public <Req> Boolean trySample(Statement statement) {
* return statement instanceof BoundStatement;
* }
* });
* }</pre>
*/
// abstract class as it lets us make helpers in the future
public abstract class CassandraDriverSampler {
/** Ignores the request and uses the {@link brave.sampler.Sampler trace ID instead}. */
public static final CassandraDriverSampler TRACE_ID = new CassandraDriverSampler() {
@Override public Boolean trySample(Statement statement) {
return null;
}

@Override public String toString() {
return "DeferDecision";
}
};
/** Returns false to never start new traces for cassandra client requests. */
public static final CassandraDriverSampler NEVER_SAMPLE = new CassandraDriverSampler() {
@Override public Boolean trySample(Statement statement) {
return false;
}

@Override public String toString() {
return "NeverSample";
}
};

/**
* Returns an overriding sampling decision for a new trace. Return null ignore the statement and
* use the {@link brave.sampler.Sampler trace ID sampler}.
*/
@Nullable public abstract Boolean trySample(Statement statement);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package brave.cassandra.driver;

import brave.Tracing;
import brave.internal.Nullable;
import com.google.auto.value.AutoValue;
import zipkin.Endpoint;

@AutoValue
public abstract class CassandraDriverTracing {
public static CassandraDriverTracing create(Tracing tracing) {
return newBuilder(tracing).build();
}

public static Builder newBuilder(Tracing tracing) {
return new AutoValue_CassandraDriverTracing.Builder()
.tracing(tracing)
.parser(new CassandraDriverParser())
.sampler(CassandraDriverSampler.TRACE_ID);
}

public abstract Tracing tracing();

public abstract CassandraDriverParser parser();

/**
* Used by cassandra clients to indicate the name of the destination service. Defaults to the
* cluster name.
*
* <p>As this is endpoint-specific, it is typical to create a scoped instance of {@linkplain
* CassandraDriverTracing} to assign this value.
*
* For example:
* <pre>{@code
* production = TracingSession.create(httpTracing.remoteServiceName("production"));
* }</pre>
*
* @see zipkin.Constants#SERVER_ADDR
* @see brave.Span#remoteEndpoint(Endpoint)
*/
@Nullable public abstract String remoteServiceName();

/**
* Scopes this component for a client of the indicated server.
*
* @see #remoteServiceName()
*/
public CassandraDriverTracing clientOf(String remoteServiceName) {
return toBuilder().remoteServiceName(remoteServiceName).build();
}

/**
* Returns an overriding sampling decision for a new trace. Defaults to ignore the request and use
* the {@link CassandraDriverSampler#TRACE_ID trace ID instead}.
*/
public abstract CassandraDriverSampler sampler();

public abstract Builder toBuilder();

@AutoValue.Builder
public static abstract class Builder {
/** @see CassandraDriverTracing#tracing() */
public abstract Builder tracing(Tracing tracing);

/** @see CassandraDriverTracing#parser() */
public abstract Builder parser(CassandraDriverParser parser);

/** @see CassandraDriverTracing#sampler() */
public abstract Builder sampler(CassandraDriverSampler sampler);

public abstract CassandraDriverTracing build();

abstract Builder remoteServiceName(@Nullable String remoteServiceName);

Builder() {
}
}

CassandraDriverTracing() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package brave.cassandra.driver;

public final class CassandraTraceKeys {
public static final String CASSANDRA_KEYSPACE = "cassandra.keyspace";

/**
* The CQL query in a statement. Ex. "select * from customers where id = ?"
*
* <p>Used to understand the complexity of a request
*/
public static final String CASSANDRA_QUERY = "cassandra.query";

private CassandraTraceKeys() {
}
}
Loading

0 comments on commit e6e3cc5

Please sign in to comment.