get lettuce 4.0 sync and async working
This commit is contained in:
parent
63ea57e7ca
commit
3f8bc96e8d
|
@ -0,0 +1,34 @@
|
|||
// Set properties before any plugins get loaded
|
||||
ext {
|
||||
minJavaVersionForTests = JavaVersion.VERSION_1_8
|
||||
maxJavaVersionForTests = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
muzzle {
|
||||
pass {
|
||||
group = "biz.paluch.redis"
|
||||
module = "lettuce"
|
||||
versions = "[4.0.Final,4.5.0.Final]"
|
||||
assertInverse = true
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
apply plugin: 'org.unbroken-dome.test-sets'
|
||||
|
||||
testSets {
|
||||
latestDepTest {
|
||||
dirName = 'test'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'biz.paluch.redis', name: 'lettuce', version: '4.0.Final'
|
||||
main_java8CompileOnly group: 'biz.paluch.redis', name: 'lettuce', version: '4.5.0.Final'
|
||||
|
||||
testCompile group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6'
|
||||
testCompile group: 'biz.paluch.redis', name: 'lettuce', version: '4.0.Final'
|
||||
|
||||
latestDepTestCompile group: 'biz.paluch.redis', name: 'lettuce', version: '4.+'
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package datadog.trace.instrumentation.lettuce;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
import java.util.Map;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
@AutoService(Instrumenter.class)
|
||||
public class LettuceAsyncCommandsInstrumentation extends Instrumenter.Default {
|
||||
|
||||
public LettuceAsyncCommandsInstrumentation() {
|
||||
super("lettuce", "lettuce-4-async");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return named("com.lambdaworks.redis.AbstractRedisAsyncCommands");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] helperClassNames() {
|
||||
return new String[] {
|
||||
packageName + ".LettuceClientDecorator",
|
||||
packageName + ".LettuceAsyncBiFunction",
|
||||
packageName + ".LettuceInstrumentationUtil"
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
return singletonMap(
|
||||
isMethod()
|
||||
.and(named("dispatch"))
|
||||
.and(takesArgument(0, named("com.lambdaworks.redis.protocol.RedisCommand"))),
|
||||
// Cannot reference class directly here because it would lead to class load failure on Java7
|
||||
packageName + ".LettuceAsyncCommandsAdvice");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package datadog.trace.instrumentation.lettuce;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
import java.util.Map;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
@AutoService(Instrumenter.class)
|
||||
public final class LettuceClientInstrumentation extends Instrumenter.Default {
|
||||
|
||||
public LettuceClientInstrumentation() {
|
||||
super("lettuce");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return named("com.lambdaworks.redis.RedisClient");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] helperClassNames() {
|
||||
return new String[] {
|
||||
packageName + ".LettuceClientDecorator", packageName + ".LettuceInstrumentationUtil"
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
return singletonMap(
|
||||
isMethod().and(named("connectStandalone")),
|
||||
// Cannot reference class directly here because it would lead to class load failure on Java7
|
||||
packageName + ".RedisConnectionAdvice");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package datadog.trace.instrumentation.lettuce;
|
||||
|
||||
import static datadog.trace.instrumentation.lettuce.LettuceClientDecorator.DECORATE;
|
||||
|
||||
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
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
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
public class LettuceAsyncBiFunction<T extends Object, U extends Throwable, R extends Object>
|
||||
implements BiFunction<T, Throwable, R> {
|
||||
|
||||
private final AgentSpan span;
|
||||
|
||||
public LettuceAsyncBiFunction(final AgentSpan span) {
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R apply(final T t, final Throwable throwable) {
|
||||
if (throwable instanceof CancellationException) {
|
||||
span.setTag("db.command.cancelled", true);
|
||||
} else {
|
||||
DECORATE.onError(span, throwable);
|
||||
}
|
||||
DECORATE.beforeFinish(span);
|
||||
span.finish();
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package datadog.trace.instrumentation.lettuce;
|
||||
|
||||
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
|
||||
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
|
||||
import static datadog.trace.instrumentation.lettuce.LettuceClientDecorator.DECORATE;
|
||||
import static datadog.trace.instrumentation.lettuce.LettuceInstrumentationUtil.doFinishSpanEarly;
|
||||
|
||||
import com.lambdaworks.redis.protocol.AsyncCommand;
|
||||
import com.lambdaworks.redis.protocol.RedisCommand;
|
||||
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
|
||||
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
||||
public class LettuceAsyncCommandsAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static AgentScope onEnter(@Advice.Argument(0) final RedisCommand command) {
|
||||
final AgentSpan span = startSpan("redis.query");
|
||||
DECORATE.afterStart(span);
|
||||
DECORATE.onCommand(span, command);
|
||||
return activateSpan(span, doFinishSpanEarly(command));
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void stopSpan(
|
||||
@Advice.Argument(0) final RedisCommand command,
|
||||
@Advice.Enter final AgentScope scope,
|
||||
@Advice.Thrown final Throwable throwable,
|
||||
@Advice.Return final AsyncCommand<?, ?, ?> asyncCommand) {
|
||||
final AgentSpan span = scope.span();
|
||||
if (throwable != null) {
|
||||
DECORATE.onError(span, throwable);
|
||||
DECORATE.beforeFinish(span);
|
||||
span.finish();
|
||||
scope.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// close spans on error or normal completion
|
||||
if (!doFinishSpanEarly(command)) {
|
||||
asyncCommand.handleAsync(new LettuceAsyncBiFunction<>(span));
|
||||
}
|
||||
scope.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package datadog.trace.instrumentation.lettuce;
|
||||
|
||||
import com.lambdaworks.redis.RedisURI;
|
||||
import com.lambdaworks.redis.protocol.RedisCommand;
|
||||
import datadog.trace.api.DDSpanTypes;
|
||||
import datadog.trace.api.DDTags;
|
||||
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||
import datadog.trace.bootstrap.instrumentation.api.Tags;
|
||||
import datadog.trace.bootstrap.instrumentation.decorator.DatabaseClientDecorator;
|
||||
|
||||
public class LettuceClientDecorator extends DatabaseClientDecorator<RedisURI> {
|
||||
|
||||
public static final LettuceClientDecorator DECORATE = new LettuceClientDecorator();
|
||||
|
||||
@Override
|
||||
protected String[] instrumentationNames() {
|
||||
return new String[] {"lettuce"};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String service() {
|
||||
return "redis";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String component() {
|
||||
return "redis-client";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String spanType() {
|
||||
return DDSpanTypes.REDIS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String dbType() {
|
||||
return "redis";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String dbUser(final RedisURI connection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String dbInstance(final RedisURI connection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentSpan onConnection(final AgentSpan span, final RedisURI connection) {
|
||||
if (connection != null) {
|
||||
span.setTag(Tags.PEER_HOSTNAME, connection.getHost());
|
||||
span.setTag(Tags.PEER_PORT, connection.getPort());
|
||||
|
||||
span.setTag("db.redis.dbIndex", connection.getDatabase());
|
||||
span.setTag(
|
||||
DDTags.RESOURCE_NAME,
|
||||
"CONNECT:"
|
||||
+ connection.getHost()
|
||||
+ ":"
|
||||
+ connection.getPort()
|
||||
+ "/"
|
||||
+ connection.getDatabase());
|
||||
}
|
||||
return super.onConnection(span, connection);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public AgentSpan onCommand(final AgentSpan span, final RedisCommand command) {
|
||||
String commandName = LettuceInstrumentationUtil.getCommandName(command);
|
||||
span.setTag(
|
||||
DDTags.RESOURCE_NAME, LettuceInstrumentationUtil.getCommandResourceName(commandName));
|
||||
return span;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package datadog.trace.instrumentation.lettuce;
|
||||
|
||||
import com.lambdaworks.redis.protocol.RedisCommand;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class LettuceInstrumentationUtil {
|
||||
|
||||
public static final String AGENT_CRASHING_COMMAND_PREFIX = "COMMAND-NAME:";
|
||||
|
||||
public static final Set<String> nonInstrumentingCommands =
|
||||
new HashSet<>(Arrays.asList("SHUTDOWN", "DEBUG", "OOM", "SEGFAULT"));
|
||||
|
||||
public static final Set<String> agentCrashingCommands =
|
||||
new HashSet<>(Arrays.asList("CLIENT", "CLUSTER", "COMMAND", "CONFIG", "DEBUG", "SCRIPT"));
|
||||
|
||||
/**
|
||||
* Determines whether a redis command should finish its relevant span early (as soon as tags are
|
||||
* added and the command is executed) because these commands have no return values/call backs, so
|
||||
* we must close the span early in order to provide info for the users
|
||||
*
|
||||
* @param command
|
||||
* @return true if finish the span early (the command will not have a return value)
|
||||
*/
|
||||
public static boolean doFinishSpanEarly(final RedisCommand command) {
|
||||
final String commandName = LettuceInstrumentationUtil.getCommandName(command);
|
||||
return nonInstrumentingCommands.contains(commandName);
|
||||
}
|
||||
|
||||
// Workaround to keep trace agent from crashing
|
||||
// Currently the commands in AGENT_CRASHING_COMMANDS_WORDS will crash the trace agent and
|
||||
// traces with these commands as the resource name will not be processed by the trace agent
|
||||
// https://github.com/DataDog/datadog-trace-agent/blob/master/quantizer/redis.go#L18 has
|
||||
// list of commands that will currently fail at the trace agent level.
|
||||
|
||||
/**
|
||||
* Workaround to keep trace agent from crashing Currently the commands in
|
||||
* AGENT_CRASHING_COMMANDS_WORDS will crash the trace agent and traces with these commands as the
|
||||
* resource name will not be processed by the trace agent
|
||||
* https://github.com/DataDog/datadog-trace-agent/blob/master/quantizer/redis.go#L18 has list of
|
||||
* commands that will currently fail at the trace agent level.
|
||||
*
|
||||
* @param actualCommandName the actual redis command
|
||||
* @return the redis command with a prefix if it is a command that will crash the trace agent,
|
||||
* otherwise, the original command is returned.
|
||||
*/
|
||||
public static String getCommandResourceName(final String actualCommandName) {
|
||||
if (agentCrashingCommands.contains(actualCommandName)) {
|
||||
return AGENT_CRASHING_COMMAND_PREFIX + actualCommandName;
|
||||
}
|
||||
return actualCommandName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the actual redis command name from a RedisCommand object
|
||||
*
|
||||
* @param command the lettuce RedisCommand object
|
||||
* @return the redis command as a string
|
||||
*/
|
||||
public static String getCommandName(final RedisCommand command) {
|
||||
String commandName = "Redis Command";
|
||||
if (command != null) {
|
||||
// get the redis command name (i.e. GET, SET, HMSET, etc)
|
||||
if (command.getType() != null) {
|
||||
commandName = command.getType().name().trim();
|
||||
}
|
||||
}
|
||||
return commandName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package datadog.trace.instrumentation.lettuce;
|
||||
|
||||
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
|
||||
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
|
||||
import static datadog.trace.instrumentation.lettuce.LettuceClientDecorator.DECORATE;
|
||||
|
||||
import com.lambdaworks.redis.RedisURI;
|
||||
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
|
||||
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
||||
public class RedisConnectionAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static AgentScope onEnter(@Advice.Argument(1) RedisURI redisURI) {
|
||||
AgentSpan span = startSpan("redis.query");
|
||||
DECORATE.afterStart(span);
|
||||
DECORATE.onConnection(span, redisURI);
|
||||
return activateSpan(span, false);
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void onReturn(@Advice.Enter AgentScope scope, @Advice.Thrown Throwable throwable) {
|
||||
AgentSpan span = scope.span();
|
||||
try {
|
||||
if (throwable != null) {
|
||||
DECORATE.onError(span, throwable);
|
||||
DECORATE.beforeFinish(span);
|
||||
}
|
||||
} finally {
|
||||
span.finish();
|
||||
scope.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,542 @@
|
|||
import com.lambdaworks.redis.ClientOptions
|
||||
import com.lambdaworks.redis.RedisClient
|
||||
import com.lambdaworks.redis.RedisFuture
|
||||
import com.lambdaworks.redis.RedisURI
|
||||
import com.lambdaworks.redis.api.StatefulConnection
|
||||
import com.lambdaworks.redis.api.async.RedisAsyncCommands
|
||||
import com.lambdaworks.redis.api.sync.RedisCommands
|
||||
import com.lambdaworks.redis.protocol.AsyncCommand
|
||||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.utils.PortUtils
|
||||
import datadog.trace.api.DDSpanTypes
|
||||
import datadog.trace.bootstrap.instrumentation.api.Tags
|
||||
|
||||
import redis.embedded.RedisServer
|
||||
import spock.lang.Shared
|
||||
import spock.util.concurrent.AsyncConditions
|
||||
import com.lambdaworks.redis.codec.Utf8StringCodec
|
||||
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.function.BiConsumer
|
||||
import java.util.function.BiFunction
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Function
|
||||
|
||||
import com.lambdaworks.redis.RedisConnectionException
|
||||
|
||||
import static datadog.trace.instrumentation.lettuce.LettuceInstrumentationUtil.AGENT_CRASHING_COMMAND_PREFIX
|
||||
|
||||
class LettuceAsyncClientTest extends AgentTestRunner {
|
||||
public static final String HOST = "127.0.0.1"
|
||||
public static final int DB_INDEX = 0
|
||||
// Disable autoreconnect so we do not get stray traces popping up on server shutdown
|
||||
public static final ClientOptions CLIENT_OPTIONS = new ClientOptions.Builder().autoReconnect(false).build()
|
||||
|
||||
@Shared
|
||||
int port
|
||||
@Shared
|
||||
int incorrectPort
|
||||
@Shared
|
||||
String dbAddr
|
||||
@Shared
|
||||
String dbAddrNonExistent
|
||||
@Shared
|
||||
String dbUriNonExistent
|
||||
@Shared
|
||||
String embeddedDbUri
|
||||
|
||||
@Shared
|
||||
RedisServer redisServer
|
||||
|
||||
@Shared
|
||||
Map<String, String> testHashMap = [
|
||||
firstname: "John",
|
||||
lastname : "Doe",
|
||||
age : "53"
|
||||
]
|
||||
|
||||
RedisClient redisClient
|
||||
StatefulConnection connection
|
||||
RedisAsyncCommands<String, ?> asyncCommands
|
||||
RedisCommands<String, ?> syncCommands
|
||||
|
||||
def setupSpec() {
|
||||
port = PortUtils.randomOpenPort()
|
||||
incorrectPort = PortUtils.randomOpenPort()
|
||||
dbAddr = HOST + ":" + port + "/" + DB_INDEX
|
||||
dbAddrNonExistent = HOST + ":" + incorrectPort + "/" + DB_INDEX
|
||||
dbUriNonExistent = "redis://" + dbAddrNonExistent
|
||||
embeddedDbUri = "redis://" + dbAddr
|
||||
|
||||
redisServer = RedisServer.builder()
|
||||
// bind to localhost to avoid firewall popup
|
||||
.setting("bind " + HOST)
|
||||
// set max memory to avoid problems in CI
|
||||
.setting("maxmemory 128M")
|
||||
.port(port).build()
|
||||
}
|
||||
|
||||
def setup() {
|
||||
redisClient = RedisClient.create(embeddedDbUri)
|
||||
|
||||
println "Using redis: $redisServer.args"
|
||||
redisServer.start()
|
||||
redisClient.setOptions(CLIENT_OPTIONS)
|
||||
|
||||
connection = redisClient.connect()
|
||||
asyncCommands = connection.async()
|
||||
syncCommands = connection.sync()
|
||||
|
||||
syncCommands.set("TESTKEY", "TESTVAL")
|
||||
|
||||
// 1 set + 1 connect trace
|
||||
TEST_WRITER.waitForTraces(2)
|
||||
TEST_WRITER.clear()
|
||||
}
|
||||
|
||||
def cleanup() {
|
||||
connection.close()
|
||||
redisServer.stop()
|
||||
}
|
||||
|
||||
def "connect using get on ConnectionFuture"() {
|
||||
setup:
|
||||
RedisClient testConnectionClient = RedisClient.create(embeddedDbUri)
|
||||
testConnectionClient.setOptions(CLIENT_OPTIONS)
|
||||
|
||||
when:
|
||||
StatefulConnection connection = testConnectionClient.connect(new Utf8StringCodec(),
|
||||
new RedisURI(HOST, port, 3, TimeUnit.SECONDS))
|
||||
|
||||
then:
|
||||
connection != null
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "CONNECT:" + dbAddr
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.PEER_HOSTNAME" HOST
|
||||
"$Tags.PEER_PORT" port
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
"db.redis.dbIndex" 0
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
connection.close()
|
||||
}
|
||||
|
||||
def "connect exception inside the connection future"() {
|
||||
setup:
|
||||
RedisClient testConnectionClient = RedisClient.create(dbUriNonExistent)
|
||||
testConnectionClient.setOptions(CLIENT_OPTIONS)
|
||||
|
||||
when:
|
||||
StatefulConnection connection = testConnectionClient.connect(new Utf8StringCodec(),
|
||||
new RedisURI(HOST, incorrectPort, 3, TimeUnit.SECONDS))
|
||||
|
||||
then:
|
||||
connection == null
|
||||
thrown RedisConnectionException
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "CONNECT:" + dbAddrNonExistent
|
||||
errored true
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.PEER_HOSTNAME" HOST
|
||||
"$Tags.PEER_PORT" incorrectPort
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
"db.redis.dbIndex" 0
|
||||
errorTags RedisConnectionException, String
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "set command using Future get with timeout"() {
|
||||
setup:
|
||||
RedisFuture<String> redisFuture = asyncCommands.set("TESTSETKEY", "TESTSETVAL")
|
||||
String res = redisFuture.get(3, TimeUnit.SECONDS)
|
||||
|
||||
expect:
|
||||
res == "OK"
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "SET"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "get command chained with thenAccept"() {
|
||||
setup:
|
||||
def conds = new AsyncConditions()
|
||||
Consumer<String> consumer = new Consumer<String>() {
|
||||
@Override
|
||||
void accept(String res) {
|
||||
conds.evaluate {
|
||||
assert res == "TESTVAL"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when:
|
||||
RedisFuture<String> redisFuture = asyncCommands.get("TESTKEY")
|
||||
redisFuture.thenAccept(consumer)
|
||||
|
||||
then:
|
||||
conds.await()
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "GET"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// to make sure instrumentation's chained completion stages won't interfere with user's, while still
|
||||
// recording metrics
|
||||
def "get non existent key command with handleAsync and chained with thenApply"() {
|
||||
setup:
|
||||
def conds = new AsyncConditions()
|
||||
final String successStr = "KEY MISSING"
|
||||
BiFunction<String, Throwable, String> firstStage = new BiFunction<String, Throwable, String>() {
|
||||
@Override
|
||||
String apply(String res, Throwable throwable) {
|
||||
conds.evaluate {
|
||||
assert res == null
|
||||
assert throwable == null
|
||||
}
|
||||
return (res == null ? successStr : res)
|
||||
}
|
||||
}
|
||||
Function<String, Object> secondStage = new Function<String, Object>() {
|
||||
@Override
|
||||
Object apply(String input) {
|
||||
conds.evaluate {
|
||||
assert input == successStr
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
when:
|
||||
RedisFuture<String> redisFuture = asyncCommands.get("NON_EXISTENT_KEY")
|
||||
redisFuture.handleAsync(firstStage).thenApply(secondStage)
|
||||
|
||||
then:
|
||||
conds.await()
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "GET"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "command with no arguments using a biconsumer"() {
|
||||
setup:
|
||||
def conds = new AsyncConditions()
|
||||
BiConsumer<String, Throwable> biConsumer = new BiConsumer<String, Throwable>() {
|
||||
@Override
|
||||
void accept(String keyRetrieved, Throwable throwable) {
|
||||
conds.evaluate {
|
||||
assert keyRetrieved != null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when:
|
||||
RedisFuture<String> redisFuture = asyncCommands.randomkey()
|
||||
redisFuture.whenCompleteAsync(biConsumer)
|
||||
|
||||
then:
|
||||
conds.await()
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "RANDOMKEY"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "hash set and then nest apply to hash getall"() {
|
||||
setup:
|
||||
def conds = new AsyncConditions()
|
||||
|
||||
when:
|
||||
RedisFuture<String> hmsetFuture = asyncCommands.hmset("TESTHM", testHashMap)
|
||||
hmsetFuture.thenApplyAsync(new Function<String, Object>() {
|
||||
@Override
|
||||
Object apply(String setResult) {
|
||||
TEST_WRITER.waitForTraces(1) // Wait for 'hmset' trace to get written
|
||||
conds.evaluate {
|
||||
assert setResult == "OK"
|
||||
}
|
||||
RedisFuture<Map<String, String>> hmGetAllFuture = asyncCommands.hgetall("TESTHM")
|
||||
hmGetAllFuture.exceptionally(new Function<Throwable, Map<String, String>>() {
|
||||
@Override
|
||||
Map<String, String> apply(Throwable throwable) {
|
||||
println("unexpected:" + throwable.toString())
|
||||
throwable.printStackTrace()
|
||||
assert false
|
||||
return null
|
||||
}
|
||||
})
|
||||
hmGetAllFuture.thenAccept(new Consumer<Map<String, String>>() {
|
||||
@Override
|
||||
void accept(Map<String, String> hmGetAllResult) {
|
||||
conds.evaluate {
|
||||
assert testHashMap == hmGetAllResult
|
||||
}
|
||||
}
|
||||
})
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
then:
|
||||
conds.await()
|
||||
assertTraces(2) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "HMSET"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
trace(1, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "HGETALL"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "command completes exceptionally"() {
|
||||
setup:
|
||||
// turn off auto flush to complete the command exceptionally manually
|
||||
asyncCommands.setAutoFlushCommands(false)
|
||||
def conds = new AsyncConditions()
|
||||
RedisFuture redisFuture = asyncCommands.del("key1", "key2")
|
||||
boolean completedExceptionally = ((AsyncCommand) redisFuture).completeExceptionally(new IllegalStateException("TestException"))
|
||||
redisFuture.exceptionally({
|
||||
throwable ->
|
||||
conds.evaluate {
|
||||
assert throwable != null
|
||||
assert throwable instanceof IllegalStateException
|
||||
assert throwable.getMessage() == "TestException"
|
||||
}
|
||||
throw throwable
|
||||
})
|
||||
|
||||
when:
|
||||
// now flush and execute the command
|
||||
asyncCommands.flushCommands()
|
||||
redisFuture.get()
|
||||
|
||||
then:
|
||||
conds.await()
|
||||
completedExceptionally == true
|
||||
thrown Exception
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "DEL"
|
||||
errored true
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
errorTags(IllegalStateException, "TestException")
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "cancel command before it finishes"() {
|
||||
setup:
|
||||
asyncCommands.setAutoFlushCommands(false)
|
||||
def conds = new AsyncConditions()
|
||||
RedisFuture redisFuture = asyncCommands.sadd("SKEY", "1", "2")
|
||||
redisFuture.whenCompleteAsync({
|
||||
res, throwable ->
|
||||
conds.evaluate {
|
||||
assert throwable != null
|
||||
assert throwable instanceof CancellationException
|
||||
}
|
||||
})
|
||||
|
||||
when:
|
||||
boolean cancelSuccess = redisFuture.cancel(true)
|
||||
asyncCommands.flushCommands()
|
||||
|
||||
then:
|
||||
conds.await()
|
||||
cancelSuccess == true
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "SADD"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
"db.command.cancelled" true
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "debug segfault command (returns void) with no argument should produce span"() {
|
||||
setup:
|
||||
asyncCommands.debugSegfault()
|
||||
|
||||
expect:
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName AGENT_CRASHING_COMMAND_PREFIX + "DEBUG"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def "shutdown command (returns void) should produce a span"() {
|
||||
setup:
|
||||
asyncCommands.shutdown(false)
|
||||
|
||||
expect:
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "SHUTDOWN"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,384 @@
|
|||
import com.lambdaworks.redis.ClientOptions
|
||||
import com.lambdaworks.redis.RedisClient
|
||||
import com.lambdaworks.redis.RedisConnectionException
|
||||
import com.lambdaworks.redis.api.StatefulConnection
|
||||
import com.lambdaworks.redis.api.sync.RedisCommands
|
||||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.utils.PortUtils
|
||||
import datadog.trace.api.DDSpanTypes
|
||||
import datadog.trace.bootstrap.instrumentation.api.Tags
|
||||
import redis.embedded.RedisServer
|
||||
import spock.lang.Shared
|
||||
|
||||
import static datadog.trace.instrumentation.lettuce.LettuceInstrumentationUtil.AGENT_CRASHING_COMMAND_PREFIX
|
||||
|
||||
class LettuceSyncClientTest extends AgentTestRunner {
|
||||
public static final String HOST = "127.0.0.1"
|
||||
public static final int DB_INDEX = 0
|
||||
// Disable autoreconnect so we do not get stray traces popping up on server shutdown
|
||||
public static final ClientOptions CLIENT_OPTIONS = new ClientOptions.Builder().autoReconnect(false).build()
|
||||
|
||||
@Shared
|
||||
int port
|
||||
@Shared
|
||||
int incorrectPort
|
||||
@Shared
|
||||
String dbAddr
|
||||
@Shared
|
||||
String dbAddrNonExistent
|
||||
@Shared
|
||||
String dbUriNonExistent
|
||||
@Shared
|
||||
String embeddedDbUri
|
||||
|
||||
@Shared
|
||||
RedisServer redisServer
|
||||
|
||||
@Shared
|
||||
Map<String, String> testHashMap = [
|
||||
firstname: "John",
|
||||
lastname : "Doe",
|
||||
age : "53"
|
||||
]
|
||||
|
||||
RedisClient redisClient
|
||||
StatefulConnection connection
|
||||
RedisCommands<String, ?> syncCommands
|
||||
|
||||
def setupSpec() {
|
||||
port = PortUtils.randomOpenPort()
|
||||
incorrectPort = PortUtils.randomOpenPort()
|
||||
dbAddr = HOST + ":" + port + "/" + DB_INDEX
|
||||
dbAddrNonExistent = HOST + ":" + incorrectPort + "/" + DB_INDEX
|
||||
dbUriNonExistent = "redis://" + dbAddrNonExistent
|
||||
embeddedDbUri = "redis://" + dbAddr
|
||||
|
||||
redisServer = RedisServer.builder()
|
||||
// bind to localhost to avoid firewall popup
|
||||
.setting("bind " + HOST)
|
||||
// set max memory to avoid problems in CI
|
||||
.setting("maxmemory 128M")
|
||||
.port(port).build()
|
||||
}
|
||||
|
||||
def setup() {
|
||||
redisClient = RedisClient.create(embeddedDbUri)
|
||||
|
||||
redisServer.start()
|
||||
connection = redisClient.connect()
|
||||
syncCommands = connection.sync()
|
||||
|
||||
syncCommands.set("TESTKEY", "TESTVAL")
|
||||
syncCommands.hmset("TESTHM", testHashMap)
|
||||
|
||||
// 2 sets + 1 connect trace
|
||||
TEST_WRITER.waitForTraces(3)
|
||||
TEST_WRITER.clear()
|
||||
}
|
||||
|
||||
def cleanup() {
|
||||
connection.close()
|
||||
redisServer.stop()
|
||||
}
|
||||
|
||||
def "connect"() {
|
||||
setup:
|
||||
RedisClient testConnectionClient = RedisClient.create(embeddedDbUri)
|
||||
testConnectionClient.setOptions(CLIENT_OPTIONS)
|
||||
|
||||
when:
|
||||
StatefulConnection connection = testConnectionClient.connect()
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "CONNECT:" + dbAddr
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.PEER_HOSTNAME" HOST
|
||||
"$Tags.PEER_PORT" port
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
"db.redis.dbIndex" 0
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
connection.close()
|
||||
}
|
||||
|
||||
def "connect exception"() {
|
||||
setup:
|
||||
RedisClient testConnectionClient = RedisClient.create(dbUriNonExistent)
|
||||
testConnectionClient.setOptions(CLIENT_OPTIONS)
|
||||
|
||||
when:
|
||||
testConnectionClient.connect()
|
||||
|
||||
then:
|
||||
thrown RedisConnectionException
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "CONNECT:" + dbAddrNonExistent
|
||||
errored true
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.PEER_HOSTNAME" HOST
|
||||
"$Tags.PEER_PORT" incorrectPort
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
"db.redis.dbIndex" 0
|
||||
errorTags RedisConnectionException, String
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "set command"() {
|
||||
setup:
|
||||
String res = syncCommands.set("TESTSETKEY", "TESTSETVAL")
|
||||
|
||||
expect:
|
||||
res == "OK"
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "SET"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "get command"() {
|
||||
setup:
|
||||
String res = syncCommands.get("TESTKEY")
|
||||
|
||||
expect:
|
||||
res == "TESTVAL"
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "GET"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "get non existent key command"() {
|
||||
setup:
|
||||
String res = syncCommands.get("NON_EXISTENT_KEY")
|
||||
|
||||
expect:
|
||||
res == null
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "GET"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "command with no arguments"() {
|
||||
setup:
|
||||
def keyRetrieved = syncCommands.randomkey()
|
||||
|
||||
expect:
|
||||
keyRetrieved != null
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "RANDOMKEY"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "list command"() {
|
||||
setup:
|
||||
long res = syncCommands.lpush("TESTLIST", "TESTLIST ELEMENT")
|
||||
|
||||
expect:
|
||||
res == 1
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "LPUSH"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "hash set command"() {
|
||||
setup:
|
||||
def res = syncCommands.hmset("user", testHashMap)
|
||||
|
||||
expect:
|
||||
res == "OK"
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "HMSET"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "hash getall command"() {
|
||||
setup:
|
||||
Map<String, String> res = syncCommands.hgetall("TESTHM")
|
||||
|
||||
expect:
|
||||
res == testHashMap
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "HGETALL"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "debug segfault command (returns void) with no argument should produce span"() {
|
||||
setup:
|
||||
syncCommands.debugSegfault()
|
||||
|
||||
expect:
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName AGENT_CRASHING_COMMAND_PREFIX + "DEBUG"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "shutdown command (returns void) should produce a span"() {
|
||||
setup:
|
||||
syncCommands.shutdown(false)
|
||||
|
||||
expect:
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
serviceName "redis"
|
||||
operationName "redis.query"
|
||||
spanType DDSpanTypes.REDIS
|
||||
resourceName "SHUTDOWN"
|
||||
errored false
|
||||
|
||||
tags {
|
||||
"$Tags.COMPONENT" "redis-client"
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
|
||||
"$Tags.DB_TYPE" "redis"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -107,6 +107,7 @@ include ':dd-java-agent:instrumentation:jms'
|
|||
include ':dd-java-agent:instrumentation:jsp-2.3'
|
||||
include ':dd-java-agent:instrumentation:kafka-clients-0.11'
|
||||
include ':dd-java-agent:instrumentation:kafka-streams-0.11'
|
||||
include ':dd-java-agent:instrumentation:lettuce-4.0'
|
||||
include ':dd-java-agent:instrumentation:lettuce-5'
|
||||
include ':dd-java-agent:instrumentation:log4j1'
|
||||
include ':dd-java-agent:instrumentation:log4j2'
|
||||
|
|
Loading…
Reference in New Issue