Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions instrumentation/lettuce/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Settings for the Lettuce instrumentation

| System property | Type | Default | Description |
|-------------------------------------------------------------|---------|---------|-----------------------------------------------------|
| `otel.instrumentation.lettuce.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. |
| `otel.instrumentation.lettuce.connection-telemetry.enabled` | Boolean | `false` | Enable the creation of Connect spans. |
| System property | Type | Default | Description |
|-----------------------------------------------------------------------------|---------|---------|--------------------------------------------------------------------------------|
| `otel.instrumentation.lettuce.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. |
| `otel.instrumentation.lettuce.connection-telemetry.enabled` | Boolean | `false` | Enable the creation of Connect spans. |
| `otel.instrumentation.lettuce.experimental.command-encoding-events.enabled` | Boolean | `false` | Enable the capture of `redis.encode.start` and `redis.encode.end` span events. |
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ dependencies {

tasks {
withType<Test>().configureEach {
// TODO run tests both with and without experimental span attributes
// TODO run tests both with and without experimental span attributes and span events
jvmArgs("-Dotel.instrumentation.lettuce.experimental-span-attributes=true")
jvmArgs("-Dotel.instrumentation.lettuce.connection-telemetry.enabled=true")
jvmArgs("-Dotel.instrumentation.lettuce.experimental.command-encoding-events.enabled=true")
usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ dependencies {

tasks {
withType<Test>().configureEach {
// TODO run tests both with and without experimental span attributes
// TODO run tests both with and without experimental span attributes and span events
jvmArgs("-Dotel.instrumentation.lettuce.experimental-span-attributes=true")
jvmArgs("-Dotel.instrumentation.lettuce.connection-telemetry.enabled=true")
jvmArgs("-Dotel.instrumentation.lettuce.experimental.command-encoding-events.enabled=true")
usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,19 @@
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.lettuce.v5_1.LettuceTelemetry;
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig;

public final class TracingHolder {

private static final boolean CAPTURE_COMMAND_ENCODING_EVENTS =
AgentInstrumentationConfig.get()
.getBoolean(
"otel.instrumentation.lettuce.experimental.command-encoding-events.enabled", false);

public static final Tracing TRACING =
LettuceTelemetry.builder(GlobalOpenTelemetry.get())
.setStatementSanitizationEnabled(AgentCommonConfig.get().isStatementSanitizationEnabled())
.setEncodingSpanEventsEnabled(CAPTURE_COMMAND_ENCODING_EVENTS)
.build()
.newTracing();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.lettuce.core.RedisClient;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.lettuce.v5_1.AbstractLettuceClientTest;
import io.opentelemetry.instrumentation.lettuce.v5_1.AbstractLettuceReactiveClientTest;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
Expand Down Expand Up @@ -66,9 +67,7 @@ void testAsyncSubscriberWithSpecificThreadPool() {
equalTo(SERVER_PORT, port),
equalTo(maybeStable(DB_SYSTEM), "redis"),
equalTo(maybeStable(DB_STATEMENT), "SET a ?")))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end")),
.satisfies(AbstractLettuceClientTest::assertCommandEncodeEvents),
span ->
span.hasName("GET")
.hasKind(SpanKind.CLIENT)
Expand All @@ -82,8 +81,6 @@ void testAsyncSubscriberWithSpecificThreadPool() {
equalTo(SERVER_PORT, port),
equalTo(maybeStable(DB_SYSTEM), "redis"),
equalTo(maybeStable(DB_STATEMENT), "GET a")))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))));
.satisfies(AbstractLettuceClientTest::assertCommandEncodeEvents)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ public static LettuceTelemetryBuilder builder(OpenTelemetry openTelemetry) {
private final Tracer tracer;
private final RedisCommandSanitizer sanitizer;
private final OperationListener metrics;
private final boolean encodingEventsEnabled;

LettuceTelemetry(
OpenTelemetry openTelemetry,
boolean statementSanitizationEnabled,
boolean encodingEventsEnabled,
OperationListener metrics) {
this.metrics = metrics;
TracerBuilder tracerBuilder = openTelemetry.tracerBuilder(INSTRUMENTATION_NAME);
Expand All @@ -46,13 +48,14 @@ public static LettuceTelemetryBuilder builder(OpenTelemetry openTelemetry) {
}
tracer = tracerBuilder.build();
sanitizer = RedisCommandSanitizer.create(statementSanitizationEnabled);
this.encodingEventsEnabled = encodingEventsEnabled;
}

/**
* 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, sanitizer, metrics);
return new OpenTelemetryTracing(tracer, sanitizer, metrics, encodingEventsEnabled);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public final class LettuceTelemetryBuilder {
private final OpenTelemetry openTelemetry;

private boolean statementSanitizationEnabled = true;
private boolean encodingEventsEnabled = false;

LettuceTelemetryBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
Expand All @@ -34,6 +35,16 @@ public LettuceTelemetryBuilder setStatementSanitizationEnabled(
return this;
}

/**
* Sets whether the {@code redis.encode.start} and {@code redis.encode.end} span events should be
* emitted by the constructed {@link LettuceTelemetry}. Disabled by default.
*/
@CanIgnoreReturnValue
public LettuceTelemetryBuilder setEncodingSpanEventsEnabled(boolean encodingEventsEnabled) {
this.encodingEventsEnabled = encodingEventsEnabled;
return this;
}

/**
* Returns a new {@link LettuceTelemetry} with the settings of this {@link
* LettuceTelemetryBuilder}.
Expand All @@ -42,6 +53,7 @@ public LettuceTelemetry build() {
return new LettuceTelemetry(
openTelemetry,
statementSanitizationEnabled,
encodingEventsEnabled,
DbClientMetrics.get().create(openTelemetry.getMeterProvider().get(INSTRUMENTATION_NAME)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ final class OpenTelemetryTracing implements Tracing {
OpenTelemetryTracing(
io.opentelemetry.api.trace.Tracer tracer,
RedisCommandSanitizer sanitizer,
OperationListener metrics) {
this.tracerProvider = new OpenTelemetryTracerProvider(tracer, sanitizer, metrics);
OperationListener metrics,
boolean encodingEventsEnabled) {
this.tracerProvider =
new OpenTelemetryTracerProvider(tracer, sanitizer, metrics, encodingEventsEnabled);
}

@Override
Expand Down Expand Up @@ -100,8 +102,10 @@ private static class OpenTelemetryTracerProvider implements TracerProvider {
OpenTelemetryTracerProvider(
io.opentelemetry.api.trace.Tracer tracer,
RedisCommandSanitizer sanitizer,
OperationListener metrics) {
openTelemetryTracer = new OpenTelemetryTracer(tracer, sanitizer, metrics);
OperationListener metrics,
boolean encodingEventsEnabled) {
openTelemetryTracer =
new OpenTelemetryTracer(tracer, sanitizer, metrics, encodingEventsEnabled);
}

@Override
Expand Down Expand Up @@ -143,14 +147,17 @@ private static class OpenTelemetryTracer extends Tracer {
private final io.opentelemetry.api.trace.Tracer tracer;
private final RedisCommandSanitizer sanitizer;
private final OperationListener metrics;
private final boolean encodingEventsEnabled;

OpenTelemetryTracer(
io.opentelemetry.api.trace.Tracer tracer,
RedisCommandSanitizer sanitizer,
OperationListener metrics) {
OperationListener metrics,
boolean encodingEventsEnabled) {
this.tracer = tracer;
this.sanitizer = sanitizer;
this.metrics = metrics;
this.encodingEventsEnabled = encodingEventsEnabled;
}

@Override
Expand Down Expand Up @@ -179,7 +186,7 @@ private OpenTelemetrySpan nextSpan(Context context) {
if (SemconvStability.emitOldDatabaseSemconv()) {
spanBuilder.setAttribute(DB_SYSTEM, REDIS);
}
return new OpenTelemetrySpan(context, spanBuilder, sanitizer, metrics);
return new OpenTelemetrySpan(context, spanBuilder, sanitizer, metrics, encodingEventsEnabled);
}
}

Expand All @@ -193,6 +200,7 @@ private static class OpenTelemetrySpan extends Tracer.Span {
private final SpanBuilder spanBuilder;
private final RedisCommandSanitizer sanitizer;
private final OperationListener metrics;
private final boolean encodingEventsEnabled;

@Nullable private String name;
@Nullable private List<Object> events;
Expand All @@ -207,12 +215,14 @@ private static class OpenTelemetrySpan extends Tracer.Span {
Context context,
SpanBuilder spanBuilder,
RedisCommandSanitizer sanitizer,
OperationListener metrics) {
OperationListener metrics,
boolean encodingEventsEnabled) {
this.context = context;
this.spanBuilder = spanBuilder;
this.sanitizer = sanitizer;
this.metrics = metrics;
this.attributesBuilder = Attributes.builder();
this.encodingEventsEnabled = encodingEventsEnabled;
if (SemconvStability.emitStableDatabaseSemconv()) {
attributesBuilder.put(DB_SYSTEM_NAME, REDIS);
}
Expand Down Expand Up @@ -326,6 +336,11 @@ public synchronized Tracer.Span start() {
@Override
@CanIgnoreReturnValue
public synchronized Tracer.Span annotate(String value) {
if (!encodingEventsEnabled && value.startsWith("redis.encode.")) {
// skip noisy encode events produced by io.lettuce.core.protocol.TracedCommand
return this;
}

if (span != null) {
span.addEvent(value);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,7 @@ void testSetCommandUsingFutureGetWithTimeout() throws Exception {
equalTo(SERVER_PORT, port),
equalTo(maybeStable(DB_SYSTEM), "redis"),
equalTo(maybeStable(DB_STATEMENT), "SET TESTSETKEY ?")))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))));
.satisfies(AbstractLettuceClientTest::assertCommandEncodeEvents)));
}

@Test
Expand Down Expand Up @@ -214,9 +212,8 @@ void testGetCommandChainedWithThenAccept() throws Exception {
equalTo(SERVER_PORT, port),
equalTo(maybeStable(DB_SYSTEM), "redis"),
equalTo(maybeStable(DB_STATEMENT), "GET TESTKEY")))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))));
.satisfies(
AbstractLettuceClientTest::assertCommandEncodeEvents)));

if (testCallback()) {
spanAsserts.add(
Expand Down Expand Up @@ -294,9 +291,8 @@ void testGetNonExistentKeyCommandWithHandleAsyncAndChainedWithThenApply() throws
equalTo(maybeStable(DB_SYSTEM), "redis"),
equalTo(
maybeStable(DB_STATEMENT), "GET NON_EXISTENT_KEY")))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))));
.satisfies(
AbstractLettuceClientTest::assertCommandEncodeEvents)));

if (testCallback()) {
spanAsserts.addAll(
Expand Down Expand Up @@ -360,9 +356,8 @@ void testCommandWithNoArgumentsUsingBiconsumer() throws Exception {
equalTo(SERVER_PORT, port),
equalTo(maybeStable(DB_SYSTEM), "redis"),
equalTo(maybeStable(DB_STATEMENT), "RANDOMKEY")))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))));
.satisfies(
AbstractLettuceClientTest::assertCommandEncodeEvents)));

if (testCallback()) {
spanAsserts.add(
Expand Down Expand Up @@ -422,9 +417,7 @@ void testHashSetAndThenNestApplyToHashGetall() throws Exception {
equalTo(
maybeStable(DB_STATEMENT),
"HMSET TESTHM firstname ? lastname ? age ?")))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))),
.satisfies(AbstractLettuceClientTest::assertCommandEncodeEvents)),
trace ->
trace.hasSpansSatisfyingExactly(
span ->
Expand All @@ -439,8 +432,6 @@ void testHashSetAndThenNestApplyToHashGetall() throws Exception {
equalTo(SERVER_PORT, port),
equalTo(maybeStable(DB_SYSTEM), "redis"),
equalTo(maybeStable(DB_STATEMENT), "HGETALL TESTHM")))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))));
.satisfies(AbstractLettuceClientTest::assertCommandEncodeEvents)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package io.opentelemetry.instrumentation.lettuce.v5_1;

import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.semconv.DbAttributes.DB_NAMESPACE;
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_REDIS_DATABASE_INDEX;
Expand All @@ -15,6 +16,7 @@
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
import io.opentelemetry.sdk.trace.data.SpanData;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -27,9 +29,13 @@
import org.testcontainers.containers.wait.strategy.Wait;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
abstract class AbstractLettuceClientTest {
public abstract class AbstractLettuceClientTest {
protected static final Logger logger = LoggerFactory.getLogger(AbstractLettuceClientTest.class);

private static final boolean COMMAND_ENCODING_EVENTS_ENABLED =
Boolean.getBoolean(
"otel.instrumentation.lettuce.experimental.command-encoding-events.enabled");

@RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create();

protected static final int DB_INDEX = 0;
Expand Down Expand Up @@ -94,4 +100,18 @@ protected static List<AttributeAssertion> addExtraAttributes(AttributeAssertion.
}
return result;
}

protected static void assertCommandEncodeEvents(SpanData span) {
if (COMMAND_ENCODING_EVENTS_ENABLED) {
assertThat(span)
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"));
} else {
assertThat(span.getEvents())
.noneSatisfy(event -> assertThat(event).hasName("redis.encode.start"));
assertThat(span.getEvents())
.noneSatisfy(event -> assertThat(event).hasName("redis.encode.end"));
}
}
}
Loading