diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java index 3589be3c1b74..77286017dd8d 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java @@ -48,7 +48,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -980,7 +979,7 @@ void testGetConnection( trace -> { List> assertions = new ArrayList<>( - Arrays.asList( + asList( span1 -> span1.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span1 -> span1 diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTelemetry.java b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTelemetry.java index 665f5f236fb3..3dbf0234ea53 100644 --- a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTelemetry.java +++ b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTelemetry.java @@ -10,6 +10,7 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.TracerBuilder; import io.opentelemetry.instrumentation.api.incubator.semconv.db.RedisCommandSanitizer; +import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties; /** Entrypoint for instrumenting Lettuce or clients. */ @@ -31,8 +32,13 @@ public static LettuceTelemetryBuilder builder(OpenTelemetry openTelemetry) { private final Tracer tracer; private final RedisCommandSanitizer sanitizer; + private final OperationListener metrics; - LettuceTelemetry(OpenTelemetry openTelemetry, boolean statementSanitizationEnabled) { + LettuceTelemetry( + OpenTelemetry openTelemetry, + boolean statementSanitizationEnabled, + OperationListener metrics) { + this.metrics = metrics; TracerBuilder tracerBuilder = openTelemetry.tracerBuilder(INSTRUMENTATION_NAME); String version = EmbeddedInstrumentationProperties.findVersion(INSTRUMENTATION_NAME); if (version != null) { @@ -47,6 +53,6 @@ public static LettuceTelemetryBuilder builder(OpenTelemetry openTelemetry) { * io.lettuce.core.resource.ClientResources.Builder#tracing(Tracing)}. */ public Tracing newTracing() { - return new OpenTelemetryTracing(tracer, sanitizer); + return new OpenTelemetryTracing(tracer, sanitizer, metrics); } } diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTelemetryBuilder.java b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTelemetryBuilder.java index d86c3f77628b..d44ab73e0586 100644 --- a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTelemetryBuilder.java +++ b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTelemetryBuilder.java @@ -5,8 +5,11 @@ package io.opentelemetry.instrumentation.lettuce.v5_1; +import static io.opentelemetry.instrumentation.lettuce.v5_1.LettuceTelemetry.INSTRUMENTATION_NAME; + import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; /** A builder of {@link LettuceTelemetry}. */ public final class LettuceTelemetryBuilder { @@ -36,6 +39,9 @@ public LettuceTelemetryBuilder setStatementSanitizationEnabled( * LettuceTelemetryBuilder}. */ public LettuceTelemetry build() { - return new LettuceTelemetry(openTelemetry, statementSanitizationEnabled); + return new LettuceTelemetry( + openTelemetry, + statementSanitizationEnabled, + DbClientMetrics.get().create(openTelemetry.getMeterProvider().get(INSTRUMENTATION_NAME))); } } 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 3b2e06f3b64e..2fee8305d91a 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 @@ -27,6 +27,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.incubator.semconv.db.RedisCommandSanitizer; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.internal.SemconvStability; import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesExtractor; import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesExtractor; @@ -54,8 +55,11 @@ final class OpenTelemetryTracing implements Tracing { NetworkAttributesExtractor.create(new LettuceServerAttributesGetter()); private final TracerProvider tracerProvider; - OpenTelemetryTracing(io.opentelemetry.api.trace.Tracer tracer, RedisCommandSanitizer sanitizer) { - this.tracerProvider = new OpenTelemetryTracerProvider(tracer, sanitizer); + OpenTelemetryTracing( + io.opentelemetry.api.trace.Tracer tracer, + RedisCommandSanitizer sanitizer, + OperationListener metrics) { + this.tracerProvider = new OpenTelemetryTracerProvider(tracer, sanitizer, metrics); } @Override @@ -93,8 +97,10 @@ private static class OpenTelemetryTracerProvider implements TracerProvider { private final Tracer openTelemetryTracer; OpenTelemetryTracerProvider( - io.opentelemetry.api.trace.Tracer tracer, RedisCommandSanitizer sanitizer) { - openTelemetryTracer = new OpenTelemetryTracer(tracer, sanitizer); + io.opentelemetry.api.trace.Tracer tracer, + RedisCommandSanitizer sanitizer, + OperationListener metrics) { + openTelemetryTracer = new OpenTelemetryTracer(tracer, sanitizer, metrics); } @Override @@ -135,10 +141,15 @@ private static class OpenTelemetryTracer extends Tracer { private final io.opentelemetry.api.trace.Tracer tracer; private final RedisCommandSanitizer sanitizer; + private final OperationListener metrics; - OpenTelemetryTracer(io.opentelemetry.api.trace.Tracer tracer, RedisCommandSanitizer sanitizer) { + OpenTelemetryTracer( + io.opentelemetry.api.trace.Tracer tracer, + RedisCommandSanitizer sanitizer, + OperationListener metrics) { this.tracer = tracer; this.sanitizer = sanitizer; + this.metrics = metrics; } @Override @@ -165,7 +176,7 @@ private OpenTelemetrySpan nextSpan(Context context) { .setSpanKind(SpanKind.CLIENT) .setParent(context) .setAttribute(DB_SYSTEM, REDIS); - return new OpenTelemetrySpan(context, spanBuilder, sanitizer); + return new OpenTelemetrySpan(context, spanBuilder, sanitizer, metrics); } } @@ -178,18 +189,26 @@ private static class OpenTelemetrySpan extends Tracer.Span { private final Context context; private final SpanBuilder spanBuilder; private final RedisCommandSanitizer sanitizer; + private final OperationListener metrics; @Nullable private String name; @Nullable private List events; @Nullable private Throwable error; @Nullable private Span span; + private long spanStartNanos; + private final AttributesBuilder attributesBuilder = Attributes.builder().put(DB_SYSTEM, REDIS); @Nullable private List argsList; @Nullable private String argsString; - OpenTelemetrySpan(Context context, SpanBuilder spanBuilder, RedisCommandSanitizer sanitizer) { + OpenTelemetrySpan( + Context context, + SpanBuilder spanBuilder, + RedisCommandSanitizer sanitizer, + OperationListener metrics) { this.context = context; this.spanBuilder = spanBuilder; this.sanitizer = sanitizer; + this.metrics = metrics; } @Override @@ -218,11 +237,13 @@ private void fillEndpoint(OpenTelemetryEndpoint endpoint) { Context currentContext = span == null ? context : context.with(span); serverAttributesExtractor.onStart(attributesBuilder, currentContext, endpoint); networkAttributesExtractor.onEnd(attributesBuilder, currentContext, endpoint, null, null); + Attributes attributes = attributesBuilder.build(); if (span != null) { - span.setAllAttributes(attributesBuilder.build()); + span.setAllAttributes(attributes); } else { - spanBuilder.setAllAttributes(attributesBuilder.build()); + spanBuilder.setAllAttributes(attributes); } + this.attributesBuilder.putAll(attributes); } // Added and called in 6.0+ @@ -231,6 +252,7 @@ private void fillEndpoint(OpenTelemetryEndpoint endpoint) { @SuppressWarnings("UnusedMethod") public synchronized Tracer.Span start(RedisCommand command) { start(); + long startNanos = System.nanoTime(); Span span = this.span; if (span == null) { @@ -258,7 +280,7 @@ public synchronized Tracer.Span start(RedisCommand command) { } } - finish(span); + finish(span, startNanos); }); } @@ -270,6 +292,7 @@ public synchronized Tracer.Span start(RedisCommand command) { @CanIgnoreReturnValue public synchronized Tracer.Span start() { span = spanBuilder.startSpan(); + spanStartNanos = System.nanoTime(); if (name != null) { span.updateName(name); } @@ -330,6 +353,7 @@ public synchronized Tracer.Span tag(String key, String value) { } else { spanBuilder.setAttribute(key, value); } + attributesBuilder.put(key, value); return this; } @@ -347,16 +371,20 @@ public synchronized Tracer.Span error(Throwable throwable) { @Override public synchronized void finish() { if (span != null) { - finish(span); + finish(span, spanStartNanos); } } - private void finish(Span span) { + private void finish(Span span, long startTime) { if (name != null) { String statement = sanitizer.sanitize(name, argsList != null ? argsList : splitArgs(argsString)); if (SemconvStability.emitStableDatabaseSemconv()) { span.setAttribute(DB_QUERY_TEXT, statement); + metrics.onEnd( + metrics.onStart(Context.current(), Attributes.empty(), startTime), + attributesBuilder.build(), + System.nanoTime()); } if (SemconvStability.emitOldDatabaseSemconv()) { span.setAttribute(DB_STATEMENT, statement); diff --git a/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.java b/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.java index f09ec6b687c2..460ed827a85f 100644 --- a/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.java +++ b/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.lettuce.v5_1; +import static io.opentelemetry.instrumentation.testing.junit.db.DbClientMetricsTestUtil.assertDurationMetric; import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; @@ -13,9 +14,11 @@ import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_TYPE; import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAMESPACE; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowable; @@ -31,7 +34,9 @@ import io.opentelemetry.instrumentation.test.utils.PortUtils; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.ArrayList; import java.util.Base64; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -135,6 +140,20 @@ void testSetCommand() { .hasEventsSatisfyingExactly( event -> event.hasName("redis.encode.start"), event -> event.hasName("redis.encode.end")))); + + List> expected = + new ArrayList<>( + asList( + DB_SYSTEM, SERVER_ADDRESS, SERVER_PORT, NETWORK_PEER_ADDRESS, NETWORK_PEER_PORT)); + if (Boolean.getBoolean("testLatestDeps")) { + expected.add(DB_NAMESPACE); + } + assertDurationMetric(testing(), "io.opentelemetry.lettuce-5.1", toArray(expected)); + } + + @SuppressWarnings("rawtypes") + private static AttributeKey[] toArray(List> expected) { + return expected.toArray(new AttributeKey[0]); } @Test diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/db/DbClientMetricsTestUtil.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/db/DbClientMetricsTestUtil.java index 40f2832790c7..9bfff8d60b5f 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/db/DbClientMetricsTestUtil.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/db/DbClientMetricsTestUtil.java @@ -7,6 +7,7 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; @@ -19,6 +20,9 @@ public static void assertDurationMetric( InstrumentationExtension testing, String instrumentationName, AttributeKey... expectedKeys) { + // db.system is required - see + // https://opentelemetry.io/docs/specs/semconv/database/database-metrics/#metric-dbclientoperationduration + assertThat(expectedKeys).extracting(AttributeKey::getKey).contains(DB_SYSTEM.getKey()); if (!emitStableDatabaseSemconv()) { return; }