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 8ee3e3fd96ff..2af1d9183621 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 @@ -4,7 +4,7 @@ muzzle { pass { group = "io.lettuce" module = "lettuce-core" - versions = "[5.1.0.RELEASE,6.0.0.RELEASE)" + versions = "[5.1.0.RELEASE,)" assertInverse = true } } @@ -20,6 +20,8 @@ dependencies { // Only 5.2+ will have command arguments in the db.statement tag. testLibrary group: 'io.lettuce', name: 'lettuce-core', version: '5.2.0.RELEASE' testInstrumentation project(':instrumentation:reactor-3.1:javaagent') +} - latestDepTestLibrary group: 'io.lettuce', name: 'lettuce-core', version: '5.+' +test { + systemProperty "testLatestDeps", testLatestDeps } 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 index 3ccfb1525c5f..154770f682c8 100644 --- a/instrumentation/lettuce/lettuce-5.1/library/lettuce-5.1-library.gradle +++ b/instrumentation/lettuce/lettuce-5.1/library/lettuce-5.1-library.gradle @@ -6,8 +6,10 @@ dependencies { 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') } + +test { + systemProperty "testLatestDeps", testLatestDeps +} diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java index 5b826d82c1f2..97e117fd9a00 100644 --- a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java +++ b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java @@ -7,6 +7,9 @@ import static io.opentelemetry.instrumentation.lettuce.common.LettuceArgSplitter.splitArgs; +import io.lettuce.core.output.CommandOutput; +import io.lettuce.core.protocol.CompleteableCommand; +import io.lettuce.core.protocol.RedisCommand; import io.lettuce.core.tracing.TraceContext; import io.lettuce.core.tracing.TraceContextProvider; import io.lettuce.core.tracing.Tracer; @@ -52,7 +55,7 @@ public boolean isEnabled() { return true; } - // Added in lettuce 5.2 + // Added in lettuce 5.2, ignored in 6.0+ // @Override public boolean includeCommandArgsInSpanTags() { return true; @@ -196,6 +199,45 @@ public synchronized Tracer.Span remoteEndpoint(Endpoint endpoint) { return this; } + // Added and called in 6.0+ + // @Override + public synchronized Tracer.Span start(RedisCommand command) { + start(); + + Span span = this.span; + if (span == null) { + throw new IllegalStateException("Span started but null, this is a programming error."); + } + span.updateName(command.getType().name()); + + if (command.getArgs() != null) { + args = command.getArgs().toCommandString(); + } + + if (command instanceof CompleteableCommand) { + CompleteableCommand completeableCommand = (CompleteableCommand) command; + completeableCommand.onComplete( + (o, throwable) -> { + if (throwable != null) { + span.recordException(throwable); + } + + CommandOutput output = command.getOutput(); + if (output != null) { + String error = output.getError(); + if (error != null) { + span.setStatus(StatusCode.ERROR, error); + } + } + + finish(span); + }); + } + + return this; + } + + // Not called by Lettuce in 6.0+ (though we call it ourselves above). @Override public synchronized Tracer.Span start() { span = spanBuilder.startSpan(); @@ -260,12 +302,16 @@ public synchronized Tracer.Span error(Throwable throwable) { @Override public synchronized void finish() { if (span != null) { - if (name != null) { - String statement = RedisCommandSanitizer.sanitize(name, splitArgs(args)); - span.setAttribute(SemanticAttributes.DB_STATEMENT, statement); - } - span.end(); + finish(span); + } + } + + private void finish(Span span) { + if (name != null) { + String statement = RedisCommandSanitizer.sanitize(name, splitArgs(args)); + span.setAttribute(SemanticAttributes.DB_STATEMENT, statement); } + span.end(); } } 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 index 81355bc0782d..704b8092ffbe 100644 --- 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 @@ -7,7 +7,6 @@ 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 @@ -32,8 +31,6 @@ 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) @@ -86,7 +83,7 @@ abstract class AbstractLettuceAsyncClientTest extends InstrumentationSpecificati println "Using redis: $redisServer.args" redisServer.start() - redisClient.setOptions(CLIENT_OPTIONS) + redisClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS) connection = redisClient.connect() asyncCommands = connection.async() @@ -106,11 +103,11 @@ abstract class AbstractLettuceAsyncClientTest extends InstrumentationSpecificati def "connect using get on ConnectionFuture"() { setup: RedisClient testConnectionClient = RedisClient.create(embeddedDbUri) - testConnectionClient.setOptions(CLIENT_OPTIONS) + testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS) when: ConnectionFuture connectionFuture = testConnectionClient.connectAsync(StringCodec.UTF8, - new RedisURI(HOST, port, 3, TimeUnit.SECONDS)) + RedisURI.create("redis://${HOST}:${port}?timeout=3s")) StatefulConnection connection = connectionFuture.get() then: @@ -125,11 +122,11 @@ abstract class AbstractLettuceAsyncClientTest extends InstrumentationSpecificati def "connect exception inside the connection future"() { setup: RedisClient testConnectionClient = RedisClient.create(dbUriNonExistent) - testConnectionClient.setOptions(CLIENT_OPTIONS) + testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS) when: ConnectionFuture connectionFuture = testConnectionClient.connectAsync(StringCodec.UTF8, - new RedisURI(HOST, incorrectPort, 3, TimeUnit.SECONDS)) + RedisURI.create("redis://${HOST}:${incorrectPort}?timeout=3s")) StatefulConnection connection = connectionFuture.get() then: 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 index fc650f393449..73617ac1b6a5 100644 --- 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 @@ -8,7 +8,6 @@ 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 @@ -24,8 +23,6 @@ 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) @@ -60,7 +57,7 @@ abstract class AbstractLettuceReactiveClientTest extends InstrumentationSpecific println "Using redis: $redisServer.args" redisServer.start() - redisClient.setOptions(CLIENT_OPTIONS) + redisClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS) connection = redisClient.connect() reactiveCommands = connection.reactive() 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 index 55310c58bbc1..48423383b74a 100644 --- 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 @@ -7,7 +7,6 @@ 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 @@ -18,8 +17,6 @@ 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) @@ -55,7 +52,7 @@ abstract class AbstractLettuceSyncClientAuthTest extends InstrumentationSpecific def setup() { redisClient = createClient(embeddedDbUri) - redisClient.setOptions(CLIENT_OPTIONS) + redisClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS) redisServer.start() } 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 index db2d1b100753..d64d7a1c509c 100644 --- 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 @@ -8,9 +8,9 @@ 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.RedisException import io.lettuce.core.ScriptOutputType import io.lettuce.core.api.StatefulConnection import io.lettuce.core.api.sync.RedisCommands @@ -23,8 +23,6 @@ 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) @@ -76,6 +74,7 @@ abstract class AbstractLettuceSyncClientTest extends InstrumentationSpecificatio def setup() { redisClient = createClient(embeddedDbUri) + redisClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS) redisServer.start() connection = redisClient.connect() @@ -96,7 +95,7 @@ abstract class AbstractLettuceSyncClientTest extends InstrumentationSpecificatio def "connect"() { setup: RedisClient testConnectionClient = createClient(embeddedDbUri) - testConnectionClient.setOptions(CLIENT_OPTIONS) + testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS) when: StatefulConnection connection = testConnectionClient.connect() @@ -112,7 +111,7 @@ abstract class AbstractLettuceSyncClientTest extends InstrumentationSpecificatio def "connect exception"() { setup: RedisClient testConnectionClient = createClient(dbUriNonExistent) - testConnectionClient.setOptions(CLIENT_OPTIONS) + testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS) when: testConnectionClient.connect() @@ -157,7 +156,7 @@ abstract class AbstractLettuceSyncClientTest extends InstrumentationSpecificatio def "set command localhost"() { setup: RedisClient testConnectionClient = createClient(embeddedDbLocalhostUri) - testConnectionClient.setOptions(CLIENT_OPTIONS) + testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS) StatefulConnection connection = testConnectionClient.connect() String res = connection.sync().set("TESTSETKEY", "TESTSETVAL") @@ -451,8 +450,33 @@ abstract class AbstractLettuceSyncClientTest extends InstrumentationSpecificatio syncCommands.debugSegfault() expect: - // lettuce tracing does not trace debug - assertTraces(0) {} + assertTraces(1) { + trace(0, 1) { + span(0) { + name "DEBUG" + // Disconnect not an actual error even though an exception is recorded. + 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}" "DEBUG SEGFAULT" + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + if (Boolean.getBoolean("testLatestDeps")) { + // Seems to only be recorded with Lettuce 6+ + errorEvent(RedisException, "Connection disconnected", 2) + } + } + } + } } def "shutdown command (returns void) produces no span"() { @@ -460,7 +484,38 @@ abstract class AbstractLettuceSyncClientTest extends InstrumentationSpecificatio syncCommands.shutdown(false) expect: - // lettuce tracing does not trace shutdown - assertTraces(0) {} + assertTraces(1) { + trace(0, 1) { + span(0) { + name "SHUTDOWN" + if (Boolean.getBoolean("testLatestDeps")) { + // Seems to only be treated as an error with Lettuce 6+ + errored true + } + 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}" "SHUTDOWN NOSAVE" + if (!Boolean.getBoolean("testLatestDeps")) { + // Lettuce adds this tag before 6.0 + // TODO(anuraaga): Filter this out? + "error" "Connection disconnected" + } + } + event(0) { + eventName "redis.encode.start" + } + event(1) { + eventName "redis.encode.end" + } + if (Boolean.getBoolean("testLatestDeps")) { + errorEvent(RedisException, "Connection disconnected", 2) + } + } + } + } } } diff --git a/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTestUtil.groovy b/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTestUtil.groovy new file mode 100644 index 000000000000..7e90d682161a --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTestUtil.groovy @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1 + +import groovy.transform.PackageScope +import io.lettuce.core.ClientOptions + +@PackageScope +final class LettuceTestUtil { + + static final ClientOptions CLIENT_OPTIONS + + static { + def options = ClientOptions.builder() + // Disable autoreconnect so we do not get stray traces popping up on server shutdown + .autoReconnect(false) + if (Boolean.getBoolean("testLatestDeps")) { + // Force RESP2 on 6+ for consistency in tests + options + .pingBeforeActivateConnection(false) + .protocolVersion(Class.forName("io.lettuce.core.protocol.ProtocolVersion").getField("RESP2").get(null)) + } + CLIENT_OPTIONS = options.build() + } + + private LettuceTestUtil() {} +}