diff --git a/dd-java-agent/instrumentation/lettuce-5/lettuce-5.gradle b/dd-java-agent/instrumentation/lettuce-5/lettuce-5.gradle index e138d1761d..3e9bf39514 100644 --- a/dd-java-agent/instrumentation/lettuce-5/lettuce-5.gradle +++ b/dd-java-agent/instrumentation/lettuce-5/lettuce-5.gradle @@ -1,10 +1,37 @@ apply from: "${rootDir}/gradle/java.gradle" -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +sourceSets { + main_java8 { + java.srcDirs "${project.projectDir}/src/main/java8" + } +} + +compileMain_java8Java { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 +} + +apply plugin: 'org.unbroken-dome.test-sets' + +testSets { + latestDepTest { + dirName = 'test' + } +} dependencies { - compileOnly group: 'io.lettuce', name: 'lettuce-core', version: '5.0.4.RELEASE' + main_java8CompileOnly group: 'io.lettuce', name: 'lettuce-core', version: '5.0.0.RELEASE' + + main_java8Compile project(':dd-java-agent:agent-tooling') + + main_java8Compile deps.bytebuddy + main_java8Compile deps.opentracing + + compileOnly sourceSets.main_java8.compileClasspath + + compile sourceSets.main_java8.output + + compileOnly group: 'io.lettuce', name: 'lettuce-core', version: '5.0.0.RELEASE' compile project(':dd-java-agent:agent-tooling') @@ -16,5 +43,15 @@ dependencies { testCompile project(':dd-java-agent:testing') testCompile group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6' - testCompile group: 'io.lettuce', name: 'lettuce-core', version: '5.0.4.RELEASE' + testCompile group: 'io.lettuce', name: 'lettuce-core', version: '5.0.0.RELEASE' } + +configurations.latestDepTestCompile { + resolutionStrategy { + force group: 'io.lettuce', name: 'lettuce-core', version: '+' + } +} + +testJava8Only += '**/LettuceAsyncClientTest.class' +testJava8Only += '**/LettuceSyncClientTest.class' + diff --git a/dd-java-agent/instrumentation/lettuce-5/src/main/java/datadog/trace/instrumentation/lettuce/RedisAsyncCommandsInstrumentation.java b/dd-java-agent/instrumentation/lettuce-5/src/main/java/datadog/trace/instrumentation/lettuce/RedisAsyncCommandsInstrumentation.java index 195e458662..4ea2da306c 100644 --- a/dd-java-agent/instrumentation/lettuce-5/src/main/java/datadog/trace/instrumentation/lettuce/RedisAsyncCommandsInstrumentation.java +++ b/dd-java-agent/instrumentation/lettuce-5/src/main/java/datadog/trace/instrumentation/lettuce/RedisAsyncCommandsInstrumentation.java @@ -7,27 +7,17 @@ import datadog.trace.agent.tooling.DDAdvice; import datadog.trace.agent.tooling.DDTransformers; import datadog.trace.agent.tooling.HelperInjector; import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.api.DDTags; -import io.lettuce.core.protocol.AsyncCommand; -import io.lettuce.core.protocol.RedisCommand; -import io.opentracing.Scope; -import io.opentracing.Span; -import io.opentracing.tag.Tags; -import io.opentracing.util.GlobalTracer; -import java.util.Collections; import net.bytebuddy.agent.builder.AgentBuilder; -import net.bytebuddy.asm.Advice; @AutoService(Instrumenter.class) public class RedisAsyncCommandsInstrumentation extends Instrumenter.Configurable { private static final HelperInjector REDIS_ASYNC_HELPERS = - new HelperInjector(RedisAsyncCommandsInstrumentation.class.getPackage().getName() + ".RedisAsyncBiFunction"); - private static final String SERVICE_NAME = "redis"; - private static final String COMPONENT_NAME = SERVICE_NAME + "-client"; + new HelperInjector( + RedisAsyncCommandsInstrumentation.class.getPackage().getName() + ".RedisAsyncBiFunction"); public RedisAsyncCommandsInstrumentation() { - super(SERVICE_NAME); + super("redis"); } @Override @@ -50,56 +40,4 @@ public class RedisAsyncCommandsInstrumentation extends Instrumenter.Configurable RedisAsyncCommandsAdvice.class.getName())) .asDecorator(); } - - public static class RedisAsyncCommandsAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static Scope startSpan(@Advice.Argument(0) final RedisCommand command) { - final Scope scope = GlobalTracer.get().buildSpan(SERVICE_NAME + ".query").startActive(false); - - final Span span = scope.span(); - Tags.DB_TYPE.set(span, SERVICE_NAME); - Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT); - Tags.COMPONENT.set(span, COMPONENT_NAME); - - String commandName = "Unknown Redis Command"; - String commandArgs = null; - if (command != null) { - // get the redis command name (i.e. GET, SET, HMSET, etc) - if (command.getType() != null) { - commandName = command.getType().name(); - } - // get the arguments passed into the redis command - if (command.getArgs() != null) { - commandArgs = command.getArgs().toCommandString(); - } - } - - span.setTag(DDTags.RESOURCE_NAME, commandName); - span.setTag("db.redis.command.args", commandArgs); - span.setTag(DDTags.SERVICE_NAME, SERVICE_NAME); - span.setTag(DDTags.SPAN_TYPE, SERVICE_NAME); - - return scope; - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void stopSpan( - @Advice.Enter final Scope scope, - @Advice.Thrown final Throwable throwable, - @Advice.Return(readOnly = false) final AsyncCommand asyncCommand) { - - if (throwable != null) { - final Span span = scope.span(); - Tags.ERROR.set(span, true); - span.log(Collections.singletonMap("error.object", throwable)); - scope.close(); - return; - } - - // close spans on error or normal completion - asyncCommand.handleAsync(new RedisAsyncBiFunction<>(scope.span())); - scope.close(); - } - } } diff --git a/dd-java-agent/instrumentation/lettuce-5/src/main/java/datadog/trace/instrumentation/lettuce/RedisClientInstrumentation.java b/dd-java-agent/instrumentation/lettuce-5/src/main/java/datadog/trace/instrumentation/lettuce/RedisClientInstrumentation.java index b0ff1e0841..07146c1e92 100644 --- a/dd-java-agent/instrumentation/lettuce-5/src/main/java/datadog/trace/instrumentation/lettuce/RedisClientInstrumentation.java +++ b/dd-java-agent/instrumentation/lettuce-5/src/main/java/datadog/trace/instrumentation/lettuce/RedisClientInstrumentation.java @@ -7,30 +7,17 @@ import datadog.trace.agent.tooling.DDAdvice; import datadog.trace.agent.tooling.DDTransformers; import datadog.trace.agent.tooling.HelperInjector; import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.api.DDTags; -import io.lettuce.core.*; -import io.opentracing.Scope; -import io.opentracing.Span; -import io.opentracing.tag.Tags; -import io.opentracing.util.GlobalTracer; -import java.util.Collections; import net.bytebuddy.agent.builder.AgentBuilder; -import net.bytebuddy.asm.Advice; @AutoService(Instrumenter.class) public final class RedisClientInstrumentation extends Instrumenter.Configurable { private static final HelperInjector REDIS_ASYNC_HELPERS = - new HelperInjector(RedisAsyncCommandsInstrumentation.class.getPackage().getName() + ".RedisAsyncBiFunction"); - - public static final String SERVICE_NAME = "redis"; - public static final String COMPONENT_NAME = SERVICE_NAME + "-client"; - public static final String RESOURCE_NAME_PREFIX = "CONNECT:"; - public static final String REDIS_URL_TAG_NAME = "db.redis.url"; - public static final String REDIS_DB_INDEX_TAG_NAME = "db.redis.dbIndex"; + new HelperInjector( + RedisAsyncCommandsInstrumentation.class.getPackage().getName() + ".RedisAsyncBiFunction"); public RedisClientInstrumentation() { - super(SERVICE_NAME); + super("redis"); } @Override @@ -41,9 +28,7 @@ public final class RedisClientInstrumentation extends Instrumenter.Configurable @Override public AgentBuilder apply(final AgentBuilder agentBuilder) { return agentBuilder - .type( - named("io.lettuce.core.RedisClient") - .and(hasSuperType(named("io.lettuce.core.AbstractRedisClient")))) + .type(named("io.lettuce.core.RedisClient")) .transform(DDTransformers.defaultTransformers()) .transform(REDIS_ASYNC_HELPERS) .transform( @@ -58,45 +43,4 @@ public final class RedisClientInstrumentation extends Instrumenter.Configurable ConnectionFutureAdvice.class.getName())) .asDecorator(); } - - public static class ConnectionFutureAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static Scope startSpan(@Advice.Argument(1) final RedisURI redisURI) { - final Scope scope = GlobalTracer.get().buildSpan(SERVICE_NAME + ".query").startActive(false); - - final Span span = scope.span(); - Tags.DB_TYPE.set(span, SERVICE_NAME); - Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT); - Tags.COMPONENT.set(span, COMPONENT_NAME); - - final String url = - redisURI.getHost() + ":" + redisURI.getPort() + "/" + redisURI.getDatabase(); - span.setTag(REDIS_URL_TAG_NAME, url); - span.setTag(REDIS_DB_INDEX_TAG_NAME, redisURI.getDatabase()); - span.setTag(DDTags.RESOURCE_NAME, RESOURCE_NAME_PREFIX + url); - span.setTag(DDTags.SERVICE_NAME, SERVICE_NAME); - span.setTag(DDTags.SPAN_TYPE, SERVICE_NAME); - - return scope; - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void stopSpan( - @Advice.Enter final Scope scope, - @Advice.Thrown final Throwable throwable, - @Advice.Return(readOnly = false) final ConnectionFuture connectionFuture) { - if (throwable != null) { - final Span span = scope.span(); - Tags.ERROR.set(span, true); - span.log(Collections.singletonMap("error.object", throwable)); - scope.close(); - return; - } - - // close spans on error or normal completion - connectionFuture.handleAsync(new RedisAsyncBiFunction<>(scope.span())); - scope.close(); - } - } } diff --git a/dd-java-agent/instrumentation/lettuce-5/src/main/java8/datadog/trace/instrumentation/lettuce/ConnectionFutureAdvice.java b/dd-java-agent/instrumentation/lettuce-5/src/main/java8/datadog/trace/instrumentation/lettuce/ConnectionFutureAdvice.java new file mode 100644 index 0000000000..623aae30b2 --- /dev/null +++ b/dd-java-agent/instrumentation/lettuce-5/src/main/java8/datadog/trace/instrumentation/lettuce/ConnectionFutureAdvice.java @@ -0,0 +1,62 @@ +package datadog.trace.instrumentation.lettuce; + +import datadog.trace.api.DDTags; +import io.lettuce.core.ConnectionFuture; +import io.lettuce.core.RedisURI; +import io.opentracing.Scope; +import io.opentracing.Span; +import io.opentracing.tag.Tags; +import io.opentracing.util.GlobalTracer; +import java.util.Collections; +import net.bytebuddy.asm.Advice; + +public class ConnectionFutureAdvice { + + public static final String SERVICE_NAME = "redis"; + public static final String COMPONENT_NAME = SERVICE_NAME + "-client"; + public static final String REDIS_URL_TAG_NAME = "db.redis.url"; + public static final String REDIS_DB_INDEX_TAG_NAME = "db.redis.dbIndex"; + public static final String RESOURCE_NAME_PREFIX = "CONNECT:"; + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static Scope startSpan(@Advice.Argument(1) final RedisURI redisURI) { + final Scope scope = GlobalTracer.get().buildSpan("redis.query").startActive(false); + + final Span span = scope.span(); + Tags.DB_TYPE.set(span, SERVICE_NAME); + Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT); + Tags.COMPONENT.set(span, COMPONENT_NAME); + + final int redisPort = redisURI.getPort(); + Tags.PEER_PORT.set(span, redisPort); + final String redisHost = redisURI.getHost(); + Tags.PEER_HOSTNAME.set(span, redisHost); + + final String url = redisHost + ":" + redisPort + "/" + redisURI.getDatabase(); + span.setTag(REDIS_URL_TAG_NAME, url); + span.setTag(REDIS_DB_INDEX_TAG_NAME, redisURI.getDatabase()); + span.setTag(DDTags.RESOURCE_NAME, RESOURCE_NAME_PREFIX + url); + span.setTag(DDTags.SERVICE_NAME, SERVICE_NAME); + span.setTag(DDTags.SPAN_TYPE, SERVICE_NAME); + + return scope; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Enter final Scope scope, + @Advice.Thrown final Throwable throwable, + @Advice.Return final ConnectionFuture connectionFuture) { + if (throwable != null) { + final Span span = scope.span(); + Tags.ERROR.set(span, true); + span.log(Collections.singletonMap("error.object", throwable)); + scope.close(); + return; + } + + // close spans on error or normal completion + connectionFuture.handleAsync(new RedisAsyncBiFunction<>(scope.span())); + scope.close(); + } +} diff --git a/dd-java-agent/instrumentation/lettuce-5/src/main/java/datadog/trace/instrumentation/lettuce/RedisAsyncBiFunction.java b/dd-java-agent/instrumentation/lettuce-5/src/main/java8/datadog/trace/instrumentation/lettuce/RedisAsyncBiFunction.java similarity index 86% rename from dd-java-agent/instrumentation/lettuce-5/src/main/java/datadog/trace/instrumentation/lettuce/RedisAsyncBiFunction.java rename to dd-java-agent/instrumentation/lettuce-5/src/main/java8/datadog/trace/instrumentation/lettuce/RedisAsyncBiFunction.java index 67173e18ab..6f52939c1b 100644 --- a/dd-java-agent/instrumentation/lettuce-5/src/main/java/datadog/trace/instrumentation/lettuce/RedisAsyncBiFunction.java +++ b/dd-java-agent/instrumentation/lettuce-5/src/main/java8/datadog/trace/instrumentation/lettuce/RedisAsyncBiFunction.java @@ -6,12 +6,13 @@ import java.util.Collections; import java.util.function.BiFunction; /** - * Callback class to close the span on an error or a success in the RedisFuture returned by - * the lettuce async API + * Callback class to close the span on an error or a success in the RedisFuture returned by the + * lettuce async API + * * @param the normal completion result * @param the error - * @param the return type, should be null since nothing else should happen from tracing standpoint - * after the span is closed + * @param the return type, should be null since nothing else should happen from tracing + * standpoint after the span is closed */ public class RedisAsyncBiFunction implements BiFunction { diff --git a/dd-java-agent/instrumentation/lettuce-5/src/main/java8/datadog/trace/instrumentation/lettuce/RedisAsyncCommandsAdvice.java b/dd-java-agent/instrumentation/lettuce-5/src/main/java8/datadog/trace/instrumentation/lettuce/RedisAsyncCommandsAdvice.java new file mode 100644 index 0000000000..56b48be6e0 --- /dev/null +++ b/dd-java-agent/instrumentation/lettuce-5/src/main/java8/datadog/trace/instrumentation/lettuce/RedisAsyncCommandsAdvice.java @@ -0,0 +1,70 @@ +package datadog.trace.instrumentation.lettuce; + +import datadog.trace.api.DDTags; +import io.lettuce.core.protocol.AsyncCommand; +import io.lettuce.core.protocol.RedisCommand; +import io.opentracing.Scope; +import io.opentracing.Span; +import io.opentracing.tag.Tags; +import io.opentracing.util.GlobalTracer; +import java.util.Collections; +import net.bytebuddy.asm.Advice; + +public class RedisAsyncCommandsAdvice { + + private static final String SERVICE_NAME = "redis"; + private static final String COMPONENT_NAME = SERVICE_NAME + "-client"; + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static Scope startSpan(@Advice.Argument(0) final RedisCommand command) { + final Scope scope = GlobalTracer.get().buildSpan(SERVICE_NAME + ".query").startActive(false); + + final Span span = scope.span(); + Tags.DB_TYPE.set(span, SERVICE_NAME); + Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT); + Tags.COMPONENT.set(span, COMPONENT_NAME); + + String commandName = "Redis Command"; + String commandArgs = null; + if (command != null) { + // get the arguments passed into the redis command + if (command.getArgs() != null) { + commandArgs = command.getArgs().toCommandString(); + } + // get the redis command name (i.e. GET, SET, HMSET, etc) + if (command.getType() != null) { + commandName = command.getType().name(); + // if it is an AUTH command, then remove the extracted command arguments since it is the password + if ("AUTH".equals(commandName)) { + commandArgs = null; + } + } + } + + span.setTag(DDTags.RESOURCE_NAME, commandName); + span.setTag("db.command.args", commandArgs); + span.setTag(DDTags.SERVICE_NAME, SERVICE_NAME); + span.setTag(DDTags.SPAN_TYPE, SERVICE_NAME); + + return scope; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Enter final Scope scope, + @Advice.Thrown final Throwable throwable, + @Advice.Return final AsyncCommand asyncCommand) { + + if (throwable != null) { + final Span span = scope.span(); + Tags.ERROR.set(span, true); + span.log(Collections.singletonMap("error.object", throwable)); + scope.close(); + return; + } + + // close spans on error or normal completion + asyncCommand.handleAsync(new RedisAsyncBiFunction<>(scope.span())); + scope.close(); + } +} diff --git a/dd-java-agent/instrumentation/lettuce-5/src/test/groovy/LettuceAsyncClientTest.groovy b/dd-java-agent/instrumentation/lettuce-5/src/test/groovy/LettuceAsyncClientTest.groovy index 79fb0105c8..356527450d 100644 --- a/dd-java-agent/instrumentation/lettuce-5/src/test/groovy/LettuceAsyncClientTest.groovy +++ b/dd-java-agent/instrumentation/lettuce-5/src/test/groovy/LettuceAsyncClientTest.groovy @@ -1,8 +1,8 @@ import datadog.trace.agent.test.AgentTestRunner -import datadog.trace.instrumentation.lettuce.RedisAsyncCommandsInstrumentation -import datadog.trace.instrumentation.lettuce.RedisClientInstrumentation +import datadog.trace.agent.test.TestUtils import io.lettuce.core.ConnectionFuture import io.lettuce.core.RedisClient +import io.lettuce.core.RedisConnectionException import io.lettuce.core.RedisFuture import io.lettuce.core.RedisURI import io.lettuce.core.api.StatefulConnection @@ -18,6 +18,9 @@ import java.util.function.BiFunction import java.util.function.Consumer import java.util.function.Function +import static datadog.trace.instrumentation.lettuce.ConnectionFutureAdvice.RESOURCE_NAME_PREFIX +import static datadog.trace.agent.test.ListWriterAssert.assertTraces + class LettuceAsyncClientTest extends AgentTestRunner { static { @@ -26,8 +29,8 @@ class LettuceAsyncClientTest extends AgentTestRunner { @Shared public static final String HOST = "127.0.0.1" - public static final int PORT = 6399 - public static final int INCORRECT_PORT = 9999 + public static final int PORT = TestUtils.randomOpenPort() + public static final int INCORRECT_PORT = TestUtils.randomOpenPort() public static final int DB_INDEX = 0 @Shared public static final String DB_ADDR = HOST + ":" + PORT + "/" + DB_INDEX @@ -69,10 +72,6 @@ class LettuceAsyncClientTest extends AgentTestRunner { redisServer.stop() } - def setup() { - TEST_WRITER.start() - } - def "connect using get on ConnectionFuture"() { setup: RedisClient testConnectionClient = RedisClient.create(EMBEDDED_DB_URI) @@ -83,26 +82,29 @@ class LettuceAsyncClientTest extends AgentTestRunner { expect: connection != null - TEST_WRITER.size() == 1 + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName RESOURCE_NAME_PREFIX + DB_ADDR + errored false - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 - def span = trace[0] - span.getServiceName() == RedisClientInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisClientInstrumentation.SERVICE_NAME - span.getResourceName() == RedisClientInstrumentation.RESOURCE_NAME_PREFIX + DB_ADDR - !span.context().getErrorFlag() - - def tags = span.context().tags - tags[RedisClientInstrumentation.REDIS_URL_TAG_NAME] == DB_ADDR - tags[RedisClientInstrumentation.REDIS_DB_INDEX_TAG_NAME] == 0 - tags["span.kind"] == "client" - tags["span.type"] == RedisClientInstrumentation.SERVICE_NAME - tags["db.type"] == RedisClientInstrumentation.SERVICE_NAME - tags["component"] == RedisClientInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.redis.url" DB_ADDR + "db.redis.dbIndex" 0 + "db.type" "redis" + "peer.hostname" HOST + "peer.port" PORT + "span.kind" "client" + "span.type" "redis" + } + } + } + } } def "connect exception inside the connection future"() { @@ -121,26 +123,30 @@ class LettuceAsyncClientTest extends AgentTestRunner { expect: TEST_WRITER.waitForTraces(1) connection == null - TEST_WRITER.size() == 1 + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName RESOURCE_NAME_PREFIX + DB_ADDR_NON_EXISTENT + errored true - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 - def span = trace[0] - span.getServiceName() == RedisClientInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisClientInstrumentation.SERVICE_NAME - span.getResourceName() == RedisClientInstrumentation.RESOURCE_NAME_PREFIX + DB_ADDR_NON_EXISTENT - span.context().getErrorFlag() - - def tags = span.context().tags - tags[RedisClientInstrumentation.REDIS_URL_TAG_NAME] == DB_ADDR_NON_EXISTENT - tags[RedisClientInstrumentation.REDIS_DB_INDEX_TAG_NAME] == 0 - tags["span.kind"] == "client" - tags["span.type"] == RedisClientInstrumentation.SERVICE_NAME - tags["db.type"] == RedisClientInstrumentation.SERVICE_NAME - tags["component"] == RedisClientInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.redis.url" DB_ADDR_NON_EXISTENT + "db.redis.dbIndex" 0 + "db.type" "redis" + errorTags(RedisConnectionException, "some error due to incorrect port number") + "peer.hostname" HOST + "peer.port" INCORRECT_PORT + "span.kind" "client" + "span.type" "redis" + } + } + } + } } def "set command using Future get with timeout"() { @@ -151,24 +157,26 @@ class LettuceAsyncClientTest extends AgentTestRunner { expect: res == "OK" - TEST_WRITER.size() == 1 - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 - def span = trace[0] - span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getResourceName() == "SET" - !span.context().getErrorFlag() + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "SET" + errored false - def tags = span.context().tags - tags["span.kind"] == "client" - tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.redis.command.args"] == "key value" - tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "db.command.args" "key value" + "span.kind" "client" + "span.type" "redis" + } + } + } + } } def "get command chained with thenAccept"() { @@ -188,27 +196,28 @@ class LettuceAsyncClientTest extends AgentTestRunner { redisFuture.thenAccept(consumer) then: - conds.await() TEST_WRITER.waitForTraces(1) - TEST_WRITER.size() == 1 + conds.await() + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "GET" + errored false - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 - def span = trace[0] - span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getResourceName() == "GET" - !span.context().getErrorFlag() - - def tags = span.context().tags - tags["span.kind"] == "client" - tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.redis.command.args"] == "key" - tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "db.command.args" "key" + "span.kind" "client" + "span.type" "redis" + } + } + } + } } // to make sure instrumentation's chained completion stages won't interfere with user's, while still @@ -216,7 +225,7 @@ class LettuceAsyncClientTest extends AgentTestRunner { def "get non existent key command with handleAsync and chained with thenApply"() { setup: def conds = new AsyncConditions() - final String SUCCESS = "KEY MISSING" + final String successStr = "KEY MISSING" BiFunction firstStage = new BiFunction() { @Override String apply(String res, Throwable throwable) { @@ -224,14 +233,14 @@ class LettuceAsyncClientTest extends AgentTestRunner { assert res == null assert throwable == null } - return (res == null ? SUCCESS : res) + return (res == null ? successStr : res) } } Function secondStage = new Function() { @Override Object apply(String input) { conds.evaluate{ - assert input == SUCCESS + assert input == successStr } return null } @@ -242,27 +251,29 @@ class LettuceAsyncClientTest extends AgentTestRunner { redisFuture.handleAsync(firstStage).thenApply(secondStage) then: - conds.await() TEST_WRITER.waitForTraces(1) - TEST_WRITER.size() == 1 + conds.await() + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "GET" + errored false - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 - def span = trace[0] - span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getResourceName() == "GET" - !span.context().getErrorFlag() + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "db.command.args" "key" + "span.kind" "client" + "span.type" "redis" + } + } + } + } - def tags = span.context().tags - tags["span.kind"] == "client" - tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.redis.command.args"] == "key" - tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null } def "command with no arguments using a biconsumer"() { @@ -282,28 +293,27 @@ class LettuceAsyncClientTest extends AgentTestRunner { redisFuture.whenCompleteAsync(biConsumer) then: - conds.await() TEST_WRITER.waitForTraces(1) - TEST_WRITER.size() == 1 + conds.await() + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "RANDOMKEY" + errored false - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 - - def span = trace[0] - span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getResourceName() == "RANDOMKEY" - !span.context().getErrorFlag() - - def tags = span.context().tags - tags["span.kind"] == "client" - tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.redis.command.args"] == null - tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "span.kind" "client" + "span.type" "redis" + } + } + } + } } def "hash set and then nest apply to hash getall"() { @@ -315,8 +325,6 @@ class LettuceAsyncClientTest extends AgentTestRunner { hmsetFuture.thenApplyAsync(new Function() { @Override Object apply(String setResult) { - TEST_WRITER.waitForTraces(1) - TEST_WRITER.start() conds.evaluate { assert setResult == "OK" } @@ -343,25 +351,45 @@ class LettuceAsyncClientTest extends AgentTestRunner { }) then: + TEST_WRITER.waitForTraces(2) conds.await() - TEST_WRITER.waitForTraces(1) - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 + assertTraces(TEST_WRITER, 2) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "HMSET" + errored false - def span = trace[0] - span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getResourceName() == "HGETALL" - !span.context().getErrorFlag() + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "db.command.args" "key key value key value key value<53>" + "span.kind" "client" + "span.type" "redis" + } + } + } + trace(1, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "HGETALL" + errored false - def tags = span.context().tags - tags["span.kind"] == "client" - tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.redis.command.args"] == "key" - tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "db.command.args" "key" + "span.kind" "client" + "span.type" "redis" + } + } + } + } } } diff --git a/dd-java-agent/instrumentation/lettuce-5/src/test/groovy/LettuceSyncClientTest.groovy b/dd-java-agent/instrumentation/lettuce-5/src/test/groovy/LettuceSyncClientTest.groovy index 6d89bb1d9f..35dfd8c19f 100644 --- a/dd-java-agent/instrumentation/lettuce-5/src/test/groovy/LettuceSyncClientTest.groovy +++ b/dd-java-agent/instrumentation/lettuce-5/src/test/groovy/LettuceSyncClientTest.groovy @@ -1,6 +1,5 @@ import datadog.trace.agent.test.AgentTestRunner -import datadog.trace.instrumentation.lettuce.RedisAsyncCommandsInstrumentation -import datadog.trace.instrumentation.lettuce.RedisClientInstrumentation +import datadog.trace.agent.test.TestUtils import io.lettuce.core.RedisClient import io.lettuce.core.RedisConnectionException import io.lettuce.core.api.StatefulConnection @@ -8,6 +7,9 @@ import io.lettuce.core.api.sync.RedisCommands import redis.embedded.RedisServer import spock.lang.Shared +import static datadog.trace.instrumentation.lettuce.ConnectionFutureAdvice.RESOURCE_NAME_PREFIX +import static datadog.trace.agent.test.ListWriterAssert.assertTraces + class LettuceSyncClientTest extends AgentTestRunner { static { @@ -16,12 +18,13 @@ class LettuceSyncClientTest extends AgentTestRunner { @Shared public static final String HOST = "127.0.0.1" - public static final int PORT = 6399 + public static final int PORT = TestUtils.randomOpenPort() + public static final int INCORRECT_PORT = TestUtils.randomOpenPort() public static final int DB_INDEX = 0 @Shared public static final String DB_ADDR = HOST + ":" + PORT + "/" + DB_INDEX @Shared - public static final String DB_ADDR_NON_EXISTENT = HOST + ":" + 9999 + "/" + DB_INDEX + public static final String DB_ADDR_NON_EXISTENT = HOST + ":" + INCORRECT_PORT + "/" + DB_INDEX @Shared public static final String DB_URI_NON_EXISTENT = "redis://" + DB_ADDR_NON_EXISTENT public static final String EMBEDDED_DB_URI = "redis://" + DB_ADDR @@ -48,7 +51,6 @@ class LettuceSyncClientTest extends AgentTestRunner { ] def setupSpec() { - println "Using redis: $redisServer.args" redisServer.start() StatefulConnection connection = redisClient.connect() syncCommands = connection.sync() @@ -58,10 +60,6 @@ class LettuceSyncClientTest extends AgentTestRunner { redisServer.stop() } - def setup() { - TEST_WRITER.start() - } - def "connect"() { setup: RedisClient testConnectionClient = RedisClient.create(EMBEDDED_DB_URI) @@ -69,26 +67,29 @@ class LettuceSyncClientTest extends AgentTestRunner { TEST_WRITER.waitForTraces(1) expect: - TEST_WRITER.size() == 1 + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName RESOURCE_NAME_PREFIX + DB_ADDR + errored false - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 - def span = trace[0] - span.getServiceName() == RedisClientInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisClientInstrumentation.SERVICE_NAME - span.getResourceName() == RedisClientInstrumentation.RESOURCE_NAME_PREFIX + DB_ADDR - !span.context().getErrorFlag() - - def tags = span.context().tags - tags[RedisClientInstrumentation.REDIS_URL_TAG_NAME] == DB_ADDR - tags[RedisClientInstrumentation.REDIS_DB_INDEX_TAG_NAME] == 0 - tags["span.kind"] == "client" - tags["span.type"] == RedisClientInstrumentation.SERVICE_NAME - tags["db.type"] == RedisClientInstrumentation.SERVICE_NAME - tags["component"] == RedisClientInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.redis.url" DB_ADDR + "db.redis.dbIndex" 0 + "db.type" "redis" + "peer.hostname" HOST + "peer.port" PORT + "span.kind" "client" + "span.type" "redis" + } + } + } + } } def "connect exception"() { @@ -100,26 +101,30 @@ class LettuceSyncClientTest extends AgentTestRunner { TEST_WRITER.waitForTraces(1) expect: - TEST_WRITER.size() == 1 + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName RESOURCE_NAME_PREFIX + DB_ADDR_NON_EXISTENT + errored true - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 - def span = trace[0] - span.getServiceName() == RedisClientInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisClientInstrumentation.SERVICE_NAME - span.getResourceName() == RedisClientInstrumentation.RESOURCE_NAME_PREFIX + DB_ADDR_NON_EXISTENT - span.context().getErrorFlag() - - def tags = span.context().tags - tags[RedisClientInstrumentation.REDIS_URL_TAG_NAME] == DB_ADDR_NON_EXISTENT - tags[RedisClientInstrumentation.REDIS_DB_INDEX_TAG_NAME] == 0 - tags["span.kind"] == "client" - tags["span.type"] == RedisClientInstrumentation.SERVICE_NAME - tags["db.type"] == RedisClientInstrumentation.SERVICE_NAME - tags["component"] == RedisClientInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.redis.url" DB_ADDR_NON_EXISTENT + "db.redis.dbIndex" 0 + "db.type" "redis" + errorTags(RedisConnectionException, "some error due to incorrect port number") + "peer.hostname" HOST + "peer.port" INCORRECT_PORT + "span.kind" "client" + "span.type" "redis" + } + } + } + } } def "set command"() { @@ -129,26 +134,26 @@ class LettuceSyncClientTest extends AgentTestRunner { expect: res == "OK" - TEST_WRITER.size() == 1 + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "SET" + errored false - - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 - def span = trace[0] - span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getResourceName() == "SET" - !span.context().getErrorFlag() - - def tags = span.context().tags - tags["span.kind"] == "client" - tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.redis.command.args"] == "key value" - tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "db.command.args" "key value" + "span.kind" "client" + "span.type" "redis" + } + } + } + } } def "get command"() { @@ -158,25 +163,26 @@ class LettuceSyncClientTest extends AgentTestRunner { expect: res == "TESTVAL" - TEST_WRITER.size() == 1 + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "GET" + errored false - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 - def span = trace[0] - span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getResourceName() == "GET" - !span.context().getErrorFlag() - - def tags = span.context().tags - tags["span.kind"] == "client" - tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.redis.command.args"] == "key" - tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "db.command.args" "key" + "span.kind" "client" + "span.type" "redis" + } + } + } + } } def "get non existent key command"() { @@ -186,25 +192,26 @@ class LettuceSyncClientTest extends AgentTestRunner { expect: res == null - TEST_WRITER.size() == 1 + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "GET" + errored false - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 - def span = trace[0] - span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getResourceName() == "GET" - !span.context().getErrorFlag() - - def tags = span.context().tags - tags["span.kind"] == "client" - tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.redis.command.args"] == "key" - tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "db.command.args" "key" + "span.kind" "client" + "span.type" "redis" + } + } + } + } } def "command with no arguments"() { @@ -214,26 +221,25 @@ class LettuceSyncClientTest extends AgentTestRunner { expect: keyRetrieved == "TESTKEY" - TEST_WRITER.size() == 1 + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "RANDOMKEY" + errored false - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 - - def span = trace[0] - span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getResourceName() == "RANDOMKEY" - !span.context().getErrorFlag() - - def tags = span.context().tags - tags["span.kind"] == "client" - tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.redis.command.args"] == null - tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "span.kind" "client" + "span.type" "redis" + } + } + } + } } def "list command"() { @@ -243,24 +249,26 @@ class LettuceSyncClientTest extends AgentTestRunner { expect: res == 1 - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "LPUSH" + errored false - def span = trace[0] - span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getResourceName() == "LPUSH" - !span.context().getErrorFlag() - - def tags = span.context().tags - tags["span.kind"] == "client" - tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.redis.command.args"] == "key value" - tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "db.command.args" "key value" + "span.kind" "client" + "span.type" "redis" + } + } + } + } } def "hash set command"() { @@ -270,24 +278,26 @@ class LettuceSyncClientTest extends AgentTestRunner { expect: res == "OK" - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "HMSET" + errored false - def span = trace[0] - span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getResourceName() == "HMSET" - !span.context().getErrorFlag() - - def tags = span.context().tags - tags["span.kind"] == "client" - tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.redis.command.args"] == "key key value key value key value<53>" - tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "db.command.args" "key key value key value key value<53>" + "span.kind" "client" + "span.type" "redis" + } + } + } + } } def "hash getall command"() { @@ -297,23 +307,25 @@ class LettuceSyncClientTest extends AgentTestRunner { expect: res == testHashMap - def trace = TEST_WRITER.firstTrace() - trace.size() == 1 + assertTraces(TEST_WRITER, 1) { + trace(0, 1) { + span(0) { + serviceName "redis" + operationName "redis.query" + spanType "redis" + resourceName "HGETALL" + errored false - def span = trace[0] - span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getOperationName() == "redis.query" - span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME - span.getResourceName() == "HGETALL" - !span.context().getErrorFlag() - - def tags = span.context().tags - tags["span.kind"] == "client" - tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME - tags["db.redis.command.args"] == "key" - tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME - tags["thread.name"] != null - tags["thread.id"] != null + tags { + defaultTags() + "component" "redis-client" + "db.type" "redis" + "db.command.args" "key" + "span.kind" "client" + "span.type" "redis" + } + } + } + } } }