diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/utils/NetPeerUtils.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/utils/NetPeerUtils.java index 66bddc985c67..ee32d2ecb421 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/utils/NetPeerUtils.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/utils/NetPeerUtils.java @@ -76,7 +76,8 @@ public void setNetPeer(Span span, String peerName, String peerIp, int port) { setNetPeer(span::setAttribute, peerName, peerIp, port); } - public void setNetPeer(SpanAttributeSetter span, String peerName, String peerIp, int port) { + public void setNetPeer( + SpanAttributeSetter span, @Nullable String peerName, @Nullable String peerIp, int port) { if (peerName != null && !peerName.equals(peerIp)) { span.setAttribute(SemanticAttributes.NET_PEER_NAME, peerName); } @@ -109,6 +110,6 @@ private String mapToPeerService(String endpoint) { */ @FunctionalInterface public interface SpanAttributeSetter { - void setAttribute(AttributeKey key, @Nullable T value); + void setAttribute(AttributeKey key, T value); } } diff --git a/instrumentation/lettuce/lettuce-5.0/javaagent/lettuce-5.0-javaagent.gradle b/instrumentation/lettuce/lettuce-5.0/javaagent/lettuce-5.0-javaagent.gradle index b0c758fa1ded..46e2f26f7891 100644 --- a/instrumentation/lettuce/lettuce-5.0/javaagent/lettuce-5.0-javaagent.gradle +++ b/instrumentation/lettuce/lettuce-5.0/javaagent/lettuce-5.0-javaagent.gradle @@ -12,7 +12,7 @@ muzzle { dependencies { compileOnly group: 'io.lettuce', name: 'lettuce-core', version: '5.0.0.RELEASE' - implementation project(':instrumentation:lettuce:lettuce-common:javaagent') + implementation project(':instrumentation:lettuce:lettuce-common:library') testImplementation group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6' testImplementation group: 'io.lettuce', name: 'lettuce-core', version: '5.0.0.RELEASE' diff --git a/instrumentation/lettuce/lettuce-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_0/LettuceDatabaseClientTracer.java b/instrumentation/lettuce/lettuce-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_0/LettuceDatabaseClientTracer.java index 185bc0fdb766..3daab49ec7f9 100644 --- a/instrumentation/lettuce/lettuce-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_0/LettuceDatabaseClientTracer.java +++ b/instrumentation/lettuce/lettuce-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_0/LettuceDatabaseClientTracer.java @@ -8,7 +8,7 @@ import io.lettuce.core.RedisURI; import io.lettuce.core.protocol.RedisCommand; import io.opentelemetry.instrumentation.api.db.RedisCommandSanitizer; -import io.opentelemetry.javaagent.instrumentation.lettuce.LettuceArgSplitter; +import io.opentelemetry.instrumentation.lettuce.common.LettuceArgSplitter; import java.util.Collections; import java.util.List; diff --git a/instrumentation/lettuce/lettuce-5.1/javaagent/lettuce-5.1-javaagent.gradle b/instrumentation/lettuce/lettuce-5.1/javaagent/lettuce-5.1-javaagent.gradle index f61593f5bafb..8ee3e3fd96ff 100644 --- a/instrumentation/lettuce/lettuce-5.1/javaagent/lettuce-5.1-javaagent.gradle +++ b/instrumentation/lettuce/lettuce-5.1/javaagent/lettuce-5.1-javaagent.gradle @@ -12,7 +12,9 @@ muzzle { dependencies { library group: 'io.lettuce', name: 'lettuce-core', version: '5.1.0.RELEASE' - implementation project(':instrumentation:lettuce:lettuce-common:javaagent') + implementation project(':instrumentation:lettuce:lettuce-5.1:library') + + testImplementation project(':instrumentation:lettuce:lettuce-5.1:testing') testImplementation group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6' // Only 5.2+ will have command arguments in the db.statement tag. diff --git a/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceInstrumentationModule.java b/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceInstrumentationModule.java index 135666bb3e82..b1516dd27612 100644 --- a/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceInstrumentationModule.java +++ b/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceInstrumentationModule.java @@ -59,7 +59,7 @@ public static class DefaultClientResourcesAdvice { @Advice.OnMethodExit(suppress = Throwable.class) public static void methodEnter(@Advice.Return DefaultClientResources.Builder builder) { - builder.tracing(OpenTelemetryTracing.INSTANCE); + builder.tracing(TracingHolder.TRACING); } } } diff --git a/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/TracingHolder.java b/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/TracingHolder.java new file mode 100644 index 000000000000..09a69fae5061 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/TracingHolder.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.lettuce.v5_1; + +import io.lettuce.core.tracing.Tracing; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.lettuce.v5_1.LettuceTracing; + +public final class TracingHolder { + + public static final Tracing TRACING = + LettuceTracing.create(GlobalOpenTelemetry.get()).newTracing(); + + private TracingHolder() {} +} diff --git a/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceAsyncClientTest.groovy b/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceAsyncClientTest.groovy index e13f9519fd63..04fa6160c87c 100644 --- a/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceAsyncClientTest.groovy +++ b/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceAsyncClientTest.groovy @@ -5,392 +5,13 @@ package io.opentelemetry.javaagent.instrumentation.lettuce.v5_1 -import static io.opentelemetry.api.trace.SpanKind.CLIENT - -import io.lettuce.core.ClientOptions -import io.lettuce.core.ConnectionFuture import io.lettuce.core.RedisClient -import io.lettuce.core.RedisFuture -import io.lettuce.core.RedisURI -import io.lettuce.core.api.StatefulConnection -import io.lettuce.core.api.async.RedisAsyncCommands -import io.lettuce.core.api.sync.RedisCommands -import io.lettuce.core.codec.StringCodec -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.utils.PortUtils -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import java.util.concurrent.ExecutionException -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 redis.embedded.RedisServer -import spock.lang.Shared -import spock.util.concurrent.AsyncConditions - -class LettuceAsyncClientTest extends AgentInstrumentationSpecification { - 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 = 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 testHashMap = [ - firstname: "John", - lastname : "Doe", - age : "53" - ] - - RedisClient redisClient - StatefulConnection connection - RedisAsyncCommands asyncCommands - RedisCommands 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 - ignoreTracesAndClear(1) - } - - def cleanup() { - connection.close() - redisServer.stop() - } - - def "connect using get on ConnectionFuture"() { - setup: - RedisClient testConnectionClient = RedisClient.create(embeddedDbUri) - testConnectionClient.setOptions(CLIENT_OPTIONS) - - when: - ConnectionFuture connectionFuture = testConnectionClient.connectAsync(StringCodec.UTF8, - new RedisURI(HOST, port, 3, TimeUnit.SECONDS)) - StatefulConnection connection = connectionFuture.get() - - then: - connection != null - // Lettuce tracing does not trace connect - assertTraces(0) {} - - cleanup: - connection.close() - } - - def "connect exception inside the connection future"() { - setup: - RedisClient testConnectionClient = RedisClient.create(dbUriNonExistent) - testConnectionClient.setOptions(CLIENT_OPTIONS) - - when: - ConnectionFuture connectionFuture = testConnectionClient.connectAsync(StringCodec.UTF8, - new RedisURI(HOST, incorrectPort, 3, TimeUnit.SECONDS)) - StatefulConnection connection = connectionFuture.get() - - then: - connection == null - thrown ExecutionException - // Lettuce tracing does not trace connect - assertTraces(0) {} - } - - def "set command using Future get with timeout"() { - setup: - RedisFuture redisFuture = asyncCommands.set("TESTSETKEY", "TESTSETVAL") - String res = redisFuture.get(3, TimeUnit.SECONDS) - - expect: - res == "OK" - assertTraces(1) { - trace(0, 1) { - span(0) { - name "SET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "SET TESTSETKEY ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "get command chained with thenAccept"() { - setup: - def conds = new AsyncConditions() - Consumer consumer = new Consumer() { - @Override - void accept(String res) { - conds.evaluate { - assert res == "TESTVAL" - } - } - } - - when: - RedisFuture redisFuture = asyncCommands.get("TESTKEY") - redisFuture.thenAccept(consumer) - - then: - conds.await() - assertTraces(1) { - trace(0, 1) { - span(0) { - name "GET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "GET TESTKEY" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - // 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() - String successStr = "KEY MISSING" - BiFunction firstStage = new BiFunction() { - @Override - String apply(String res, Throwable throwable) { - conds.evaluate { - assert res == null - assert throwable == null - } - return (res == null ? successStr : res) - } - } - Function secondStage = new Function() { - @Override - Object apply(String input) { - conds.evaluate { - assert input == successStr - } - return null - } - } - - when: - RedisFuture redisFuture = asyncCommands.get("NON_EXISTENT_KEY") - redisFuture.handleAsync(firstStage).thenApply(secondStage) - - then: - conds.await() - assertTraces(1) { - trace(0, 1) { - span(0) { - name "GET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "GET NON_EXISTENT_KEY" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "command with no arguments using a biconsumer"() { - setup: - def conds = new AsyncConditions() - BiConsumer biConsumer = new BiConsumer() { - @Override - void accept(String keyRetrieved, Throwable throwable) { - conds.evaluate { - assert keyRetrieved != null - } - } - } - - when: - RedisFuture redisFuture = asyncCommands.randomkey() - redisFuture.whenCompleteAsync(biConsumer) - - then: - conds.await() - assertTraces(1) { - trace(0, 1) { - span(0) { - name "RANDOMKEY" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_STATEMENT.key}" "RANDOMKEY" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "hash set and then nest apply to hash getall"() { - setup: - def conds = new AsyncConditions() - - when: - RedisFuture hmsetFuture = asyncCommands.hmset("TESTHM", testHashMap) - hmsetFuture.thenApplyAsync(new Function() { - @Override - Object apply(String setResult) { - conds.evaluate { - assert setResult == "OK" - } - RedisFuture> hmGetAllFuture = asyncCommands.hgetall("TESTHM") - hmGetAllFuture.exceptionally(new Function>() { - @Override - Map apply(Throwable throwable) { - println("unexpected:" + throwable.toString()) - throwable.printStackTrace() - assert false - return null - } - }) - hmGetAllFuture.thenAccept(new Consumer>() { - @Override - void accept(Map hmGetAllResult) { - conds.evaluate { - assert testHashMap == hmGetAllResult - } - } - }) - return null - } - }) +import io.opentelemetry.instrumentation.lettuce.v5_1.AbstractLettuceAsyncClientTest +import io.opentelemetry.instrumentation.test.AgentTestTrait - then: - conds.await() - assertTraces(2) { - trace(0, 1) { - span(0) { - name "HMSET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "HMSET TESTHM firstname ? lastname ? age ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - trace(1, 1) { - span(0) { - name "HGETALL" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "HGETALL TESTHM" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } +class LettuceAsyncClientTest extends AbstractLettuceAsyncClientTest implements AgentTestTrait { + @Override + RedisClient createClient(String uri) { + return RedisClient.create(uri) } } diff --git a/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceReactiveClientTest.groovy b/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceReactiveClientTest.groovy index e4a6778bfdbe..8d337e672c1a 100644 --- a/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceReactiveClientTest.groovy +++ b/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceReactiveClientTest.groovy @@ -5,397 +5,23 @@ package io.opentelemetry.javaagent.instrumentation.lettuce.v5_1 -import static io.opentelemetry.api.trace.SpanKind.CLIENT import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace -import io.lettuce.core.ClientOptions import io.lettuce.core.RedisClient -import io.lettuce.core.api.StatefulConnection -import io.lettuce.core.api.reactive.RedisReactiveCommands -import io.lettuce.core.api.sync.RedisCommands -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.utils.PortUtils +import io.opentelemetry.api.trace.SpanKind +import io.opentelemetry.instrumentation.lettuce.v5_1.AbstractLettuceReactiveClientTest +import io.opentelemetry.instrumentation.test.AgentTestTrait import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import java.util.function.Consumer import reactor.core.scheduler.Schedulers -import redis.embedded.RedisServer -import spock.lang.Shared -import spock.util.concurrent.AsyncConditions -class LettuceReactiveClientTest extends AgentInstrumentationSpecification { - 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 = ClientOptions.builder().autoReconnect(false).build() - - @Shared - int port - @Shared - String embeddedDbUri - - @Shared - RedisServer redisServer - - RedisClient redisClient - StatefulConnection connection - RedisReactiveCommands reactiveCommands - RedisCommands syncCommands - - def setupSpec() { - port = PortUtils.randomOpenPort() - String dbAddr = HOST + ":" + port + "/" + DB_INDEX - 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() - reactiveCommands = connection.reactive() - syncCommands = connection.sync() - - syncCommands.set("TESTKEY", "TESTVAL") - - // 1 set - ignoreTracesAndClear(1) - } - - def cleanup() { - connection.close() - redisClient.shutdown() - redisServer.stop() - } - - def "set command with subscribe on a defined consumer"() { - setup: - def conds = new AsyncConditions() - Consumer consumer = new Consumer() { - @Override - void accept(String res) { - conds.evaluate { - assert res == "OK" - } - } - } - - when: - reactiveCommands.set("TESTSETKEY", "TESTSETVAL").subscribe(consumer) - - then: - conds.await() - assertTraces(1) { - trace(0, 1) { - span(0) { - name "SET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "SET TESTSETKEY ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "get command with lambda function"() { - setup: - def conds = new AsyncConditions() - - when: - reactiveCommands.get("TESTKEY").subscribe { res -> conds.evaluate { assert res == "TESTVAL" } } - - then: - conds.await() - assertTraces(1) { - trace(0, 1) { - span(0) { - name "GET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "GET TESTKEY" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - // to make sure instrumentation's chained completion stages won't interfere with user's, while still - // recording metrics - def "get non existent key command"() { - setup: - def conds = new AsyncConditions() - final defaultVal = "NOT THIS VALUE" - - when: - reactiveCommands.get("NON_EXISTENT_KEY").defaultIfEmpty(defaultVal).subscribe { - res -> - conds.evaluate { - assert res == defaultVal - } - } - - then: - conds.await() - assertTraces(1) { - trace(0, 1) { - span(0) { - name "GET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "GET NON_EXISTENT_KEY" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - - } - - def "command with no arguments"() { - setup: - def conds = new AsyncConditions() - - when: - reactiveCommands.randomkey().subscribe { - res -> - conds.evaluate { - assert res == "TESTKEY" - } - } - - then: - conds.await() - assertTraces(1) { - trace(0, 1) { - span(0) { - name "RANDOMKEY" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_STATEMENT.key}" "RANDOMKEY" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "command flux publisher "() { - setup: - reactiveCommands.command().subscribe() - - expect: - assertTraces(1) { - trace(0, 1) { - span(0) { - name "COMMAND" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_STATEMENT.key}" "COMMAND" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "non reactive command should not produce span"() { - when: - def res = reactiveCommands.digest() - - then: - res != null - traces.size() == 0 - } - - def "blocking subscriber"() { - when: - runUnderTrace("test-parent") { - reactiveCommands.set("a", "1") - .then(reactiveCommands.get("a")) - .block() - } - - then: - assertTraces(1) { - trace(0, 3) { - span(0) { - name "test-parent" - errored false - attributes { - } - } - span(1) { - name "SET" - kind CLIENT - errored false - childOf span(0) - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "SET a ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - span(2) { - name "GET" - kind CLIENT - errored false - childOf span(0) - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "GET a" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "async subscriber"() { - when: - runUnderTrace("test-parent") { - reactiveCommands.set("a", "1") - .then(reactiveCommands.get("a")) - .subscribe() - } - - then: - assertTraces(1) { - trace(0, 3) { - span(0) { - name "test-parent" - errored false - attributes { - } - } - span(1) { - name "SET" - kind CLIENT - errored false - childOf span(0) - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "SET a ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - span(2) { - name "GET" - kind CLIENT - errored false - childOf span(0) - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "GET a" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } +class LettuceReactiveClientTest extends AbstractLettuceReactiveClientTest implements AgentTestTrait { + @Override + RedisClient createClient(String uri) { + return RedisClient.create(uri) } + // TODO(anuraaga): reactor library instrumentation doesn't seem to handle this case, figure out if + // it should and if so move back to base class. def "async subscriber with specific thread pool"() { when: runUnderTrace("test-parent") { @@ -416,7 +42,7 @@ class LettuceReactiveClientTest extends AgentInstrumentationSpecification { } span(1) { name "SET" - kind CLIENT + kind SpanKind.CLIENT errored false childOf span(0) attributes { @@ -436,7 +62,7 @@ class LettuceReactiveClientTest extends AgentInstrumentationSpecification { } span(2) { name "GET" - kind CLIENT + kind SpanKind.CLIENT errored false childOf span(0) attributes { diff --git a/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientAuthTest.groovy b/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientAuthTest.groovy index 4c88c1c312f3..664e501841cd 100644 --- a/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientAuthTest.groovy +++ b/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientAuthTest.groovy @@ -5,90 +5,13 @@ package io.opentelemetry.javaagent.instrumentation.lettuce.v5_1 -import static io.opentelemetry.api.trace.SpanKind.CLIENT - -import io.lettuce.core.ClientOptions import io.lettuce.core.RedisClient -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.utils.PortUtils -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import redis.embedded.RedisServer -import spock.lang.Shared - -class LettuceSyncClientAuthTest extends AgentInstrumentationSpecification { - 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 = ClientOptions.builder().autoReconnect(false).build() - - @Shared - int port - @Shared - String password - @Shared - String dbAddr - @Shared - String embeddedDbUri - - @Shared - RedisServer redisServer - - RedisClient redisClient - - def setupSpec() { - port = PortUtils.randomOpenPort() - dbAddr = HOST + ":" + port + "/" + DB_INDEX - embeddedDbUri = "redis://" + dbAddr - password = "password" - - redisServer = RedisServer.builder() - // bind to localhost to avoid firewall popup - .setting("bind " + HOST) - // set max memory to avoid problems in CI - .setting("maxmemory 128M") - // Set password - .setting("requirepass " + password) - .port(port).build() - } - - def setup() { - redisClient = RedisClient.create(embeddedDbUri) - redisClient.setOptions(CLIENT_OPTIONS) - redisServer.start() - } - - def cleanup() { - redisServer.stop() - } - - def "auth command"() { - setup: - def res = redisClient.connect().sync().auth(password) +import io.opentelemetry.instrumentation.lettuce.v5_1.AbstractLettuceSyncClientAuthTest +import io.opentelemetry.instrumentation.test.AgentTestTrait - expect: - res == "OK" - assertTraces(1) { - trace(0, 1) { - span(0) { - name "AUTH" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "AUTH ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } +class LettuceSyncClientAuthTest extends AbstractLettuceSyncClientAuthTest implements AgentTestTrait { + @Override + RedisClient createClient(String uri) { + return RedisClient.create(uri) } } diff --git a/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy b/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy index 89a85a26a42d..0b1d15baab6b 100644 --- a/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy +++ b/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy @@ -5,460 +5,13 @@ package io.opentelemetry.javaagent.instrumentation.lettuce.v5_1 -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static java.nio.charset.StandardCharsets.UTF_8 - -import io.lettuce.core.ClientOptions import io.lettuce.core.RedisClient -import io.lettuce.core.RedisConnectionException -import io.lettuce.core.ScriptOutputType -import io.lettuce.core.api.StatefulConnection -import io.lettuce.core.api.sync.RedisCommands -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.utils.PortUtils -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import redis.embedded.RedisServer -import spock.lang.Shared - -class LettuceSyncClientTest extends AgentInstrumentationSpecification { - 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 = ClientOptions.builder().autoReconnect(false).build() - - @Shared - int port - @Shared - int incorrectPort - @Shared - String dbAddr - @Shared - String dbAddrNonExistent - @Shared - String dbUriNonExistent - @Shared - String embeddedDbUri - @Shared - String embeddedDbLocalhostUri - - @Shared - RedisServer redisServer - - @Shared - Map testHashMap = [ - firstname: "John", - lastname : "Doe", - age : "53" - ] - - RedisClient redisClient - StatefulConnection connection - RedisCommands 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 - embeddedDbLocalhostUri = "redis://localhost:" + port + "/" + DB_INDEX - - 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 - ignoreTracesAndClear(2) - } - - def cleanup() { - connection.close() - redisServer.stop() - } - - def "connect"() { - setup: - RedisClient testConnectionClient = RedisClient.create(embeddedDbUri) - testConnectionClient.setOptions(CLIENT_OPTIONS) - - when: - StatefulConnection connection = testConnectionClient.connect() - - then: - // Lettuce tracing does not trace connect - assertTraces(0) {} - - cleanup: - connection.close() - } - - def "connect exception"() { - setup: - RedisClient testConnectionClient = RedisClient.create(dbUriNonExistent) - testConnectionClient.setOptions(CLIENT_OPTIONS) - - when: - testConnectionClient.connect() - - then: - thrown RedisConnectionException - // Lettuce tracing does not trace connect - assertTraces(0) {} - } - - def "set command"() { - setup: - String res = syncCommands.set("TESTSETKEY", "TESTSETVAL") - - expect: - res == "OK" - assertTraces(1) { - trace(0, 1) { - span(0) { - name "SET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "SET TESTSETKEY ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "set command localhost"() { - setup: - RedisClient testConnectionClient = RedisClient.create(embeddedDbLocalhostUri) - testConnectionClient.setOptions(CLIENT_OPTIONS) - StatefulConnection connection = testConnectionClient.connect() - String res = connection.sync().set("TESTSETKEY", "TESTSETVAL") - - expect: - res == "OK" - assertTraces(1) { - trace(0, 1) { - span(0) { - name "SET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_NAME.key}" "localhost" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://localhost:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "SET TESTSETKEY ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "get command"() { - setup: - String res = syncCommands.get("TESTKEY") - - expect: - res == "TESTVAL" - assertTraces(1) { - trace(0, 1) { - span(0) { - name "GET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "GET TESTKEY" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "get non existent key command"() { - setup: - String res = syncCommands.get("NON_EXISTENT_KEY") - - expect: - res == null - assertTraces(1) { - trace(0, 1) { - span(0) { - name "GET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "GET NON_EXISTENT_KEY" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "command with no arguments"() { - setup: - def keyRetrieved = syncCommands.randomkey() - - expect: - keyRetrieved != null - assertTraces(1) { - trace(0, 1) { - span(0) { - name "RANDOMKEY" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_STATEMENT.key}" "RANDOMKEY" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "list command"() { - setup: - long res = syncCommands.lpush("TESTLIST", "TESTLIST ELEMENT") - - expect: - res == 1 - assertTraces(1) { - trace(0, 1) { - span(0) { - name "LPUSH" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "LPUSH TESTLIST ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "hash set command"() { - setup: - def res = syncCommands.hmset("user", testHashMap) - - expect: - res == "OK" - assertTraces(1) { - trace(0, 1) { - span(0) { - name "HMSET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "HMSET user firstname ? lastname ? age ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "hash getall command"() { - setup: - Map res = syncCommands.hgetall("TESTHM") - - expect: - res == testHashMap - assertTraces(1) { - trace(0, 1) { - span(0) { - name "HGETALL" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "HGETALL TESTHM" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "eval command"() { - given: - def script = "redis.call('lpush', KEYS[1], ARGV[1], ARGV[2]); return redis.call('llen', KEYS[1])" - - when: - def result = syncCommands.eval(script, ScriptOutputType.INTEGER, ["TESTLIST"] as String[], "abc", "def") - - then: - result == 2 - - def b64Script = Base64.encoder.encodeToString(script.getBytes(UTF_8)) - assertTraces(1) { - trace(0, 1) { - span(0) { - name "EVAL" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "EVAL $b64Script 1 TESTLIST ? ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "mset command"() { - when: - def res = syncCommands.mset([ - "key1": "value1", - "key2": "value2" - ]) - - then: - res == "OK" - - assertTraces(1) { - trace(0, 1) { - span(0) { - name "MSET" - kind CLIENT - errored false - attributes { - "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" port - "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" - "${SemanticAttributes.DB_SYSTEM.key}" "redis" - "${SemanticAttributes.DB_STATEMENT.key}" "MSET key1 ? key2 ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "debug segfault command (returns void) with no argument produces no span"() { - setup: - syncCommands.debugSegfault() - - expect: - // lettuce tracing does not trace debug - assertTraces(0) {} - } - - def "shutdown command (returns void) produces no span"() { - setup: - syncCommands.shutdown(false) +import io.opentelemetry.instrumentation.lettuce.v5_1.AbstractLettuceSyncClientTest +import io.opentelemetry.instrumentation.test.AgentTestTrait - expect: - // lettuce tracing does not trace shutdown - assertTraces(0) {} +class LettuceSyncClientTest extends AbstractLettuceSyncClientTest implements AgentTestTrait { + @Override + RedisClient createClient(String uri) { + return RedisClient.create(uri) } } diff --git a/instrumentation/lettuce/lettuce-5.1/library/lettuce-5.1-library.gradle b/instrumentation/lettuce/lettuce-5.1/library/lettuce-5.1-library.gradle new file mode 100644 index 000000000000..3ccfb1525c5f --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/library/lettuce-5.1-library.gradle @@ -0,0 +1,13 @@ +apply from: "$rootDir/gradle/instrumentation-library.gradle" +apply plugin: "net.ltgt.errorprone" + +dependencies { + library group: 'io.lettuce', name: 'lettuce-core', version: '5.1.0.RELEASE' + + implementation project(':instrumentation:lettuce:lettuce-common:library') + + latestDepTestLibrary group: 'io.lettuce', name: 'lettuce-core', version: '5.+' + + testImplementation project(':instrumentation:lettuce:lettuce-5.1:testing') + testImplementation project(':instrumentation:reactor-3.1:library') +} diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTracing.java b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTracing.java new file mode 100644 index 000000000000..b11c9e509962 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTracing.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1; + +import io.lettuce.core.tracing.Tracing; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Tracer; + +/** Entrypoint for tracing Lettuce or clients. */ +public final class LettuceTracing { + + /** Returns a new {@link LettuceTracing} configured with the given {@link OpenTelemetry}. */ + public static LettuceTracing create(OpenTelemetry openTelemetry) { + return new LettuceTracing(openTelemetry); + } + + private final Tracer tracer; + + private LettuceTracing(OpenTelemetry openTelemetry) { + tracer = openTelemetry.getTracer("io.opentelemetry.javaagent.lettuce-5.1"); + } + + /** + * Returns a new {@link Tracing} which can be used with methods like {@link + * io.lettuce.core.resource.ClientResources.Builder#tracing(Tracing)}. + */ + public Tracing newTracing() { + return new OpenTelemetryTracing(tracer); + } +} diff --git a/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java similarity index 76% rename from instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java rename to instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java index 4a017693f11f..dbca3d932f0f 100644 --- a/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java +++ b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java @@ -3,16 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.lettuce.v5_1; +package io.opentelemetry.instrumentation.lettuce.v5_1; -import static io.opentelemetry.javaagent.instrumentation.lettuce.LettuceArgSplitter.splitArgs; +import static io.opentelemetry.instrumentation.lettuce.common.LettuceArgSplitter.splitArgs; import io.lettuce.core.tracing.TraceContext; import io.lettuce.core.tracing.TraceContextProvider; import io.lettuce.core.tracing.Tracer; import io.lettuce.core.tracing.TracerProvider; import io.lettuce.core.tracing.Tracing; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanKind; @@ -30,24 +29,22 @@ import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; -public enum OpenTelemetryTracing implements Tracing { - INSTANCE; +final class OpenTelemetryTracing implements Tracing { - private static final io.opentelemetry.api.trace.Tracer TRACER = - GlobalOpenTelemetry.getTracer("io.opentelemetry.javaagent.lettuce-5.1"); + private final TracerProvider tracerProvider; - public static io.opentelemetry.api.trace.Tracer tracer() { - return TRACER; + OpenTelemetryTracing(io.opentelemetry.api.trace.Tracer tracer) { + this.tracerProvider = new OpenTelemetryTracerProvider(tracer); } @Override public TracerProvider getTracerProvider() { - return OpenTelemetryTracerProvider.INSTANCE; + return tracerProvider; } @Override public TraceContextProvider initialTraceContextProvider() { - return OpenTelemetryTraceContextProvider.INSTANCE; + return new OpenTelemetryTraceContextProvider(); } @Override @@ -62,6 +59,7 @@ public boolean includeCommandArgsInSpanTags() { } @Override + @Nullable public Endpoint createEndpoint(SocketAddress socketAddress) { if (socketAddress instanceof InetSocketAddress) { InetSocketAddress address = (InetSocketAddress) socketAddress; @@ -72,10 +70,13 @@ public Endpoint createEndpoint(SocketAddress socketAddress) { return null; } - private enum OpenTelemetryTracerProvider implements TracerProvider { - INSTANCE; + private static class OpenTelemetryTracerProvider implements TracerProvider { - private final Tracer openTelemetryTracer = new OpenTelemetryTracer(); + private final Tracer openTelemetryTracer; + + OpenTelemetryTracerProvider(io.opentelemetry.api.trace.Tracer tracer) { + openTelemetryTracer = new OpenTelemetryTracer(tracer); + } @Override public Tracer getTracer() { @@ -83,8 +84,7 @@ public Tracer getTracer() { } } - private enum OpenTelemetryTraceContextProvider implements TraceContextProvider { - INSTANCE; + private static class OpenTelemetryTraceContextProvider implements TraceContextProvider { @Override public TraceContext getTraceContext() { @@ -118,11 +118,15 @@ private static class OpenTelemetryEndpoint implements Endpoint { private static class OpenTelemetryTracer extends Tracer { - OpenTelemetryTracer() {} + private final io.opentelemetry.api.trace.Tracer tracer; + + OpenTelemetryTracer(io.opentelemetry.api.trace.Tracer tracer) { + this.tracer = tracer; + } @Override public OpenTelemetrySpan nextSpan() { - return new OpenTelemetrySpan(Context.current()); + return nextSpan(Context.current()); } @Override @@ -132,8 +136,19 @@ public OpenTelemetrySpan nextSpan(TraceContext traceContext) { } Context context = ((OpenTelemetryTraceContext) traceContext).getSpanContext(); + return nextSpan(context); + } - return new OpenTelemetrySpan(context); + private OpenTelemetrySpan nextSpan(Context context) { + // Name will be updated later, we create with an arbitrary one here to store other data before + // the span starts. + SpanBuilder spanBuilder = + tracer + .spanBuilder("redis") + .setSpanKind(SpanKind.CLIENT) + .setParent(context) + .setAttribute(SemanticAttributes.DB_SYSTEM, DbSystemValues.REDIS); + return new OpenTelemetrySpan(spanBuilder); } } @@ -154,15 +169,8 @@ private static class OpenTelemetrySpan extends Tracer.Span { @Nullable private String args; - OpenTelemetrySpan(Context parent) { - // Name will be updated later, we create with an arbitrary one here to store other data before - // the span starts. - spanBuilder = - TRACER - .spanBuilder("redis") - .setSpanKind(SpanKind.CLIENT) - .setParent(parent) - .setAttribute(SemanticAttributes.DB_SYSTEM, DbSystemValues.REDIS); + OpenTelemetrySpan(SpanBuilder spanBuilder) { + this.spanBuilder = spanBuilder; } @Override @@ -259,18 +267,18 @@ public synchronized void finish() { span.end(); } } + } - private static void fillEndpoint(SpanAttributeSetter span, OpenTelemetryEndpoint endpoint) { - span.setAttribute(SemanticAttributes.NET_TRANSPORT, "IP.TCP"); - NetPeerUtils.INSTANCE.setNetPeer(span, endpoint.name, endpoint.ip, endpoint.port); - - StringBuilder redisUrl = - new StringBuilder("redis://").append(endpoint.name != null ? endpoint.name : endpoint.ip); - if (endpoint.port > 0) { - redisUrl.append(":").append(endpoint.port); - } + private static void fillEndpoint(SpanAttributeSetter span, OpenTelemetryEndpoint endpoint) { + span.setAttribute(SemanticAttributes.NET_TRANSPORT, "IP.TCP"); + NetPeerUtils.INSTANCE.setNetPeer(span, endpoint.name, endpoint.ip, endpoint.port); - span.setAttribute(SemanticAttributes.DB_CONNECTION_STRING, redisUrl.toString()); + StringBuilder redisUrl = + new StringBuilder("redis://").append(endpoint.name != null ? endpoint.name : endpoint.ip); + if (endpoint.port > 0) { + redisUrl.append(":").append(endpoint.port); } + + span.setAttribute(SemanticAttributes.DB_CONNECTION_STRING, redisUrl.toString()); } } diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceAsyncSyncClientTest.groovy b/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceAsyncSyncClientTest.groovy new file mode 100644 index 000000000000..f93192e3a88f --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceAsyncSyncClientTest.groovy @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1 + +import io.lettuce.core.RedisClient +import io.lettuce.core.resource.ClientResources +import io.opentelemetry.instrumentation.test.LibraryTestTrait + +class LettuceAsyncSyncClientTest extends AbstractLettuceAsyncClientTest implements LibraryTestTrait { + @Override + RedisClient createClient(String uri) { + return RedisClient.create( + ClientResources.builder() + .tracing(LettuceTracing.create(getOpenTelemetry()).newTracing()) + .build(), + uri) + } +} diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceReactiveClientTest.groovy b/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceReactiveClientTest.groovy new file mode 100644 index 000000000000..2cfb789cccc1 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceReactiveClientTest.groovy @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1 + +import io.lettuce.core.RedisClient +import io.lettuce.core.resource.ClientResources +import io.opentelemetry.instrumentation.reactor.TracingOperator +import io.opentelemetry.instrumentation.test.LibraryTestTrait + +class LettuceReactiveClientTest extends AbstractLettuceReactiveClientTest implements LibraryTestTrait { + @Override + RedisClient createClient(String uri) { + return RedisClient.create( + ClientResources.builder() + .tracing(LettuceTracing.create(getOpenTelemetry()).newTracing()) + .build(), + uri) + } + + def setupSpec() { + TracingOperator.registerOnEachOperator() + } + + def cleanupSpec() { + TracingOperator.resetOnEachOperator() + } +} diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceSyncClientAuthTest.groovy b/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceSyncClientAuthTest.groovy new file mode 100644 index 000000000000..1d770b074e79 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceSyncClientAuthTest.groovy @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1 + +import io.lettuce.core.RedisClient +import io.lettuce.core.resource.ClientResources +import io.opentelemetry.instrumentation.test.LibraryTestTrait + +class LettuceSyncClientAuthTest extends AbstractLettuceSyncClientAuthTest implements LibraryTestTrait { + @Override + RedisClient createClient(String uri) { + return RedisClient.create( + ClientResources.builder() + .tracing(LettuceTracing.create(getOpenTelemetry()).newTracing()) + .build(), + uri) + } +} diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy b/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy new file mode 100644 index 000000000000..79ddc5a95526 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1 + +import io.lettuce.core.RedisClient +import io.lettuce.core.resource.ClientResources +import io.opentelemetry.instrumentation.test.LibraryTestTrait + +class LettuceSyncClientTest extends AbstractLettuceSyncClientTest implements LibraryTestTrait { + @Override + RedisClient createClient(String uri) { + return RedisClient.create( + ClientResources.builder() + .tracing(LettuceTracing.create(getOpenTelemetry()).newTracing()) + .build(), + uri) + } +} diff --git a/instrumentation/lettuce/lettuce-5.1/testing/lettuce-5.1-testing.gradle b/instrumentation/lettuce/lettuce-5.1/testing/lettuce-5.1-testing.gradle new file mode 100644 index 000000000000..f8477467b67b --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/testing/lettuce-5.1-testing.gradle @@ -0,0 +1,15 @@ +apply from: "$rootDir/gradle/java.gradle" + +dependencies { + api project(':testing-common') + + api group: 'io.lettuce', name: 'lettuce-core', version: '5.1.0.RELEASE' + + api group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6' + + implementation deps.guava + + implementation deps.groovy + implementation deps.opentelemetryApi + implementation deps.spock +} diff --git a/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceAsyncClientTest.groovy b/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceAsyncClientTest.groovy new file mode 100644 index 000000000000..81355bc0782d --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceAsyncClientTest.groovy @@ -0,0 +1,398 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1 + +import static io.opentelemetry.api.trace.SpanKind.CLIENT + +import io.lettuce.core.ClientOptions +import io.lettuce.core.ConnectionFuture +import io.lettuce.core.RedisClient +import io.lettuce.core.RedisFuture +import io.lettuce.core.RedisURI +import io.lettuce.core.api.StatefulConnection +import io.lettuce.core.api.async.RedisAsyncCommands +import io.lettuce.core.api.sync.RedisCommands +import io.lettuce.core.codec.StringCodec +import io.opentelemetry.instrumentation.test.InstrumentationSpecification +import io.opentelemetry.instrumentation.test.utils.PortUtils +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import java.util.concurrent.ExecutionException +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 redis.embedded.RedisServer +import spock.lang.Shared +import spock.util.concurrent.AsyncConditions + +abstract class AbstractLettuceAsyncClientTest extends InstrumentationSpecification { + 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 = ClientOptions.builder().autoReconnect(false).build() + + abstract RedisClient createClient(String uri) + + @Shared + int port + @Shared + int incorrectPort + @Shared + String dbAddr + @Shared + String dbAddrNonExistent + @Shared + String dbUriNonExistent + @Shared + String embeddedDbUri + + @Shared + RedisServer redisServer + + @Shared + Map testHashMap = [ + firstname: "John", + lastname : "Doe", + age : "53" + ] + + RedisClient redisClient + StatefulConnection connection + RedisAsyncCommands asyncCommands + RedisCommands 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 = createClient(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 + ignoreTracesAndClear(1) + } + + def cleanup() { + connection.close() + redisServer.stop() + } + + def "connect using get on ConnectionFuture"() { + setup: + RedisClient testConnectionClient = RedisClient.create(embeddedDbUri) + testConnectionClient.setOptions(CLIENT_OPTIONS) + + when: + ConnectionFuture connectionFuture = testConnectionClient.connectAsync(StringCodec.UTF8, + new RedisURI(HOST, port, 3, TimeUnit.SECONDS)) + StatefulConnection connection = connectionFuture.get() + + then: + connection != null + // Lettuce tracing does not trace connect + assertTraces(0) {} + + cleanup: + connection.close() + } + + def "connect exception inside the connection future"() { + setup: + RedisClient testConnectionClient = RedisClient.create(dbUriNonExistent) + testConnectionClient.setOptions(CLIENT_OPTIONS) + + when: + ConnectionFuture connectionFuture = testConnectionClient.connectAsync(StringCodec.UTF8, + new RedisURI(HOST, incorrectPort, 3, TimeUnit.SECONDS)) + StatefulConnection connection = connectionFuture.get() + + then: + connection == null + thrown ExecutionException + // Lettuce tracing does not trace connect + assertTraces(0) {} + } + + def "set command using Future get with timeout"() { + setup: + RedisFuture redisFuture = asyncCommands.set("TESTSETKEY", "TESTSETVAL") + String res = redisFuture.get(3, TimeUnit.SECONDS) + + expect: + res == "OK" + assertTraces(1) { + trace(0, 1) { + span(0) { + name "SET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "SET TESTSETKEY ?" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "get command chained with thenAccept"() { + setup: + def conds = new AsyncConditions() + Consumer consumer = new Consumer() { + @Override + void accept(String res) { + conds.evaluate { + assert res == "TESTVAL" + } + } + } + + when: + RedisFuture redisFuture = asyncCommands.get("TESTKEY") + redisFuture.thenAccept(consumer) + + then: + conds.await() + assertTraces(1) { + trace(0, 1) { + span(0) { + name "GET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "GET TESTKEY" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + // 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() + String successStr = "KEY MISSING" + BiFunction firstStage = new BiFunction() { + @Override + String apply(String res, Throwable throwable) { + conds.evaluate { + assert res == null + assert throwable == null + } + return (res == null ? successStr : res) + } + } + Function secondStage = new Function() { + @Override + Object apply(String input) { + conds.evaluate { + assert input == successStr + } + return null + } + } + + when: + RedisFuture redisFuture = asyncCommands.get("NON_EXISTENT_KEY") + redisFuture.handleAsync(firstStage).thenApply(secondStage) + + then: + conds.await() + assertTraces(1) { + trace(0, 1) { + span(0) { + name "GET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "GET NON_EXISTENT_KEY" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "command with no arguments using a biconsumer"() { + setup: + def conds = new AsyncConditions() + BiConsumer biConsumer = new BiConsumer() { + @Override + void accept(String keyRetrieved, Throwable throwable) { + conds.evaluate { + assert keyRetrieved != null + } + } + } + + when: + RedisFuture redisFuture = asyncCommands.randomkey() + redisFuture.whenCompleteAsync(biConsumer) + + then: + conds.await() + assertTraces(1) { + trace(0, 1) { + span(0) { + name "RANDOMKEY" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_STATEMENT.key}" "RANDOMKEY" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "hash set and then nest apply to hash getall"() { + setup: + def conds = new AsyncConditions() + + when: + RedisFuture hmsetFuture = asyncCommands.hmset("TESTHM", testHashMap) + hmsetFuture.thenApplyAsync(new Function() { + @Override + Object apply(String setResult) { + conds.evaluate { + assert setResult == "OK" + } + RedisFuture> hmGetAllFuture = asyncCommands.hgetall("TESTHM") + hmGetAllFuture.exceptionally(new Function>() { + @Override + Map apply(Throwable throwable) { + println("unexpected:" + throwable.toString()) + throwable.printStackTrace() + assert false + return null + } + }) + hmGetAllFuture.thenAccept(new Consumer>() { + @Override + void accept(Map hmGetAllResult) { + conds.evaluate { + assert testHashMap == hmGetAllResult + } + } + }) + return null + } + }) + + then: + conds.await() + assertTraces(2) { + trace(0, 1) { + span(0) { + name "HMSET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "HMSET TESTHM firstname ? lastname ? age ?" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + trace(1, 1) { + span(0) { + name "HGETALL" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "HGETALL TESTHM" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } +} diff --git a/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceReactiveClientTest.groovy b/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceReactiveClientTest.groovy new file mode 100644 index 000000000000..fc650f393449 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceReactiveClientTest.groovy @@ -0,0 +1,399 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1 + +import static io.opentelemetry.api.trace.SpanKind.CLIENT +import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace + +import io.lettuce.core.ClientOptions +import io.lettuce.core.RedisClient +import io.lettuce.core.api.StatefulConnection +import io.lettuce.core.api.reactive.RedisReactiveCommands +import io.lettuce.core.api.sync.RedisCommands +import io.opentelemetry.instrumentation.test.InstrumentationSpecification +import io.opentelemetry.instrumentation.test.utils.PortUtils +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import java.util.function.Consumer +import redis.embedded.RedisServer +import spock.lang.Shared +import spock.util.concurrent.AsyncConditions + +abstract class AbstractLettuceReactiveClientTest extends InstrumentationSpecification { + 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 = ClientOptions.builder().autoReconnect(false).build() + + abstract RedisClient createClient(String uri) + + @Shared + int port + @Shared + String embeddedDbUri + + @Shared + RedisServer redisServer + + RedisClient redisClient + StatefulConnection connection + RedisReactiveCommands reactiveCommands + RedisCommands syncCommands + + def setupSpec() { + port = PortUtils.randomOpenPort() + String dbAddr = HOST + ":" + port + "/" + DB_INDEX + 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 = createClient(embeddedDbUri) + + println "Using redis: $redisServer.args" + redisServer.start() + redisClient.setOptions(CLIENT_OPTIONS) + + connection = redisClient.connect() + reactiveCommands = connection.reactive() + syncCommands = connection.sync() + + syncCommands.set("TESTKEY", "TESTVAL") + + // 1 set + ignoreTracesAndClear(1) + } + + def cleanup() { + connection.close() + redisClient.shutdown() + redisServer.stop() + } + + def "set command with subscribe on a defined consumer"() { + setup: + def conds = new AsyncConditions() + Consumer consumer = new Consumer() { + @Override + void accept(String res) { + conds.evaluate { + assert res == "OK" + } + } + } + + when: + reactiveCommands.set("TESTSETKEY", "TESTSETVAL").subscribe(consumer) + + then: + conds.await() + assertTraces(1) { + trace(0, 1) { + span(0) { + name "SET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "SET TESTSETKEY ?" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "get command with lambda function"() { + setup: + def conds = new AsyncConditions() + + when: + reactiveCommands.get("TESTKEY").subscribe { res -> conds.evaluate { assert res == "TESTVAL" } } + + then: + conds.await() + assertTraces(1) { + trace(0, 1) { + span(0) { + name "GET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "GET TESTKEY" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + // to make sure instrumentation's chained completion stages won't interfere with user's, while still + // recording metrics + def "get non existent key command"() { + setup: + def conds = new AsyncConditions() + final defaultVal = "NOT THIS VALUE" + + when: + reactiveCommands.get("NON_EXISTENT_KEY").defaultIfEmpty(defaultVal).subscribe { + res -> + conds.evaluate { + assert res == defaultVal + } + } + + then: + conds.await() + assertTraces(1) { + trace(0, 1) { + span(0) { + name "GET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "GET NON_EXISTENT_KEY" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + + } + + def "command with no arguments"() { + setup: + def conds = new AsyncConditions() + + when: + reactiveCommands.randomkey().subscribe { + res -> + conds.evaluate { + assert res == "TESTKEY" + } + } + + then: + conds.await() + assertTraces(1) { + trace(0, 1) { + span(0) { + name "RANDOMKEY" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_STATEMENT.key}" "RANDOMKEY" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "command flux publisher "() { + setup: + reactiveCommands.command().subscribe() + + expect: + assertTraces(1) { + trace(0, 1) { + span(0) { + name "COMMAND" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_STATEMENT.key}" "COMMAND" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "non reactive command should not produce span"() { + when: + def res = reactiveCommands.digest() + + then: + res != null + traces.size() == 0 + } + + def "blocking subscriber"() { + when: + runUnderTrace("test-parent") { + reactiveCommands.set("a", "1") + .then(reactiveCommands.get("a")) + .block() + } + + then: + assertTraces(1) { + trace(0, 3) { + span(0) { + name "test-parent" + errored false + attributes { + } + } + span(1) { + name "SET" + kind CLIENT + errored false + childOf span(0) + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "SET a ?" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + span(2) { + name "GET" + kind CLIENT + errored false + childOf span(0) + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "GET a" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "async subscriber"() { + when: + runUnderTrace("test-parent") { + reactiveCommands.set("a", "1") + .then(reactiveCommands.get("a")) + .subscribe() + } + + then: + assertTraces(1) { + trace(0, 3) { + span(0) { + name "test-parent" + errored false + attributes { + } + } + span(1) { + name "SET" + kind CLIENT + errored false + childOf span(0) + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "SET a ?" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + span(2) { + name "GET" + kind CLIENT + errored false + childOf span(0) + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "GET a" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } +} diff --git a/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientAuthTest.groovy b/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientAuthTest.groovy new file mode 100644 index 000000000000..55310c58bbc1 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientAuthTest.groovy @@ -0,0 +1,96 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1 + +import static io.opentelemetry.api.trace.SpanKind.CLIENT + +import io.lettuce.core.ClientOptions +import io.lettuce.core.RedisClient +import io.opentelemetry.instrumentation.test.InstrumentationSpecification +import io.opentelemetry.instrumentation.test.utils.PortUtils +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import redis.embedded.RedisServer +import spock.lang.Shared + +abstract class AbstractLettuceSyncClientAuthTest extends InstrumentationSpecification { + 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 = ClientOptions.builder().autoReconnect(false).build() + + abstract RedisClient createClient(String uri) + + @Shared + int port + @Shared + String password + @Shared + String dbAddr + @Shared + String embeddedDbUri + + @Shared + RedisServer redisServer + + RedisClient redisClient + + def setupSpec() { + port = PortUtils.randomOpenPort() + dbAddr = HOST + ":" + port + "/" + DB_INDEX + embeddedDbUri = "redis://" + dbAddr + password = "password" + + redisServer = RedisServer.builder() + // bind to localhost to avoid firewall popup + .setting("bind " + HOST) + // set max memory to avoid problems in CI + .setting("maxmemory 128M") + // Set password + .setting("requirepass " + password) + .port(port).build() + } + + def setup() { + redisClient = createClient(embeddedDbUri) + redisClient.setOptions(CLIENT_OPTIONS) + redisServer.start() + } + + def cleanup() { + redisServer.stop() + } + + def "auth command"() { + setup: + def res = redisClient.connect().sync().auth(password) + + expect: + res == "OK" + assertTraces(1) { + trace(0, 1) { + span(0) { + name "AUTH" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "AUTH ?" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } +} diff --git a/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.groovy b/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.groovy new file mode 100644 index 000000000000..db2d1b100753 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.groovy @@ -0,0 +1,466 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1 + +import static io.opentelemetry.api.trace.SpanKind.CLIENT +import static java.nio.charset.StandardCharsets.UTF_8 + +import io.lettuce.core.ClientOptions +import io.lettuce.core.RedisClient +import io.lettuce.core.RedisConnectionException +import io.lettuce.core.ScriptOutputType +import io.lettuce.core.api.StatefulConnection +import io.lettuce.core.api.sync.RedisCommands +import io.opentelemetry.instrumentation.test.InstrumentationSpecification +import io.opentelemetry.instrumentation.test.utils.PortUtils +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import redis.embedded.RedisServer +import spock.lang.Shared + +abstract class AbstractLettuceSyncClientTest extends InstrumentationSpecification { + 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 = ClientOptions.builder().autoReconnect(false).build() + + abstract RedisClient createClient(String uri) + + @Shared + int port + @Shared + int incorrectPort + @Shared + String dbAddr + @Shared + String dbAddrNonExistent + @Shared + String dbUriNonExistent + @Shared + String embeddedDbUri + @Shared + String embeddedDbLocalhostUri + + @Shared + RedisServer redisServer + + @Shared + Map testHashMap = [ + firstname: "John", + lastname : "Doe", + age : "53" + ] + + RedisClient redisClient + StatefulConnection connection + RedisCommands 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 + embeddedDbLocalhostUri = "redis://localhost:" + port + "/" + DB_INDEX + + 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 = createClient(embeddedDbUri) + + redisServer.start() + connection = redisClient.connect() + syncCommands = connection.sync() + + syncCommands.set("TESTKEY", "TESTVAL") + syncCommands.hmset("TESTHM", testHashMap) + + // 2 sets + ignoreTracesAndClear(2) + } + + def cleanup() { + connection.close() + redisServer.stop() + } + + def "connect"() { + setup: + RedisClient testConnectionClient = createClient(embeddedDbUri) + testConnectionClient.setOptions(CLIENT_OPTIONS) + + when: + StatefulConnection connection = testConnectionClient.connect() + + then: + // Lettuce tracing does not trace connect + assertTraces(0) {} + + cleanup: + connection.close() + } + + def "connect exception"() { + setup: + RedisClient testConnectionClient = createClient(dbUriNonExistent) + testConnectionClient.setOptions(CLIENT_OPTIONS) + + when: + testConnectionClient.connect() + + then: + thrown RedisConnectionException + // Lettuce tracing does not trace connect + assertTraces(0) {} + } + + def "set command"() { + setup: + String res = syncCommands.set("TESTSETKEY", "TESTSETVAL") + + expect: + res == "OK" + assertTraces(1) { + trace(0, 1) { + span(0) { + name "SET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "SET TESTSETKEY ?" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "set command localhost"() { + setup: + RedisClient testConnectionClient = createClient(embeddedDbLocalhostUri) + testConnectionClient.setOptions(CLIENT_OPTIONS) + StatefulConnection connection = testConnectionClient.connect() + String res = connection.sync().set("TESTSETKEY", "TESTSETVAL") + + expect: + res == "OK" + assertTraces(1) { + trace(0, 1) { + span(0) { + name "SET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_NAME.key}" "localhost" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://localhost:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "SET TESTSETKEY ?" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "get command"() { + setup: + String res = syncCommands.get("TESTKEY") + + expect: + res == "TESTVAL" + assertTraces(1) { + trace(0, 1) { + span(0) { + name "GET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "GET TESTKEY" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "get non existent key command"() { + setup: + String res = syncCommands.get("NON_EXISTENT_KEY") + + expect: + res == null + assertTraces(1) { + trace(0, 1) { + span(0) { + name "GET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "GET NON_EXISTENT_KEY" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "command with no arguments"() { + setup: + def keyRetrieved = syncCommands.randomkey() + + expect: + keyRetrieved != null + assertTraces(1) { + trace(0, 1) { + span(0) { + name "RANDOMKEY" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_STATEMENT.key}" "RANDOMKEY" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "list command"() { + setup: + long res = syncCommands.lpush("TESTLIST", "TESTLIST ELEMENT") + + expect: + res == 1 + assertTraces(1) { + trace(0, 1) { + span(0) { + name "LPUSH" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "LPUSH TESTLIST ?" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "hash set command"() { + setup: + def res = syncCommands.hmset("user", testHashMap) + + expect: + res == "OK" + assertTraces(1) { + trace(0, 1) { + span(0) { + name "HMSET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "HMSET user firstname ? lastname ? age ?" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "hash getall command"() { + setup: + Map res = syncCommands.hgetall("TESTHM") + + expect: + res == testHashMap + assertTraces(1) { + trace(0, 1) { + span(0) { + name "HGETALL" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "HGETALL TESTHM" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "eval command"() { + given: + def script = "redis.call('lpush', KEYS[1], ARGV[1], ARGV[2]); return redis.call('llen', KEYS[1])" + + when: + def result = syncCommands.eval(script, ScriptOutputType.INTEGER, ["TESTLIST"] as String[], "abc", "def") + + then: + result == 2 + + def b64Script = Base64.encoder.encodeToString(script.getBytes(UTF_8)) + assertTraces(1) { + trace(0, 1) { + span(0) { + name "EVAL" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "EVAL $b64Script 1 TESTLIST ? ?" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "mset command"() { + when: + def res = syncCommands.mset([ + "key1": "value1", + "key2": "value2" + ]) + + then: + res == "OK" + + assertTraces(1) { + trace(0, 1) { + span(0) { + name "MSET" + kind CLIENT + errored false + attributes { + "${SemanticAttributes.NET_TRANSPORT.key}" "IP.TCP" + "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" + "${SemanticAttributes.NET_PEER_PORT.key}" port + "${SemanticAttributes.DB_CONNECTION_STRING.key}" "redis://127.0.0.1:$port" + "${SemanticAttributes.DB_SYSTEM.key}" "redis" + "${SemanticAttributes.DB_STATEMENT.key}" "MSET key1 ? key2 ?" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + } + } + } + } + + def "debug segfault command (returns void) with no argument produces no span"() { + setup: + syncCommands.debugSegfault() + + expect: + // lettuce tracing does not trace debug + assertTraces(0) {} + } + + def "shutdown command (returns void) produces no span"() { + setup: + syncCommands.shutdown(false) + + expect: + // lettuce tracing does not trace shutdown + assertTraces(0) {} + } +} diff --git a/instrumentation/lettuce/lettuce-common/javaagent/lettuce-common-javaagent.gradle b/instrumentation/lettuce/lettuce-common/library/lettuce-common-library.gradle similarity index 100% rename from instrumentation/lettuce/lettuce-common/javaagent/lettuce-common-javaagent.gradle rename to instrumentation/lettuce/lettuce-common/library/lettuce-common-library.gradle diff --git a/instrumentation/lettuce/lettuce-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/LettuceArgSplitter.java b/instrumentation/lettuce/lettuce-common/library/src/main/java/io/opentelemetry/instrumentation/lettuce/common/LettuceArgSplitter.java similarity index 94% rename from instrumentation/lettuce/lettuce-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/LettuceArgSplitter.java rename to instrumentation/lettuce/lettuce-common/library/src/main/java/io/opentelemetry/instrumentation/lettuce/common/LettuceArgSplitter.java index 68087c078abd..39ab126fde67 100644 --- a/instrumentation/lettuce/lettuce-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/LettuceArgSplitter.java +++ b/instrumentation/lettuce/lettuce-common/library/src/main/java/io/opentelemetry/instrumentation/lettuce/common/LettuceArgSplitter.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.lettuce; +package io.opentelemetry.instrumentation.lettuce.common; import java.util.ArrayList; import java.util.Collections; diff --git a/instrumentation/lettuce/lettuce-common/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/LettuceArgSplitterTest.groovy b/instrumentation/lettuce/lettuce-common/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/common/LettuceArgSplitterTest.groovy similarity index 94% rename from instrumentation/lettuce/lettuce-common/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/LettuceArgSplitterTest.groovy rename to instrumentation/lettuce/lettuce-common/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/common/LettuceArgSplitterTest.groovy index 4c3bb91a6e06..a708f83ee4b2 100644 --- a/instrumentation/lettuce/lettuce-common/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/LettuceArgSplitterTest.groovy +++ b/instrumentation/lettuce/lettuce-common/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/common/LettuceArgSplitterTest.groovy @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.lettuce +package io.opentelemetry.instrumentation.lettuce.common import spock.lang.Specification import spock.lang.Unroll diff --git a/settings.gradle b/settings.gradle index dd9c0527b24b..ef012b97b1d8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -157,10 +157,12 @@ include ':instrumentation:khttp-0.1:javaagent' include ':instrumentation:kotlinx-coroutines:javaagent' include ':instrumentation:kubernetes-client-7.0:javaagent' include ':instrumentation:kubernetes-client-7.0:javaagent-unittests' -include ':instrumentation:lettuce:lettuce-common:javaagent' +include ':instrumentation:lettuce:lettuce-common:library' include ':instrumentation:lettuce:lettuce-4.0:javaagent' include ':instrumentation:lettuce:lettuce-5.0:javaagent' include ':instrumentation:lettuce:lettuce-5.1:javaagent' +include ':instrumentation:lettuce:lettuce-5.1:library' +include ':instrumentation:lettuce:lettuce-5.1:testing' include ':instrumentation:liberty:compile-stub' include ':instrumentation:liberty:liberty:javaagent' include ':instrumentation:liberty:liberty-dispatcher:javaagent'