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

Adds Cassandra client and server integration #414

Closed
wants to merge 2 commits into from
Closed
Changes from 1 commit
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
Next Next commit
Adds Cassandra client and server integration
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.cassandra.driver.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 15, 2017
commit de7873cb362289561713c0ac5b44680c2ed6c3a2
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, SpanCustomizer customizer) {
super.requestTags(statement, tagger);
customizer.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.2-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,57 @@
package brave.cassandra.driver;

import brave.SpanCustomizer;
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 static zipkin.Constants.ERROR;

/**
* 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 {

/**
* Override to change what data from the statement are parsed into the span representing it. By
* default, this sets the span name to the lower-camel case type name and tags {@link
* CassandraTraceKeys#CASSANDRA_KEYSPACE} and {@link CassandraTraceKeys#CASSANDRA_QUERY} for bound
* statements.
*
* <p>If you only want to change the span name, you can override {@link #spanName(Statement)}
* instead.
*
* @see #spanName(Statement)
*/
public void request(Statement statement, SpanCustomizer customizer) {
customizer.name(spanName(statement));
String keyspace = statement.getKeyspace();
if (keyspace != null) {
customizer.tag(CassandraTraceKeys.CASSANDRA_KEYSPACE, statement.getKeyspace());
}
if (statement instanceof BoundStatement) {
customizer.tag(CassandraTraceKeys.CASSANDRA_QUERY,
((BoundStatement) statement).preparedStatement().getQueryString());
}
}

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

/** Override to parse data from the result set into the span modeling it. */
public void response(ResultSet resultSet, SpanCustomizer customizer) {
}

/**
* Override to change what data from an error are parsed into the span modeling it. Defaults to
* the throwable's message, or the simple name of the throwable's type.
*/
public void error(Throwable throwable, SpanCustomizer customizer) {
String message = throwable.getMessage();
customizer.tag(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 com.datastax.driver.core.Statement;
import javax.annotation.Nullable;

/**
* 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 com.google.auto.value.AutoValue;
import javax.annotation.Nullable;
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