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"
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'

View File

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

View File

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

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;
/**
* 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 <T> the normal completion result
* @param <U> the error
* @param <R> the return type, should be null since nothing else should happen from tracing standpoint
* after the span is closed
* @param <R> the return type, should be null since nothing else should happen from tracing
* standpoint after the span is closed
*/
public class RedisAsyncBiFunction<T extends Object, U extends Throwable, R extends Object>
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.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<TESTKEY> value<TESTVAL>"
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<TESTKEY> value<TESTVAL>"
"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<TESTKEY>"
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<TESTKEY>"
"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<String, Throwable, String> firstStage = new BiFunction<String, Throwable, String>() {
@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<String, Object> secondStage = new Function<String, Object>() {
@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<NON_EXISTENT_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<NON_EXISTENT_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<String, Object>() {
@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<user> key<firstname> value<John> key<lastname> value<Doe> key<age> 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<user>"
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<user>"
"span.kind" "client"
"span.type" "redis"
}
}
}
}
}
}

View File

@ -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<TESTKEY> value<TESTVAL>"
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<TESTKEY> value<TESTVAL>"
"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<TESTKEY>"
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<TESTKEY>"
"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<NON_EXISTENT_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<NON_EXISTENT_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<TESTLIST> value<TESTLIST ELEMENT>"
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<TESTLIST> value<TESTLIST ELEMENT>"
"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<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
tags {
defaultTags()
"component" "redis-client"
"db.type" "redis"
"db.command.args" "key<user> key<firstname> value<John> key<lastname> value<Doe> key<age> 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<user>"
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<user>"
"span.kind" "client"
"span.type" "redis"
}
}
}
}
}
}