Add operation id and network tags to couchbase
MODIFY
dd-java-agent/instrumentation/couchbase-2.0/couchbase-2.0.gradle
dd-java-agent/instrumentation/couchbase-2.0/couchbase-2.0.gradle
@@ -43,6 +43,14 @@ muzzle {
}
}
+configurations {
+ testArtifacts
+}
+
+artifacts {
+ testArtifacts testJar
+}
+
dependencies {
compile project(':dd-java-agent:instrumentation:rxjava-1')
@@ -50,6 +58,8 @@ dependencies {
testCompile group: 'com.couchbase.mock', name: 'CouchbaseMock', version: '1.5.19'
+ //Test cross-compile
+ testCompile project(':dd-java-agent:instrumentation:couchbase-2.6')
testCompile group: 'org.springframework.data', name: 'spring-data-couchbase', version: '2.0.0.RELEASE'
// Earliest version that seems to allow queries with CouchbaseMock:
testCompile group: 'com.couchbase.client', name: 'java-client', version: '2.5.0'

MODIFY
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/CouchbaseAsyncClientTest.groovy
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/CouchbaseAsyncClientTest.groovy
@@ -4,6 +4,7 @@ import com.couchbase.client.java.document.JsonDocument
import com.couchbase.client.java.document.json.JsonObject
import com.couchbase.client.java.env.CouchbaseEnvironment
import com.couchbase.client.java.query.N1qlQuery
+import spock.lang.Unroll
import spock.util.concurrent.BlockingVariable
import util.AbstractCouchbaseTest
@@ -12,6 +13,7 @@ import java.util.concurrent.TimeUnit
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
+@Unroll
class CouchbaseAsyncClientTest extends AbstractCouchbaseTest {
static final int TIMEOUT = 10

MODIFY
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/CouchbaseClientTest.groovy
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/CouchbaseClientTest.groovy
@@ -5,11 +5,13 @@ import com.couchbase.client.java.document.JsonDocument
import com.couchbase.client.java.document.json.JsonObject
import com.couchbase.client.java.env.CouchbaseEnvironment
import com.couchbase.client.java.query.N1qlQuery
+import spock.lang.Unroll
import util.AbstractCouchbaseTest
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
+@Unroll
class CouchbaseClientTest extends AbstractCouchbaseTest {
def "test hasBucket #type"() {
when:

MODIFY
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/springdata/CouchbaseSpringRepositoryTest.groovy
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/springdata/CouchbaseSpringRepositoryTest.groovy
@@ -9,10 +9,12 @@ import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.data.repository.CrudRepository
import spock.lang.Shared
+import spock.lang.Unroll
import util.AbstractCouchbaseTest
+@Unroll
class CouchbaseSpringRepositoryTest extends AbstractCouchbaseTest {
- private static final Closure<Doc> FIND
+ static final Closure<Doc> FIND
static {
// This method is different in Spring Data 2+
try {

MODIFY
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/springdata/CouchbaseSpringTemplateTest.groovy
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/springdata/CouchbaseSpringTemplateTest.groovy
@@ -7,8 +7,10 @@ import com.couchbase.client.java.cluster.ClusterManager
import com.couchbase.client.java.env.CouchbaseEnvironment
import org.springframework.data.couchbase.core.CouchbaseTemplate
import spock.lang.Shared
+import spock.lang.Unroll
import util.AbstractCouchbaseTest
+@Unroll
class CouchbaseSpringTemplateTest extends AbstractCouchbaseTest {
@Shared

ADD
dd-java-agent/instrumentation/couchbase-2.6/couchbase-2.6.gradle
+// Set properties before any plugins get loaded
+ext {
+ minJavaVersionForTests = JavaVersion.VERSION_1_8
+}
+
+apply from: "${rootDir}/gradle/java.gradle"
+
+apply plugin: 'org.unbroken-dome.test-sets'
+
+testSets {
+ latestDepTest {
+ dirName = 'test'
+ }
+}
+
+muzzle {
+ // Version 2.7.5 and 2.7.8 were not released properly and muzzle cannot test against it causing failure.
+ // So we have to skip them resulting in this verbose setup.
+ fail {
+ group = 'com.couchbase.client'
+ module = 'java-client'
+ versions = "[,2.6.0)"
+ }
+ pass {
+ group = 'com.couchbase.client'
+ module = 'java-client'
+ versions = "[2.6.0,2.7.5)"
+ }
+ pass {
+ group = 'com.couchbase.client'
+ module = 'java-client'
+ versions = "[2.7.6,2.7.8)"
+ }
+ pass {
+ group = 'com.couchbase.client'
+ module = 'java-client'
+ versions = "[2.7.9,)"
+ }
+ fail {
+ group = 'com.couchbase.client'
+ module = 'couchbase-client'
+ versions = "(,)"
+ }
+}
+
+dependencies {
+ compile project(':dd-java-agent:instrumentation:rxjava-1')
+
+ compileOnly group: 'com.couchbase.client', name: 'java-client', version: '2.6.0'
+
+ testCompile project(':dd-java-agent:instrumentation:couchbase-2.0')
+ testCompile project(path: ':dd-java-agent:instrumentation:couchbase-2.0', configuration: 'testArtifacts')
+ testCompile group: 'com.couchbase.mock', name: 'CouchbaseMock', version: '1.5.19'
+
+ testCompile group: 'org.springframework.data', name: 'spring-data-couchbase', version: '3.1.0.RELEASE'
+
+ testCompile group: 'com.couchbase.client', name: 'java-client', version: '2.6.0'
+ testCompile group: 'com.couchbase.client', name: 'encryption', version: '1.0.0'
+
+ latestDepTestCompile group: 'org.springframework.data', name: 'spring-data-couchbase', version: '3.1+'
+ latestDepTestCompile group: 'com.couchbase.client', name: 'java-client', version: '2.6+'
+ latestDepTestCompile group: 'com.couchbase.client', name: 'encryption', version: '+'
+}

ADD
dd-java-agent/instrumentation/couchbase-2.6/src/main/java/datadog/trace/instrumentation/couchbase/client/CouchbaseCoreInstrumentation.java
+package datadog.trace.instrumentation.couchbase.client;
+
+import static java.util.Collections.singletonMap;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.isPublic;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import com.couchbase.client.core.message.CouchbaseRequest;
+import com.couchbase.client.java.transcoder.crypto.JsonCryptoTranscoder;
+import com.google.auto.service.AutoService;
+import datadog.trace.agent.tooling.Instrumenter;
+import datadog.trace.bootstrap.ContextStore;
+import datadog.trace.bootstrap.InstrumentationContext;
+import io.opentracing.Scope;
+import io.opentracing.Span;
+import io.opentracing.util.GlobalTracer;
+import java.util.Collections;
+import java.util.Map;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+@AutoService(Instrumenter.class)
+public class CouchbaseCoreInstrumentation extends Instrumenter.Default {
+
+ public CouchbaseCoreInstrumentation() {
+ super("couchbase");
+ }
+
+ @Override
+ public ElementMatcher<? super TypeDescription> typeMatcher() {
+ return named("com.couchbase.client.core.CouchbaseCore");
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {packageName + ".CouchbaseRequestState"};
+ }
+
+ @Override
+ public Map<String, String> contextStore() {
+ return Collections.singletonMap(
+ "com.couchbase.client.core.message.CouchbaseRequest",
+ CouchbaseRequestState.class.getName());
+ }
+
+ @Override
+ public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
+ return singletonMap(
+ isMethod()
+ .and(isPublic())
+ .and(takesArgument(0, named("com.couchbase.client.core.message.CouchbaseRequest")))
+ .and(named("send")),
+ CouchbaseCoreAdvice.class.getName());
+ }
+
+ public static class CouchbaseCoreAdvice {
+
+ @Advice.OnMethodExit(suppress = Throwable.class)
+ public static void addOperationIdToSpan(@Advice.Argument(0) final CouchbaseRequest request) {
+
+ final Scope scope = GlobalTracer.get().scopeManager().active();
+ if (scope != null) {
+ // The scope from the initial rxJava subscribe is not available to the networking layer
+ // To transfer the span, the span is added to the context store
+
+ final ContextStore<CouchbaseRequest, CouchbaseRequestState> contextStore =
+ InstrumentationContext.get(CouchbaseRequest.class, CouchbaseRequestState.class);
+
+ CouchbaseRequestState state = contextStore.get(request);
+
+ if (state == null) {
+ final Span span = GlobalTracer.get().activeSpan();
+
+ state = new CouchbaseRequestState(span);
+ contextStore.put(request, state);
+
+ span.setTag("couchbase.operation_id", request.operationId());
+ }
+ }
+ }
+
+ // 2.6.0 and above
+ public static void muzzleCheck(final JsonCryptoTranscoder transcoder) {
+ transcoder.documentType();
+ }
+ }
+}

ADD
dd-java-agent/instrumentation/couchbase-2.6/src/main/java/datadog/trace/instrumentation/couchbase/client/CouchbaseNetworkInstrumentation.java
+package datadog.trace.instrumentation.couchbase.client;
+
+import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
+import static java.util.Collections.singletonMap;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import com.couchbase.client.core.message.CouchbaseRequest;
+import com.couchbase.client.java.transcoder.crypto.JsonCryptoTranscoder;
+import com.google.auto.service.AutoService;
+import datadog.trace.agent.tooling.Instrumenter;
+import datadog.trace.bootstrap.ContextStore;
+import datadog.trace.bootstrap.InstrumentationContext;
+import io.opentracing.Span;
+import io.opentracing.tag.Tags;
+import java.util.Collections;
+import java.util.Map;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+@AutoService(Instrumenter.class)
+public class CouchbaseNetworkInstrumentation extends Instrumenter.Default {
+ public CouchbaseNetworkInstrumentation() {
+ super("couchbase");
+ }
+
+ @Override
+ public ElementMatcher<? super TypeDescription> typeMatcher() {
+ // Exact class because private fields are used
+ return safeHasSuperType(named("com.couchbase.client.core.endpoint.AbstractGenericHandler"));
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {packageName + ".CouchbaseRequestState"};
+ }
+
+ @Override
+ public Map<String, String> contextStore() {
+ return Collections.singletonMap(
+ "com.couchbase.client.core.message.CouchbaseRequest",
+ CouchbaseRequestState.class.getName());
+ }
+
+ @Override
+ public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
+ // encode(ChannelHandlerContext ctx, REQUEST msg, List<Object> out)
+ return singletonMap(
+ isMethod()
+ .and(takesArguments(3))
+ .and(
+ takesArgument(
+ 0, named("com.couchbase.client.deps.io.netty.channel.ChannelHandlerContext")))
+ .and(takesArgument(2, named("java.util.List")))
+ .and(named("encode")),
+ CouchbaseNetworkAdvice.class.getName());
+ }
+
+ public static class CouchbaseNetworkAdvice {
+ @Advice.OnMethodExit(suppress = Throwable.class)
+ public static void addNetworkTagsToSpan(
+ @Advice.FieldValue("remoteHostname") final String remoteHostname,
+ @Advice.FieldValue("remoteSocket") final String remoteSocket,
+ @Advice.FieldValue("localSocket") final String localSocket,
+ @Advice.Argument(1) final CouchbaseRequest request) {
+ final ContextStore<CouchbaseRequest, CouchbaseRequestState> contextStore =
+ InstrumentationContext.get(CouchbaseRequest.class, CouchbaseRequestState.class);
+
+ final CouchbaseRequestState state = contextStore.get(request);
+ if (state != null) {
+ final Span span = state.getSpan();
+
+ Tags.PEER_HOSTNAME.set(span, remoteHostname);
+
+ final int splitIndex = remoteSocket.lastIndexOf(":");
+ if (splitIndex != -1) {
+ Tags.PEER_PORT.set(span, Integer.valueOf(remoteSocket.substring(splitIndex + 1)));
+ }
+
+ span.setTag("local.address", localSocket);
+ }
+ }
+
+ // 2.6.0 and above
+ public static void muzzleCheck(final JsonCryptoTranscoder transcoder) {
+ transcoder.documentType();
+ }
+ }
+}

ADD
dd-java-agent/instrumentation/couchbase-2.6/src/main/java/datadog/trace/instrumentation/couchbase/client/CouchbaseRequestState.java
+package datadog.trace.instrumentation.couchbase.client;
+
+import io.opentracing.Span;
+import lombok.Data;
+import lombok.NonNull;
+
+@Data
+public class CouchbaseRequestState {
+ @NonNull public Span span;
+}

ADD
dd-java-agent/instrumentation/couchbase-2.6/src/test/groovy/CouchbaseAsyncClient26Test.groovy
+import datadog.trace.agent.test.asserts.TraceAssert
+
+class CouchbaseAsyncClient26Test extends CouchbaseAsyncClientTest {
+
+ @Override
+ void assertCouchbaseCall(TraceAssert trace, int index, String name, String bucketName = null, Object parentSpan = null) {
+ CouchbaseSpanUtil.assertCouchbaseCall(trace, index, name, bucketName, parentSpan)
+ }
+}

ADD
dd-java-agent/instrumentation/couchbase-2.6/src/test/groovy/CouchbaseClient26Test.groovy
+import datadog.trace.agent.test.asserts.TraceAssert
+
+class CouchbaseClient26Test extends CouchbaseClientTest {
+ @Override
+ void assertCouchbaseCall(TraceAssert trace, int index, String name, String bucketName = null, Object parentSpan = null) {
+ CouchbaseSpanUtil.assertCouchbaseCall(trace, index, name, bucketName, parentSpan)
+ }
+}

ADD
dd-java-agent/instrumentation/couchbase-2.6/src/test/groovy/CouchbaseSpanUtil.groovy
+import datadog.opentracing.DDSpan
+import datadog.trace.agent.test.asserts.TraceAssert
+import datadog.trace.api.DDSpanTypes
+import io.opentracing.tag.Tags
+
+class CouchbaseSpanUtil {
+ // Reusable span assertion method. Cannot directly override AbstractCouchbaseTest.assertCouchbaseSpan because
+ // Of the class hierarchy of these tests
+ static void assertCouchbaseCall(TraceAssert trace, int index, String name, String bucketName = null, Object parentSpan = null) {
+ trace.span(index) {
+ serviceName "couchbase"
+ resourceName name
+ operationName "couchbase.call"
+ spanType DDSpanTypes.COUCHBASE
+ errored false
+ if (parentSpan == null) {
+ parent()
+ } else {
+ childOf((DDSpan) parentSpan)
+ }
+ tags {
+ "$Tags.COMPONENT.key" "couchbase-client"
+ "$Tags.DB_TYPE.key" "couchbase"
+ "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
+ if (bucketName != null) {
+ "bucket" bucketName
+ "$Tags.PEER_HOSTNAME.key" { it == "localhost" || it == "127.0.0.1" }
+ "$Tags.PEER_PORT.key" Number
+ "local.address" String
+
+ // Not all couchbase operations have operation id. Notably, 'ViewQuery's do not
+ // We assign a resourceName of 'Bucket.query' and this is shared with n1ql queries
+ // that do have operation ids
+ "couchbase.operation_id" { it == null || String }
+ }
+ defaultTags()
+ }
+ }
+ }
+}

ADD
dd-java-agent/instrumentation/couchbase-2.6/src/test/groovy/springdata/CouchbaseSpringRepository26Test.groovy
+package springdata
+
+
+import datadog.trace.agent.test.asserts.TraceAssert
+
+class CouchbaseSpringRepository26Test extends CouchbaseSpringRepositoryTest {
+ @Override
+ void assertCouchbaseCall(TraceAssert trace, int index, String name, String bucketName = null, Object parentSpan = null) {
+ CouchbaseSpanUtil.assertCouchbaseCall(trace, index, name, bucketName, parentSpan)
+ }
+}

ADD
dd-java-agent/instrumentation/couchbase-2.6/src/test/groovy/springdata/CouchbaseSpringTemplate26Test.groovy
+package springdata
+
+import datadog.trace.agent.test.asserts.TraceAssert
+
+class CouchbaseSpringTemplate26Test extends CouchbaseSpringTemplateTest {
+ @Override
+ void assertCouchbaseCall(TraceAssert trace, int index, String name, String bucketName = null, Object parentSpan = null) {
+ CouchbaseSpanUtil.assertCouchbaseCall(trace, index, name, bucketName, parentSpan)
+ }
+}

MODIFY
settings.gradle
settings.gradle
@@ -28,6 +28,7 @@ include ':dd-java-agent:instrumentation:apache-httpclient-4'
include ':dd-java-agent:instrumentation:aws-java-sdk-1.11.0'
include ':dd-java-agent:instrumentation:aws-java-sdk-2.2'
include ':dd-java-agent:instrumentation:couchbase-2.0'
+include ':dd-java-agent:instrumentation:couchbase-2.6'
include ':dd-java-agent:instrumentation:datastax-cassandra-3'
include ':dd-java-agent:instrumentation:dropwizard'
include ':dd-java-agent:instrumentation:dropwizard:dropwizard-views'

Add operation id and network tags to couchbase