Fix code with suggestions from Tyler regarding previous commit.

no longer includes the command argument if the redis command being called is AUTH since it contains sensitive information.
This commit is contained in:
Gary Huang 2018-05-25 14:15:45 -04:00 committed by Gary Huang
parent ae1d9d7c52
commit 989a259ec2
8 changed files with 539 additions and 447 deletions

View File

@ -1,10 +1,37 @@
apply from: "${rootDir}/gradle/java.gradle" apply from: "${rootDir}/gradle/java.gradle"
sourceCompatibility = 1.8 sourceSets {
targetCompatibility = 1.8 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 { 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') compile project(':dd-java-agent:agent-tooling')
@ -16,5 +43,15 @@ dependencies {
testCompile project(':dd-java-agent:testing') testCompile project(':dd-java-agent:testing')
testCompile group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6' 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'

View File

@ -7,27 +7,17 @@ import datadog.trace.agent.tooling.DDAdvice;
import datadog.trace.agent.tooling.DDTransformers; import datadog.trace.agent.tooling.DDTransformers;
import datadog.trace.agent.tooling.HelperInjector; import datadog.trace.agent.tooling.HelperInjector;
import datadog.trace.agent.tooling.Instrumenter; 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.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
@AutoService(Instrumenter.class) @AutoService(Instrumenter.class)
public class RedisAsyncCommandsInstrumentation extends Instrumenter.Configurable { public class RedisAsyncCommandsInstrumentation extends Instrumenter.Configurable {
private static final HelperInjector REDIS_ASYNC_HELPERS = private static final HelperInjector REDIS_ASYNC_HELPERS =
new HelperInjector(RedisAsyncCommandsInstrumentation.class.getPackage().getName() + ".RedisAsyncBiFunction"); new HelperInjector(
private static final String SERVICE_NAME = "redis"; RedisAsyncCommandsInstrumentation.class.getPackage().getName() + ".RedisAsyncBiFunction");
private static final String COMPONENT_NAME = SERVICE_NAME + "-client";
public RedisAsyncCommandsInstrumentation() { public RedisAsyncCommandsInstrumentation() {
super(SERVICE_NAME); super("redis");
} }
@Override @Override
@ -50,56 +40,4 @@ public class RedisAsyncCommandsInstrumentation extends Instrumenter.Configurable
RedisAsyncCommandsAdvice.class.getName())) RedisAsyncCommandsAdvice.class.getName()))
.asDecorator(); .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();
}
}
} }

View File

@ -7,30 +7,17 @@ import datadog.trace.agent.tooling.DDAdvice;
import datadog.trace.agent.tooling.DDTransformers; import datadog.trace.agent.tooling.DDTransformers;
import datadog.trace.agent.tooling.HelperInjector; import datadog.trace.agent.tooling.HelperInjector;
import datadog.trace.agent.tooling.Instrumenter; 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.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
@AutoService(Instrumenter.class) @AutoService(Instrumenter.class)
public final class RedisClientInstrumentation extends Instrumenter.Configurable { public final class RedisClientInstrumentation extends Instrumenter.Configurable {
private static final HelperInjector REDIS_ASYNC_HELPERS = private static final HelperInjector REDIS_ASYNC_HELPERS =
new HelperInjector(RedisAsyncCommandsInstrumentation.class.getPackage().getName() + ".RedisAsyncBiFunction"); 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";
public RedisClientInstrumentation() { public RedisClientInstrumentation() {
super(SERVICE_NAME); super("redis");
} }
@Override @Override
@ -41,9 +28,7 @@ public final class RedisClientInstrumentation extends Instrumenter.Configurable
@Override @Override
public AgentBuilder apply(final AgentBuilder agentBuilder) { public AgentBuilder apply(final AgentBuilder agentBuilder) {
return agentBuilder return agentBuilder
.type( .type(named("io.lettuce.core.RedisClient"))
named("io.lettuce.core.RedisClient")
.and(hasSuperType(named("io.lettuce.core.AbstractRedisClient"))))
.transform(DDTransformers.defaultTransformers()) .transform(DDTransformers.defaultTransformers())
.transform(REDIS_ASYNC_HELPERS) .transform(REDIS_ASYNC_HELPERS)
.transform( .transform(
@ -58,45 +43,4 @@ public final class RedisClientInstrumentation extends Instrumenter.Configurable
ConnectionFutureAdvice.class.getName())) ConnectionFutureAdvice.class.getName()))
.asDecorator(); .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();
}
}
} }

View File

@ -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();
}
}

View File

@ -6,12 +6,13 @@ import java.util.Collections;
import java.util.function.BiFunction; import java.util.function.BiFunction;
/** /**
* Callback class to close the span on an error or a success in the RedisFuture returned by * Callback class to close the span on an error or a success in the RedisFuture returned by the
* the lettuce async API * lettuce async API
*
* @param <T> the normal completion result * @param <T> the normal completion result
* @param <U> the error * @param <U> the error
* @param <R> the return type, should be null since nothing else should happen from tracing standpoint * @param <R> the return type, should be null since nothing else should happen from tracing
* after the span is closed * standpoint after the span is closed
*/ */
public class RedisAsyncBiFunction<T extends Object, U extends Throwable, R extends Object> public class RedisAsyncBiFunction<T extends Object, U extends Throwable, R extends Object>
implements BiFunction<T, Throwable, R> { implements BiFunction<T, Throwable, R> {

View File

@ -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();
}
}

View File

@ -1,8 +1,8 @@
import datadog.trace.agent.test.AgentTestRunner import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.instrumentation.lettuce.RedisAsyncCommandsInstrumentation import datadog.trace.agent.test.TestUtils
import datadog.trace.instrumentation.lettuce.RedisClientInstrumentation
import io.lettuce.core.ConnectionFuture import io.lettuce.core.ConnectionFuture
import io.lettuce.core.RedisClient import io.lettuce.core.RedisClient
import io.lettuce.core.RedisConnectionException
import io.lettuce.core.RedisFuture import io.lettuce.core.RedisFuture
import io.lettuce.core.RedisURI import io.lettuce.core.RedisURI
import io.lettuce.core.api.StatefulConnection import io.lettuce.core.api.StatefulConnection
@ -18,6 +18,9 @@ import java.util.function.BiFunction
import java.util.function.Consumer import java.util.function.Consumer
import java.util.function.Function 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 { class LettuceAsyncClientTest extends AgentTestRunner {
static { static {
@ -26,8 +29,8 @@ class LettuceAsyncClientTest extends AgentTestRunner {
@Shared @Shared
public static final String HOST = "127.0.0.1" 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 = 9999 public static final int INCORRECT_PORT = TestUtils.randomOpenPort()
public static final int DB_INDEX = 0 public static final int DB_INDEX = 0
@Shared @Shared
public static final String DB_ADDR = HOST + ":" + PORT + "/" + DB_INDEX public static final String DB_ADDR = HOST + ":" + PORT + "/" + DB_INDEX
@ -69,10 +72,6 @@ class LettuceAsyncClientTest extends AgentTestRunner {
redisServer.stop() redisServer.stop()
} }
def setup() {
TEST_WRITER.start()
}
def "connect using get on ConnectionFuture"() { def "connect using get on ConnectionFuture"() {
setup: setup:
RedisClient testConnectionClient = RedisClient.create(EMBEDDED_DB_URI) RedisClient testConnectionClient = RedisClient.create(EMBEDDED_DB_URI)
@ -83,26 +82,29 @@ class LettuceAsyncClientTest extends AgentTestRunner {
expect: expect:
connection != null 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() tags {
trace.size() == 1 defaultTags()
def span = trace[0] "component" "redis-client"
span.getServiceName() == RedisClientInstrumentation.SERVICE_NAME "db.redis.url" DB_ADDR
span.getOperationName() == "redis.query" "db.redis.dbIndex" 0
span.getType() == RedisClientInstrumentation.SERVICE_NAME "db.type" "redis"
span.getResourceName() == RedisClientInstrumentation.RESOURCE_NAME_PREFIX + DB_ADDR "peer.hostname" HOST
!span.context().getErrorFlag() "peer.port" PORT
"span.kind" "client"
def tags = span.context().tags "span.type" "redis"
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
} }
def "connect exception inside the connection future"() { def "connect exception inside the connection future"() {
@ -121,26 +123,30 @@ class LettuceAsyncClientTest extends AgentTestRunner {
expect: expect:
TEST_WRITER.waitForTraces(1) TEST_WRITER.waitForTraces(1)
connection == null 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() tags {
trace.size() == 1 defaultTags()
def span = trace[0] "component" "redis-client"
span.getServiceName() == RedisClientInstrumentation.SERVICE_NAME "db.redis.url" DB_ADDR_NON_EXISTENT
span.getOperationName() == "redis.query" "db.redis.dbIndex" 0
span.getType() == RedisClientInstrumentation.SERVICE_NAME "db.type" "redis"
span.getResourceName() == RedisClientInstrumentation.RESOURCE_NAME_PREFIX + DB_ADDR_NON_EXISTENT errorTags(RedisConnectionException, "some error due to incorrect port number")
span.context().getErrorFlag() "peer.hostname" HOST
"peer.port" INCORRECT_PORT
def tags = span.context().tags "span.kind" "client"
tags[RedisClientInstrumentation.REDIS_URL_TAG_NAME] == DB_ADDR_NON_EXISTENT "span.type" "redis"
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
} }
def "set command using Future get with timeout"() { def "set command using Future get with timeout"() {
@ -151,24 +157,26 @@ class LettuceAsyncClientTest extends AgentTestRunner {
expect: expect:
res == "OK" res == "OK"
TEST_WRITER.size() == 1 assertTraces(TEST_WRITER, 1) {
def trace = TEST_WRITER.firstTrace() trace(0, 1) {
trace.size() == 1 span(0) {
def span = trace[0] serviceName "redis"
span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME operationName "redis.query"
span.getOperationName() == "redis.query" spanType "redis"
span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME resourceName "SET"
span.getResourceName() == "SET" errored false
!span.context().getErrorFlag()
def tags = span.context().tags tags {
tags["span.kind"] == "client" defaultTags()
tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME "component" "redis-client"
tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME "db.type" "redis"
tags["db.redis.command.args"] == "key<TESTKEY> value<TESTVAL>" "db.command.args" "key<TESTKEY> value<TESTVAL>"
tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME "span.kind" "client"
tags["thread.name"] != null "span.type" "redis"
tags["thread.id"] != null }
}
}
}
} }
def "get command chained with thenAccept"() { def "get command chained with thenAccept"() {
@ -188,27 +196,28 @@ class LettuceAsyncClientTest extends AgentTestRunner {
redisFuture.thenAccept(consumer) redisFuture.thenAccept(consumer)
then: then:
conds.await()
TEST_WRITER.waitForTraces(1) 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() tags {
trace.size() == 1 defaultTags()
def span = trace[0] "component" "redis-client"
span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "db.type" "redis"
span.getOperationName() == "redis.query" "db.command.args" "key<TESTKEY>"
span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "span.kind" "client"
span.getResourceName() == "GET" "span.type" "redis"
!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<TESTKEY>"
tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME
tags["thread.name"] != null
tags["thread.id"] != null
} }
// to make sure instrumentation's chained completion stages won't interfere with user's, while still // 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"() { def "get non existent key command with handleAsync and chained with thenApply"() {
setup: setup:
def conds = new AsyncConditions() def conds = new AsyncConditions()
final String SUCCESS = "KEY MISSING" final String successStr = "KEY MISSING"
BiFunction<String, Throwable, String> firstStage = new BiFunction<String, Throwable, String>() { BiFunction<String, Throwable, String> firstStage = new BiFunction<String, Throwable, String>() {
@Override @Override
String apply(String res, Throwable throwable) { String apply(String res, Throwable throwable) {
@ -224,14 +233,14 @@ class LettuceAsyncClientTest extends AgentTestRunner {
assert res == null assert res == null
assert throwable == null assert throwable == null
} }
return (res == null ? SUCCESS : res) return (res == null ? successStr : res)
} }
} }
Function<String, Object> secondStage = new Function<String, Object>() { Function<String, Object> secondStage = new Function<String, Object>() {
@Override @Override
Object apply(String input) { Object apply(String input) {
conds.evaluate{ conds.evaluate{
assert input == SUCCESS assert input == successStr
} }
return null return null
} }
@ -242,27 +251,29 @@ class LettuceAsyncClientTest extends AgentTestRunner {
redisFuture.handleAsync(firstStage).thenApply(secondStage) redisFuture.handleAsync(firstStage).thenApply(secondStage)
then: then:
conds.await()
TEST_WRITER.waitForTraces(1) 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() tags {
trace.size() == 1 defaultTags()
def span = trace[0] "component" "redis-client"
span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "db.type" "redis"
span.getOperationName() == "redis.query" "db.command.args" "key<NON_EXISTENT_KEY>"
span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "span.kind" "client"
span.getResourceName() == "GET" "span.type" "redis"
!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<NON_EXISTENT_KEY>"
tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME
tags["thread.name"] != null
tags["thread.id"] != null
} }
def "command with no arguments using a biconsumer"() { def "command with no arguments using a biconsumer"() {
@ -282,28 +293,27 @@ class LettuceAsyncClientTest extends AgentTestRunner {
redisFuture.whenCompleteAsync(biConsumer) redisFuture.whenCompleteAsync(biConsumer)
then: then:
conds.await()
TEST_WRITER.waitForTraces(1) 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() tags {
trace.size() == 1 defaultTags()
"component" "redis-client"
def span = trace[0] "db.type" "redis"
span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "span.kind" "client"
span.getOperationName() == "redis.query" "span.type" "redis"
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
} }
def "hash set and then nest apply to hash getall"() { def "hash set and then nest apply to hash getall"() {
@ -315,8 +325,6 @@ class LettuceAsyncClientTest extends AgentTestRunner {
hmsetFuture.thenApplyAsync(new Function<String, Object>() { hmsetFuture.thenApplyAsync(new Function<String, Object>() {
@Override @Override
Object apply(String setResult) { Object apply(String setResult) {
TEST_WRITER.waitForTraces(1)
TEST_WRITER.start()
conds.evaluate { conds.evaluate {
assert setResult == "OK" assert setResult == "OK"
} }
@ -343,25 +351,45 @@ class LettuceAsyncClientTest extends AgentTestRunner {
}) })
then: then:
TEST_WRITER.waitForTraces(2)
conds.await() conds.await()
TEST_WRITER.waitForTraces(1) assertTraces(TEST_WRITER, 2) {
def trace = TEST_WRITER.firstTrace() trace(0, 1) {
trace.size() == 1 span(0) {
serviceName "redis"
operationName "redis.query"
spanType "redis"
resourceName "HMSET"
errored false
def span = trace[0] tags {
span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME defaultTags()
span.getOperationName() == "redis.query" "component" "redis-client"
span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "db.type" "redis"
span.getResourceName() == "HGETALL" "db.command.args" "key<user> key<firstname> value<John> key<lastname> value<Doe> key<age> value<53>"
!span.context().getErrorFlag() "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 {
tags["span.kind"] == "client" defaultTags()
tags["span.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME "component" "redis-client"
tags["db.type"] == RedisAsyncCommandsInstrumentation.SERVICE_NAME "db.type" "redis"
tags["db.redis.command.args"] == "key<user>" "db.command.args" "key<user>"
tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME "span.kind" "client"
tags["thread.name"] != null "span.type" "redis"
tags["thread.id"] != null }
}
}
}
} }
} }

View File

@ -1,6 +1,5 @@
import datadog.trace.agent.test.AgentTestRunner import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.instrumentation.lettuce.RedisAsyncCommandsInstrumentation import datadog.trace.agent.test.TestUtils
import datadog.trace.instrumentation.lettuce.RedisClientInstrumentation
import io.lettuce.core.RedisClient import io.lettuce.core.RedisClient
import io.lettuce.core.RedisConnectionException import io.lettuce.core.RedisConnectionException
import io.lettuce.core.api.StatefulConnection import io.lettuce.core.api.StatefulConnection
@ -8,6 +7,9 @@ import io.lettuce.core.api.sync.RedisCommands
import redis.embedded.RedisServer import redis.embedded.RedisServer
import spock.lang.Shared 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 { class LettuceSyncClientTest extends AgentTestRunner {
static { static {
@ -16,12 +18,13 @@ class LettuceSyncClientTest extends AgentTestRunner {
@Shared @Shared
public static final String HOST = "127.0.0.1" 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 public static final int DB_INDEX = 0
@Shared @Shared
public static final String DB_ADDR = HOST + ":" + PORT + "/" + DB_INDEX public static final String DB_ADDR = HOST + ":" + PORT + "/" + DB_INDEX
@Shared @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 @Shared
public static final String DB_URI_NON_EXISTENT = "redis://" + DB_ADDR_NON_EXISTENT public static final String DB_URI_NON_EXISTENT = "redis://" + DB_ADDR_NON_EXISTENT
public static final String EMBEDDED_DB_URI = "redis://" + DB_ADDR public static final String EMBEDDED_DB_URI = "redis://" + DB_ADDR
@ -48,7 +51,6 @@ class LettuceSyncClientTest extends AgentTestRunner {
] ]
def setupSpec() { def setupSpec() {
println "Using redis: $redisServer.args"
redisServer.start() redisServer.start()
StatefulConnection connection = redisClient.connect() StatefulConnection connection = redisClient.connect()
syncCommands = connection.sync() syncCommands = connection.sync()
@ -58,10 +60,6 @@ class LettuceSyncClientTest extends AgentTestRunner {
redisServer.stop() redisServer.stop()
} }
def setup() {
TEST_WRITER.start()
}
def "connect"() { def "connect"() {
setup: setup:
RedisClient testConnectionClient = RedisClient.create(EMBEDDED_DB_URI) RedisClient testConnectionClient = RedisClient.create(EMBEDDED_DB_URI)
@ -69,26 +67,29 @@ class LettuceSyncClientTest extends AgentTestRunner {
TEST_WRITER.waitForTraces(1) TEST_WRITER.waitForTraces(1)
expect: 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() tags {
trace.size() == 1 defaultTags()
def span = trace[0] "component" "redis-client"
span.getServiceName() == RedisClientInstrumentation.SERVICE_NAME "db.redis.url" DB_ADDR
span.getOperationName() == "redis.query" "db.redis.dbIndex" 0
span.getType() == RedisClientInstrumentation.SERVICE_NAME "db.type" "redis"
span.getResourceName() == RedisClientInstrumentation.RESOURCE_NAME_PREFIX + DB_ADDR "peer.hostname" HOST
!span.context().getErrorFlag() "peer.port" PORT
"span.kind" "client"
def tags = span.context().tags "span.type" "redis"
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
} }
def "connect exception"() { def "connect exception"() {
@ -100,26 +101,30 @@ class LettuceSyncClientTest extends AgentTestRunner {
TEST_WRITER.waitForTraces(1) TEST_WRITER.waitForTraces(1)
expect: 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() tags {
trace.size() == 1 defaultTags()
def span = trace[0] "component" "redis-client"
span.getServiceName() == RedisClientInstrumentation.SERVICE_NAME "db.redis.url" DB_ADDR_NON_EXISTENT
span.getOperationName() == "redis.query" "db.redis.dbIndex" 0
span.getType() == RedisClientInstrumentation.SERVICE_NAME "db.type" "redis"
span.getResourceName() == RedisClientInstrumentation.RESOURCE_NAME_PREFIX + DB_ADDR_NON_EXISTENT errorTags(RedisConnectionException, "some error due to incorrect port number")
span.context().getErrorFlag() "peer.hostname" HOST
"peer.port" INCORRECT_PORT
def tags = span.context().tags "span.kind" "client"
tags[RedisClientInstrumentation.REDIS_URL_TAG_NAME] == DB_ADDR_NON_EXISTENT "span.type" "redis"
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
} }
def "set command"() { def "set command"() {
@ -129,26 +134,26 @@ class LettuceSyncClientTest extends AgentTestRunner {
expect: expect:
res == "OK" 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
tags {
def trace = TEST_WRITER.firstTrace() defaultTags()
trace.size() == 1 "component" "redis-client"
def span = trace[0] "db.type" "redis"
span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "db.command.args" "key<TESTKEY> value<TESTVAL>"
span.getOperationName() == "redis.query" "span.kind" "client"
span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "span.type" "redis"
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<TESTKEY> value<TESTVAL>"
tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME
tags["thread.name"] != null
tags["thread.id"] != null
} }
def "get command"() { def "get command"() {
@ -158,25 +163,26 @@ class LettuceSyncClientTest extends AgentTestRunner {
expect: expect:
res == "TESTVAL" 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() tags {
trace.size() == 1 defaultTags()
def span = trace[0] "component" "redis-client"
span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "db.type" "redis"
span.getOperationName() == "redis.query" "db.command.args" "key<TESTKEY>"
span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "span.kind" "client"
span.getResourceName() == "GET" "span.type" "redis"
!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<TESTKEY>"
tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME
tags["thread.name"] != null
tags["thread.id"] != null
} }
def "get non existent key command"() { def "get non existent key command"() {
@ -186,25 +192,26 @@ class LettuceSyncClientTest extends AgentTestRunner {
expect: expect:
res == null 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() tags {
trace.size() == 1 defaultTags()
def span = trace[0] "component" "redis-client"
span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "db.type" "redis"
span.getOperationName() == "redis.query" "db.command.args" "key<NON_EXISTENT_KEY>"
span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "span.kind" "client"
span.getResourceName() == "GET" "span.type" "redis"
!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<NON_EXISTENT_KEY>"
tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME
tags["thread.name"] != null
tags["thread.id"] != null
} }
def "command with no arguments"() { def "command with no arguments"() {
@ -214,26 +221,25 @@ class LettuceSyncClientTest extends AgentTestRunner {
expect: expect:
keyRetrieved == "TESTKEY" 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() tags {
trace.size() == 1 defaultTags()
"component" "redis-client"
def span = trace[0] "db.type" "redis"
span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "span.kind" "client"
span.getOperationName() == "redis.query" "span.type" "redis"
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
} }
def "list command"() { def "list command"() {
@ -243,24 +249,26 @@ class LettuceSyncClientTest extends AgentTestRunner {
expect: expect:
res == 1 res == 1
def trace = TEST_WRITER.firstTrace() assertTraces(TEST_WRITER, 1) {
trace.size() == 1 trace(0, 1) {
span(0) {
serviceName "redis"
operationName "redis.query"
spanType "redis"
resourceName "LPUSH"
errored false
def span = trace[0] tags {
span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME defaultTags()
span.getOperationName() == "redis.query" "component" "redis-client"
span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "db.type" "redis"
span.getResourceName() == "LPUSH" "db.command.args" "key<TESTLIST> value<TESTLIST ELEMENT>"
!span.context().getErrorFlag() "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<TESTLIST> value<TESTLIST ELEMENT>"
tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME
tags["thread.name"] != null
tags["thread.id"] != null
} }
def "hash set command"() { def "hash set command"() {
@ -270,24 +278,26 @@ class LettuceSyncClientTest extends AgentTestRunner {
expect: expect:
res == "OK" res == "OK"
def trace = TEST_WRITER.firstTrace() assertTraces(TEST_WRITER, 1) {
trace.size() == 1 trace(0, 1) {
span(0) {
serviceName "redis"
operationName "redis.query"
spanType "redis"
resourceName "HMSET"
errored false
def span = trace[0] tags {
span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME defaultTags()
span.getOperationName() == "redis.query" "component" "redis-client"
span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "db.type" "redis"
span.getResourceName() == "HMSET" "db.command.args" "key<user> key<firstname> value<John> key<lastname> value<Doe> key<age> value<53>"
!span.context().getErrorFlag() "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<user> key<firstname> value<John> key<lastname> value<Doe> key<age> value<53>"
tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME
tags["thread.name"] != null
tags["thread.id"] != null
} }
def "hash getall command"() { def "hash getall command"() {
@ -297,23 +307,25 @@ class LettuceSyncClientTest extends AgentTestRunner {
expect: expect:
res == testHashMap res == testHashMap
def trace = TEST_WRITER.firstTrace() assertTraces(TEST_WRITER, 1) {
trace.size() == 1 trace(0, 1) {
span(0) {
serviceName "redis"
operationName "redis.query"
spanType "redis"
resourceName "HGETALL"
errored false
def span = trace[0] tags {
span.getServiceName() == RedisAsyncCommandsInstrumentation.SERVICE_NAME defaultTags()
span.getOperationName() == "redis.query" "component" "redis-client"
span.getType() == RedisAsyncCommandsInstrumentation.SERVICE_NAME "db.type" "redis"
span.getResourceName() == "HGETALL" "db.command.args" "key<user>"
!span.context().getErrorFlag() "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<user>"
tags["component"] == RedisAsyncCommandsInstrumentation.COMPONENT_NAME
tags["thread.name"] != null
tags["thread.id"] != null
} }
} }