From 66be356f636f2f9538d8b74d2280d3984dcd650f Mon Sep 17 00:00:00 2001 From: Alix Date: Thu, 1 May 2025 17:28:57 +0200 Subject: [PATCH 01/34] Bootstrap NATS instrumentation with Connection.publish(Message) tracing --- .fossa.yml | 6 + .gitignore | 1 + .../nats/nats-2.21/javaagent/build.gradle.kts | 24 ++ .../nats/v2_21/ConnectionInstrumentation.java | 83 ++++ .../nats/v2_21/NatsInstrumentationModule.java | 27 ++ .../nats/v2_21/NatsSingletons.java | 20 + .../nats/v2_21/NatsInstrumentationTest.java | 97 +++++ .../nats/nats-2.21/library/build.gradle.kts | 9 + .../nats/v2_21/NatsTelemetry.java | 32 ++ .../nats/v2_21/NatsTelemetryBuilder.java | 22 ++ .../nats/v2_21/OpenTelemetryConnection.java | 359 ++++++++++++++++++ .../nats/v2_21/OpenTelemetryMessage.java | 147 +++++++ .../MessageMessagingAttributesGetter.java | 84 ++++ .../v2_21/internal/MessageTextMapSetter.java | 26 ++ .../internal/NatsInstrumenterFactory.java | 43 +++ .../nats/v2_21/NatsTelemetryTest.java | 63 +++ instrumentation/nats/nats-2.21/metadata.yaml | 7 + .../nats/nats-2.21/testing/build.gradle.kts | 9 + .../nats/v2_21/TestConnection.java | 340 +++++++++++++++++ settings.gradle.kts | 3 + 20 files changed, 1402 insertions(+) create mode 100644 instrumentation/nats/nats-2.21/javaagent/build.gradle.kts create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionInstrumentation.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationTest.java create mode 100644 instrumentation/nats/nats-2.21/library/build.gradle.kts create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessage.java create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageMessagingAttributesGetter.java create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageTextMapSetter.java create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java create mode 100644 instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryTest.java create mode 100644 instrumentation/nats/nats-2.21/metadata.yaml create mode 100644 instrumentation/nats/nats-2.21/testing/build.gradle.kts create mode 100644 instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java diff --git a/.fossa.yml b/.fossa.yml index 0a29d3ce460a..b727dd619c23 100644 --- a/.fossa.yml +++ b/.fossa.yml @@ -691,6 +691,12 @@ targets: - type: gradle path: ./ target: ':instrumentation:mongo:mongo-async-3.3:javaagent' + - type: gradle + path: ./ + target: ':instrumentation:nats:nats-2.21:javaagent' + - type: gradle + path: ./ + target: ':instrumentation:nats:nats-2.21:library' - type: gradle path: ./ target: ':instrumentation:netty:netty-3.8:javaagent' diff --git a/.gitignore b/.gitignore index 42e4e27f7515..5da34c1a55a2 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ out/ ###################### .vscode **/bin/ +.metals # Others # ########## diff --git a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts new file mode 100644 index 000000000000..bfdf930702cf --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("io.nats") + module.set("jnats") + versions.set("[2.21.0,)") + assertInverse.set(true) + } +} + +dependencies { + library("io.nats:jnats:2.21.0") + + implementation(project(":instrumentation:nats:nats-2.21:library")) +} + +tasks { + test { + usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + } +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionInstrumentation.java new file mode 100644 index 000000000000..dab44fbe4c1e --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionInstrumentation.java @@ -0,0 +1,83 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.PRODUCER_INSTRUMENTER; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.nats.client.Connection; +import io.nats.client.Message; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.nats.v2_21.OpenTelemetryMessage; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class ConnectionInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("io.nats.client.Connection")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isPublic() + .and(named("publish")) + .and(takesArguments(1)) + .and(takesArgument(0, named("io.nats.client.Message"))), + ConnectionInstrumentation.class.getName() + "$PublishAdvice"); + } + + @SuppressWarnings("unused") + public static class PublishAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + @Advice.AssignReturned.ToArguments(@ToArgument(0)) + public static Message onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) Message message, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope) { + Context parentContext = Context.current(); + + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, message)) { + return message; + } + + OpenTelemetryMessage otelMessage = new OpenTelemetryMessage(connection, message); + + otelContext = PRODUCER_INSTRUMENTER.start(parentContext, otelMessage); + otelScope = otelContext.makeCurrent(); + + return otelMessage; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Thrown Throwable throwable, + @Advice.This Connection connection, + @Advice.Argument(0) Message message, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope) { + if (otelScope == null) { + return; + } + + otelScope.close(); + PRODUCER_INSTRUMENTER.end(otelContext, message, null, throwable); + } + } +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java new file mode 100644 index 000000000000..1cf3ba651e13 --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.Collections; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class NatsInstrumentationModule extends InstrumentationModule { + + public NatsInstrumentationModule() { + super("nats", "nats-2.21"); + } + + // TODO classLoaderMatcher + + @Override + public List typeInstrumentations() { + return Collections.singletonList(new ConnectionInstrumentation()); + } +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java new file mode 100644 index 000000000000..3a6fa4d73b9c --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createProducerInstrumenter; + +import io.nats.client.Message; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; + +public final class NatsSingletons { + + public static final Instrumenter PRODUCER_INSTRUMENTER = + createProducerInstrumenter(GlobalOpenTelemetry.get()); + + private NatsSingletons() {} +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationTest.java new file mode 100644 index 000000000000..ee976e54d25a --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationTest.java @@ -0,0 +1,97 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; +import static org.assertj.core.api.Assertions.assertThat; + +import io.nats.client.Connection; +import io.nats.client.Message; +import io.nats.client.Nats; +import io.nats.client.Subscription; +import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.io.IOException; +import java.time.Duration; +import java.util.LinkedList; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +@SuppressWarnings("deprecation") // using deprecated semconv +class NatsInstrumentationTest { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + static final DockerImageName natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); + + static final GenericContainer natsContainer = + new GenericContainer<>(natsImage).withExposedPorts(4222); + + static String natsUrl; + static Connection connection; + static Subscription subscription; + + static LinkedList publishedMessages = new LinkedList<>(); + + @BeforeAll + static void beforeAll() throws IOException, InterruptedException { + natsContainer.start(); + natsUrl = "nats://" + natsContainer.getHost() + ":" + natsContainer.getMappedPort(4222); + connection = Nats.connect(natsUrl); + subscription = connection.subscribe("*"); + } + + @AfterAll + static void afterAll() throws InterruptedException { + subscription.drain(Duration.ofSeconds(10)); + connection.close(); + natsContainer.close(); + } + + @Test + void testPublishMessage() throws InterruptedException { + // given + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + int clientId = connection.getServerInfo().getClientId(); + + // when + testing.runWithSpan("testPublishMessage", () -> connection.publish(message)); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("testPublishMessage").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "publish"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))))); + + // and + Message published = subscription.nextMessage(Duration.ofSeconds(1)); + assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); + } +} diff --git a/instrumentation/nats/nats-2.21/library/build.gradle.kts b/instrumentation/nats/nats-2.21/library/build.gradle.kts new file mode 100644 index 000000000000..0814f79c75df --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("otel.library-instrumentation") +} + +dependencies { + library("io.nats:jnats:2.21.0") + + testImplementation(project(":instrumentation:nats:nats-2.21:testing")) +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java new file mode 100644 index 000000000000..99bacce26d3f --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.nats.client.Message; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; + +public final class NatsTelemetry { + + public static NatsTelemetry create(OpenTelemetry openTelemetry) { + return new NatsTelemetryBuilder(openTelemetry).build(); + } + + public static NatsTelemetryBuilder builder(OpenTelemetry openTelemetry) { + return new NatsTelemetryBuilder(openTelemetry); + } + + private final Instrumenter producerInstrumenter; + + public NatsTelemetry(Instrumenter producerInstrumenter) { + this.producerInstrumenter = producerInstrumenter; + } + + public OpenTelemetryConnection wrap(Connection connection) { + return new OpenTelemetryConnection(connection, this.producerInstrumenter); + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java new file mode 100644 index 000000000000..79de839074f6 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory; + +public final class NatsTelemetryBuilder { + + private final OpenTelemetry openTelemetry; + + NatsTelemetryBuilder(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + public NatsTelemetry build() { + return new NatsTelemetry(NatsInstrumenterFactory.createProducerInstrumenter(openTelemetry)); + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java new file mode 100644 index 000000000000..07972c234de9 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java @@ -0,0 +1,359 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.nats.client.ConnectionListener; +import io.nats.client.ConsumerContext; +import io.nats.client.Dispatcher; +import io.nats.client.ForceReconnectOptions; +import io.nats.client.JetStream; +import io.nats.client.JetStreamApiException; +import io.nats.client.JetStreamManagement; +import io.nats.client.JetStreamOptions; +import io.nats.client.KeyValue; +import io.nats.client.KeyValueManagement; +import io.nats.client.KeyValueOptions; +import io.nats.client.Message; +import io.nats.client.MessageHandler; +import io.nats.client.ObjectStore; +import io.nats.client.ObjectStoreManagement; +import io.nats.client.ObjectStoreOptions; +import io.nats.client.Options; +import io.nats.client.Statistics; +import io.nats.client.StreamContext; +import io.nats.client.Subscription; +import io.nats.client.api.ServerInfo; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import java.io.IOException; +import java.net.InetAddress; +import java.time.Duration; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; + +public class OpenTelemetryConnection implements Connection { + + private final Connection delegate; + private final Instrumenter producerInstrumenter; + + public OpenTelemetryConnection( + Connection connection, Instrumenter producerInstrumenter) { + this.delegate = connection; + this.producerInstrumenter = producerInstrumenter; + } + + @Override + public void publish(String subject, byte[] body) { + this.publish(NatsMessage.builder().subject(subject).data(body).build()); + } + + @Override + public void publish(String subject, Headers headers, byte[] body) { + this.publish(NatsMessage.builder().subject(subject).headers(headers).data(body).build()); + } + + @Override + public void publish(String subject, String replyTo, byte[] body) { + this.publish(NatsMessage.builder().subject(subject).replyTo(replyTo).data(body).build()); + } + + @Override + public void publish(String subject, String replyTo, Headers headers, byte[] body) { + this.publish( + NatsMessage.builder() + .subject(subject) + .replyTo(replyTo) + .headers(headers) + .data(body) + .build()); + } + + @Override + public void publish(Message message) { + Context parentContext = Context.current(); + + if (!Span.fromContext(parentContext).getSpanContext().isValid() + || !producerInstrumenter.shouldStart(parentContext, message)) { + delegate.publish(message); + return; + } + + Message otelMessage = new OpenTelemetryMessage(this, message); + Context context = producerInstrumenter.start(parentContext, otelMessage); + + try (Scope ignored = context.makeCurrent()) { + delegate.publish(otelMessage); + } finally { + producerInstrumenter.end(context, otelMessage, null, null); + } + } + + @Override + public CompletableFuture request(String s, byte[] bytes) { + return delegate.request(s, bytes); + } + + @Override + public Message request(String s, byte[] bytes, Duration duration) throws InterruptedException { + return delegate.request(s, bytes, duration); + } + + @Override + public CompletableFuture request(String s, Headers headers, byte[] bytes) { + return delegate.request(s, headers, bytes); + } + + @Override + public Message request(String s, Headers headers, byte[] bytes, Duration duration) + throws InterruptedException { + return delegate.request(s, headers, bytes, duration); + } + + @Override + public CompletableFuture request(Message message) { + return delegate.request(message); + } + + @Override + public Message request(Message message, Duration duration) throws InterruptedException { + return delegate.request(message, duration); + } + + @Override + public CompletableFuture requestWithTimeout(String s, byte[] bytes, Duration duration) { + return delegate.requestWithTimeout(s, bytes, duration); + } + + @Override + public CompletableFuture requestWithTimeout( + String s, Headers headers, byte[] bytes, Duration duration) { + return delegate.requestWithTimeout(s, headers, bytes, duration); + } + + @Override + public CompletableFuture requestWithTimeout(Message message, Duration duration) { + return delegate.requestWithTimeout(message, duration); + } + + @Override + public Subscription subscribe(String s) { + return delegate.subscribe(s); + } + + @Override + public Subscription subscribe(String s, String s1) { + return delegate.subscribe(s, s1); + } + + @Override + public Dispatcher createDispatcher(MessageHandler messageHandler) { + return delegate.createDispatcher(messageHandler); + } + + @Override + public Dispatcher createDispatcher() { + return delegate.createDispatcher(); + } + + @Override + public void closeDispatcher(Dispatcher dispatcher) { + delegate.closeDispatcher(dispatcher); + } + + @Override + public void addConnectionListener(ConnectionListener connectionListener) { + delegate.addConnectionListener(connectionListener); + } + + @Override + public void removeConnectionListener(ConnectionListener connectionListener) { + delegate.removeConnectionListener(connectionListener); + } + + @Override + public void flush(Duration duration) throws TimeoutException, InterruptedException { + delegate.flush(duration); + } + + @Override + public CompletableFuture drain(Duration duration) + throws TimeoutException, InterruptedException { + return delegate.drain(duration); + } + + @Override + public void close() throws InterruptedException { + delegate.close(); + } + + @Override + public Status getStatus() { + return delegate.getStatus(); + } + + @Override + public long getMaxPayload() { + return delegate.getMaxPayload(); + } + + @Override + public Collection getServers() { + return delegate.getServers(); + } + + @Override + public Statistics getStatistics() { + return delegate.getStatistics(); + } + + @Override + public Options getOptions() { + return delegate.getOptions(); + } + + @Override + public ServerInfo getServerInfo() { + return delegate.getServerInfo(); + } + + @Override + public String getConnectedUrl() { + return delegate.getConnectedUrl(); + } + + @Override + public InetAddress getClientInetAddress() { + return delegate.getClientInetAddress(); + } + + @Override + public String getLastError() { + return delegate.getLastError(); + } + + @Override + public void clearLastError() { + delegate.clearLastError(); + } + + @Override + public String createInbox() { + return delegate.createInbox(); + } + + @Override + public void flushBuffer() throws IOException { + delegate.flushBuffer(); + } + + @Override + public void forceReconnect() throws IOException, InterruptedException { + delegate.forceReconnect(); + } + + @Override + public void forceReconnect(ForceReconnectOptions forceReconnectOptions) + throws IOException, InterruptedException { + delegate.forceReconnect(forceReconnectOptions); + } + + @Override + public Duration RTT() throws IOException { + return delegate.RTT(); + } + + @Override + public StreamContext getStreamContext(String s) throws IOException, JetStreamApiException { + return delegate.getStreamContext(s); + } + + @Override + public StreamContext getStreamContext(String s, JetStreamOptions jetStreamOptions) + throws IOException, JetStreamApiException { + return delegate.getStreamContext(s, jetStreamOptions); + } + + @Override + public ConsumerContext getConsumerContext(String s, String s1) + throws IOException, JetStreamApiException { + return delegate.getConsumerContext(s, s1); + } + + @Override + public ConsumerContext getConsumerContext(String s, String s1, JetStreamOptions jetStreamOptions) + throws IOException, JetStreamApiException { + return delegate.getConsumerContext(s, s1, jetStreamOptions); + } + + @Override + public JetStream jetStream() throws IOException { + return delegate.jetStream(); + } + + @Override + public JetStream jetStream(JetStreamOptions jetStreamOptions) throws IOException { + return delegate.jetStream(jetStreamOptions); + } + + @Override + public JetStreamManagement jetStreamManagement() throws IOException { + return delegate.jetStreamManagement(); + } + + @Override + public JetStreamManagement jetStreamManagement(JetStreamOptions jetStreamOptions) + throws IOException { + return delegate.jetStreamManagement(jetStreamOptions); + } + + @Override + public KeyValue keyValue(String s) throws IOException { + return delegate.keyValue(s); + } + + @Override + public KeyValue keyValue(String s, KeyValueOptions keyValueOptions) throws IOException { + return delegate.keyValue(s, keyValueOptions); + } + + @Override + public KeyValueManagement keyValueManagement() throws IOException { + return delegate.keyValueManagement(); + } + + @Override + public KeyValueManagement keyValueManagement(KeyValueOptions keyValueOptions) throws IOException { + return delegate.keyValueManagement(keyValueOptions); + } + + @Override + public ObjectStore objectStore(String s) throws IOException { + return delegate.objectStore(s); + } + + @Override + public ObjectStore objectStore(String s, ObjectStoreOptions objectStoreOptions) + throws IOException { + return delegate.objectStore(s, objectStoreOptions); + } + + @Override + public ObjectStoreManagement objectStoreManagement() throws IOException { + return delegate.objectStoreManagement(); + } + + @Override + public ObjectStoreManagement objectStoreManagement(ObjectStoreOptions objectStoreOptions) + throws IOException { + return delegate.objectStoreManagement(objectStoreOptions); + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessage.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessage.java new file mode 100644 index 000000000000..59edb68a95d7 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessage.java @@ -0,0 +1,147 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.nats.client.Message; +import io.nats.client.Subscription; +import io.nats.client.impl.AckType; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsJetStreamMetaData; +import io.nats.client.support.Status; +import java.time.Duration; +import java.util.concurrent.TimeoutException; + +/** + * Wrapper with an always NotNull Headers property. + * + * @see io.opentelemetry.instrumentation.nats.v2_21.internal.MessageTextMapSetter#set + */ +public class OpenTelemetryMessage implements Message { + private final Connection connection; + private final Message delegate; + private final Headers headers; + + public OpenTelemetryMessage(Connection connection, Message delegate) { + this.connection = connection; + this.delegate = delegate; + this.headers = new Headers(delegate.getHeaders()); + } + + @Override + public String getSubject() { + return delegate.getSubject(); + } + + @Override + public String getReplyTo() { + return delegate.getReplyTo(); + } + + @Override + public boolean hasHeaders() { + return !headers.isEmpty(); + } + + @Override + public Headers getHeaders() { + return this.headers; + } + + @Override + public boolean isStatusMessage() { + return delegate.isStatusMessage(); + } + + @Override + public Status getStatus() { + return delegate.getStatus(); + } + + @Override + public byte[] getData() { + return delegate.getData(); + } + + @Override + public boolean isUtf8mode() { + return delegate.isUtf8mode(); + } + + @Override + public Subscription getSubscription() { + return delegate.getSubscription(); + } + + @Override + public String getSID() { + return delegate.getSID(); + } + + @Override + public Connection getConnection() { + // Connection is only set for received message. + // To be able to expose the connection.clientId + // in span attributes, let's link it in case of + // a message being sent. + Connection connection = delegate.getConnection(); + if (connection == null) { + connection = this.connection; + } + return connection; + } + + @Override + public NatsJetStreamMetaData metaData() { + return delegate.metaData(); + } + + @Override + public AckType lastAck() { + return delegate.lastAck(); + } + + @Override + public void ack() { + delegate.ack(); + } + + @Override + public void ackSync(Duration timeout) throws TimeoutException, InterruptedException { + delegate.ackSync(timeout); + } + + @Override + public void nak() { + delegate.nak(); + } + + @Override + public void nakWithDelay(Duration nakDelay) { + delegate.nakWithDelay(nakDelay); + } + + @SuppressWarnings("PreferJavaTimeOverload") + @Override + public void nakWithDelay(long nakDelayMillis) { + delegate.nakWithDelay(nakDelayMillis); + } + + @Override + public void term() { + delegate.term(); + } + + @Override + public void inProgress() { + delegate.inProgress(); + } + + @Override + public boolean isJetStream() { + return delegate.isJetStream(); + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageMessagingAttributesGetter.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageMessagingAttributesGetter.java new file mode 100644 index 000000000000..b291b88fdd3b --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageMessagingAttributesGetter.java @@ -0,0 +1,84 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21.internal; + +import io.nats.client.Message; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import java.util.List; +import javax.annotation.Nullable; + +enum MessageMessagingAttributesGetter implements MessagingAttributesGetter { + INSTANCE; + + @Nullable + @Override + public String getSystem(Message message) { + return "nats"; + } + + @Nullable + @Override + public String getDestination(Message message) { + return message.getSubject(); + } + + @Nullable + @Override + public String getDestinationTemplate(Message message) { + return null; + } + + @Override + public boolean isTemporaryDestination(Message message) { + return false; + } + + @Override + public boolean isAnonymousDestination(Message message) { + return false; + } + + @Nullable + @Override + public String getConversationId(Message message) { + return null; + } + + @Nullable + @Override + public Long getMessageBodySize(Message message) { + return (long) message.getData().length; + } + + @Nullable + @Override + public Long getMessageEnvelopeSize(Message message) { + return null; + } + + @Nullable + @Override + public String getMessageId(Message message, @Nullable Void unused) { + return null; + } + + @Nullable + @Override + public String getClientId(Message message) { + return String.valueOf(message.getConnection().getServerInfo().getClientId()); + } + + @Nullable + @Override + public Long getBatchMessageCount(Message message, @Nullable Void unused) { + return null; + } + + @Override + public List getMessageHeader(Message message, String name) { + return message.getHeaders().get(name); + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageTextMapSetter.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageTextMapSetter.java new file mode 100644 index 000000000000..d8bae4f22bc5 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageTextMapSetter.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21.internal; + +import io.nats.client.Message; +import io.opentelemetry.context.propagation.TextMapSetter; +import javax.annotation.Nullable; + +enum MessageTextMapSetter implements TextMapSetter { + INSTANCE; + + @Override + /* Can not work if getHeaders doesn't return a writable structure. */ + public void set(@Nullable Message carrier, String key, String value) { + if (carrier == null) { + return; + } + + if (carrier.getHeaders() != null && !carrier.getHeaders().isReadOnly()) { + carrier.getHeaders().put(key, value); + } + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java new file mode 100644 index 000000000000..dcd57fd298ae --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21.internal; + +import io.nats.client.Message; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time.", or "This class is internal and experimental. Its APIs are unstable and can change at + * any time. Its APIs (or a version of them) may be promoted to the public stable API in the future, + * but no guarantees are made. + */ +public final class NatsInstrumenterFactory { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.nats-2.21"; + + public static final SpanNameExtractor PRODUCER_SPAN_NAME_EXTRACTOR = + MessagingSpanNameExtractor.create( + MessageMessagingAttributesGetter.INSTANCE, MessageOperation.PUBLISH); + + public static final AttributesExtractor PUBLISH_ATTRIBUTES_EXTRACTOR = + MessagingAttributesExtractor.create( + MessageMessagingAttributesGetter.INSTANCE, MessageOperation.PUBLISH); + + public static Instrumenter createProducerInstrumenter( + OpenTelemetry openTelemetry) { + return Instrumenter.builder( + openTelemetry, INSTRUMENTATION_NAME, PRODUCER_SPAN_NAME_EXTRACTOR) + .addAttributesExtractor(PUBLISH_ATTRIBUTES_EXTRACTOR) + .buildProducerInstrumenter(MessageTextMapSetter.INSTANCE); + } + + private NatsInstrumenterFactory() {} +} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryTest.java new file mode 100644 index 000000000000..5b35e0d46501 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryTest.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.nats.client.Message; +import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +@SuppressWarnings("deprecation") // using deprecated semconv +class NatsTelemetryTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Test + void testPublishMessage() { + // given + NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + + // when + testing.runWithSpan("testPublishMessage", () -> connection.publish(message)); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("testPublishMessage").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "publish"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + + // and + Message published = testConnection.publishedMessages.peekLast(); + assertNotNull(published); + assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); + } +} diff --git a/instrumentation/nats/nats-2.21/metadata.yaml b/instrumentation/nats/nats-2.21/metadata.yaml new file mode 100644 index 000000000000..c386bbe8692a --- /dev/null +++ b/instrumentation/nats/nats-2.21/metadata.yaml @@ -0,0 +1,7 @@ +disabled_by_default: true +description: > + TODO +configurations: + - name: TODO + description: TODO + default: false diff --git a/instrumentation/nats/nats-2.21/testing/build.gradle.kts b/instrumentation/nats/nats-2.21/testing/build.gradle.kts new file mode 100644 index 000000000000..10338c07fb9d --- /dev/null +++ b/instrumentation/nats/nats-2.21/testing/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("otel.java-conventions") +} + +dependencies { + api(project(":testing-common")) + + compileOnly("io.nats:jnats:2.21.0") +} diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java new file mode 100644 index 000000000000..858138518c9f --- /dev/null +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java @@ -0,0 +1,340 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.nats.client.ConnectionListener; +import io.nats.client.ConsumerContext; +import io.nats.client.Dispatcher; +import io.nats.client.ForceReconnectOptions; +import io.nats.client.JetStream; +import io.nats.client.JetStreamApiException; +import io.nats.client.JetStreamManagement; +import io.nats.client.JetStreamOptions; +import io.nats.client.KeyValue; +import io.nats.client.KeyValueManagement; +import io.nats.client.KeyValueOptions; +import io.nats.client.Message; +import io.nats.client.MessageHandler; +import io.nats.client.ObjectStore; +import io.nats.client.ObjectStoreManagement; +import io.nats.client.ObjectStoreOptions; +import io.nats.client.Options; +import io.nats.client.Statistics; +import io.nats.client.StreamContext; +import io.nats.client.Subscription; +import io.nats.client.api.ServerInfo; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; +import java.io.IOException; +import java.net.InetAddress; +import java.time.Duration; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; + +public class TestConnection implements Connection { + + public final LinkedList publishedMessages = new LinkedList<>(); + + @Override + public void publish(String subject, byte[] body) { + this.publish(NatsMessage.builder().subject(subject).data(body).build()); + } + + @Override + public void publish(String subject, Headers headers, byte[] body) { + this.publish(NatsMessage.builder().subject(subject).headers(headers).data(body).build()); + } + + @Override + public void publish(String subject, String replyTo, byte[] body) { + this.publish(NatsMessage.builder().subject(subject).replyTo(replyTo).data(body).build()); + } + + @Override + public void publish(String subject, String replyTo, Headers headers, byte[] body) { + this.publish( + NatsMessage.builder() + .subject(subject) + .replyTo(replyTo) + .headers(headers) + .data(body) + .build()); + } + + @Override + public void publish(Message message) { + publishedMessages.add(message); + } + + @Override + public CompletableFuture request(String subject, byte[] body) { + return null; + } + + @Override + public Message request(String subject, byte[] body, Duration timeout) + throws InterruptedException { + return null; + } + + @Override + public CompletableFuture request(String subject, Headers headers, byte[] body) { + return null; + } + + @Override + public Message request(String subject, Headers headers, byte[] body, Duration timeout) + throws InterruptedException { + return null; + } + + @Override + public CompletableFuture request(Message message) { + return null; + } + + @Override + public Message request(Message message, Duration timeout) throws InterruptedException { + return null; + } + + @Override + public CompletableFuture requestWithTimeout( + String subject, byte[] body, Duration timeout) { + return null; + } + + @Override + public CompletableFuture requestWithTimeout( + String subject, Headers headers, byte[] body, Duration timeout) { + return null; + } + + @Override + public CompletableFuture requestWithTimeout(Message message, Duration timeout) { + return null; + } + + @Override + public Subscription subscribe(String subject) { + return null; + } + + @Override + public Subscription subscribe(String subject, String queueName) { + return null; + } + + @Override + public Dispatcher createDispatcher(MessageHandler handler) { + return null; + } + + @Override + public Dispatcher createDispatcher() { + return null; + } + + @Override + public void closeDispatcher(Dispatcher dispatcher) {} + + @Override + public void addConnectionListener(ConnectionListener connectionListener) {} + + @Override + public void removeConnectionListener(ConnectionListener connectionListener) {} + + @Override + public void flush(Duration timeout) throws TimeoutException, InterruptedException {} + + @Override + public CompletableFuture drain(Duration timeout) + throws TimeoutException, InterruptedException { + return null; + } + + @Override + public void close() throws InterruptedException {} + + @Override + public Status getStatus() { + return null; + } + + @Override + public long getMaxPayload() { + return 0; + } + + @Override + public Collection getServers() { + return Collections.emptyList(); + } + + @Override + public Statistics getStatistics() { + return null; + } + + @Override + public Options getOptions() { + return null; + } + + @Override + public ServerInfo getServerInfo() { + return new ServerInfo( + "{" + + "\"server_id\": \"SID\", " + + "\"server_name\": \"opentelemetry-nats\", " + + "\"version\": \"2.10.24\", " + + "\"go\": \"go1.23.4\", " + + "\"host\": \"0.0.0.0\", " + + "\"headers_supported\": true, " + + "\"auth_required\": true, " + + "\"nonce\": null, " + + "\"tls_required\": false, " + + "\"tls_available\": false, " + + "\"ldm\": false, " + + "\"jetstream\": false, " + + "\"port\": 4222, " + + "\"proto\": 1, " + + "\"max_payload\": 1048576, " + + "\"client_id\": 1, " + + "\"client_ip\": \"192.168.1.1\", " + + "\"cluster\": \"opentelemetry-nats\", " + + "\"connect_urls\": []" + + "}"); + } + + @Override + public String getConnectedUrl() { + return ""; + } + + @Override + public InetAddress getClientInetAddress() { + return null; + } + + @Override + public String getLastError() { + return ""; + } + + @Override + public void clearLastError() {} + + @Override + public String createInbox() { + return ""; + } + + @Override + public void flushBuffer() throws IOException {} + + @Override + public void forceReconnect() throws IOException, InterruptedException {} + + @Override + public void forceReconnect(ForceReconnectOptions options) + throws IOException, InterruptedException {} + + @Override + public Duration RTT() throws IOException { + return null; + } + + @Override + public StreamContext getStreamContext(String streamName) + throws IOException, JetStreamApiException { + return null; + } + + @Override + public StreamContext getStreamContext(String streamName, JetStreamOptions options) + throws IOException, JetStreamApiException { + return null; + } + + @Override + public ConsumerContext getConsumerContext(String streamName, String consumerName) + throws IOException, JetStreamApiException { + return null; + } + + @Override + public ConsumerContext getConsumerContext( + String streamName, String consumerName, JetStreamOptions options) + throws IOException, JetStreamApiException { + return null; + } + + @Override + public JetStream jetStream() throws IOException { + return null; + } + + @Override + public JetStream jetStream(JetStreamOptions options) throws IOException { + return null; + } + + @Override + public JetStreamManagement jetStreamManagement() throws IOException { + return null; + } + + @Override + public JetStreamManagement jetStreamManagement(JetStreamOptions options) throws IOException { + return null; + } + + @Override + public KeyValue keyValue(String bucketName) throws IOException { + return null; + } + + @Override + public KeyValue keyValue(String bucketName, KeyValueOptions options) throws IOException { + return null; + } + + @Override + public KeyValueManagement keyValueManagement() throws IOException { + return null; + } + + @Override + public KeyValueManagement keyValueManagement(KeyValueOptions options) throws IOException { + return null; + } + + @Override + public ObjectStore objectStore(String bucketName) throws IOException { + return null; + } + + @Override + public ObjectStore objectStore(String bucketName, ObjectStoreOptions options) throws IOException { + return null; + } + + @Override + public ObjectStoreManagement objectStoreManagement() throws IOException { + return null; + } + + @Override + public ObjectStoreManagement objectStoreManagement(ObjectStoreOptions options) + throws IOException { + return null; + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index b8c419a9a5e0..cab27d0446a9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -407,6 +407,9 @@ include(":instrumentation:mongo:mongo-4.0:javaagent") include(":instrumentation:mongo:mongo-async-3.3:javaagent") include(":instrumentation:mongo:mongo-common:testing") include(":instrumentation:mybatis-3.2:javaagent") +include(":instrumentation:nats:nats-2.21:javaagent") +include(":instrumentation:nats:nats-2.21:library") +include(":instrumentation:nats:nats-2.21:testing") include(":instrumentation:netty:netty-3.8:javaagent") include(":instrumentation:netty:netty-4.0:javaagent") include(":instrumentation:netty:netty-4.1:javaagent") From 9757a4644ecd6ca2fd7d23d4b3e45035f782de02 Mon Sep 17 00:00:00 2001 From: Alix Date: Fri, 2 May 2025 15:54:34 +0200 Subject: [PATCH 02/34] don't propagate into headers when not possible --- .../nats/nats-2.21/javaagent/build.gradle.kts | 1 - .../nats/v2_21/ConnectionInstrumentation.java | 29 ++-- .../nats/v2_21/NatsSingletons.java | 4 +- .../nats/v2_21/NatsInstrumentationTest.java | 36 ++++- .../nats/nats-2.21/library/build.gradle.kts | 3 + .../nats/v2_21/NatsTelemetry.java | 6 +- .../nats/v2_21/OpenTelemetryConnection.java | 59 +++---- .../nats/v2_21/OpenTelemetryMessage.java | 147 ------------------ .../MessageMessagingAttributesGetter.java | 38 ++--- .../v2_21/internal/MessageTextMapSetter.java | 11 +- .../internal/NatsInstrumenterFactory.java | 9 +- .../nats/v2_21/internal/NatsRequest.java | 52 +++++++ ...elemetryTest.java => NatsPublishTest.java} | 39 ++++- 13 files changed, 203 insertions(+), 231 deletions(-) delete mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessage.java create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java rename instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/{NatsTelemetryTest.java => NatsPublishTest.java} (64%) diff --git a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts index bfdf930702cf..c1d88aa5675b 100644 --- a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts @@ -7,7 +7,6 @@ muzzle { group.set("io.nats") module.set("jnats") versions.set("[2.21.0,)") - assertInverse.set(true) } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionInstrumentation.java index dab44fbe4c1e..f39570d40037 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionInstrumentation.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionInstrumentation.java @@ -16,11 +16,10 @@ import io.nats.client.Message; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.nats.v2_21.OpenTelemetryMessage; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import net.bytebuddy.asm.Advice; -import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -38,31 +37,28 @@ public void transform(TypeTransformer transformer) { .and(named("publish")) .and(takesArguments(1)) .and(takesArgument(0, named("io.nats.client.Message"))), - ConnectionInstrumentation.class.getName() + "$PublishAdvice"); + ConnectionInstrumentation.class.getName() + "$PublishMessageAdvice"); } @SuppressWarnings("unused") - public static class PublishAdvice { + public static class PublishMessageAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - @Advice.AssignReturned.ToArguments(@ToArgument(0)) - public static Message onEnter( + public static void onEnter( @Advice.This Connection connection, @Advice.Argument(0) Message message, @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope) { + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { Context parentContext = Context.current(); + natsRequest = NatsRequest.create(connection, message); - if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, message)) { - return message; + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + return; } - OpenTelemetryMessage otelMessage = new OpenTelemetryMessage(connection, message); - - otelContext = PRODUCER_INSTRUMENTER.start(parentContext, otelMessage); + otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); otelScope = otelContext.makeCurrent(); - - return otelMessage; } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @@ -71,13 +67,14 @@ public static void onExit( @Advice.This Connection connection, @Advice.Argument(0) Message message, @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope) { + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { if (otelScope == null) { return; } otelScope.close(); - PRODUCER_INSTRUMENTER.end(otelContext, message, null, throwable); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); } } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java index 3a6fa4d73b9c..9b5053dbec6d 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java @@ -7,13 +7,13 @@ import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createProducerInstrumenter; -import io.nats.client.Message; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; public final class NatsSingletons { - public static final Instrumenter PRODUCER_INSTRUMENTER = + public static final Instrumenter PRODUCER_INSTRUMENTER = createProducerInstrumenter(GlobalOpenTelemetry.get()); private NatsSingletons() {} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationTest.java index ee976e54d25a..2dba5ff5c8d0 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationTest.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationTest.java @@ -16,6 +16,7 @@ import io.nats.client.Message; import io.nats.client.Nats; import io.nats.client.Subscription; +import io.nats.client.impl.Headers; import io.nats.client.impl.NatsMessage; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanKind; @@ -64,10 +65,43 @@ static void afterAll() throws InterruptedException { } @Test - void testPublishMessage() throws InterruptedException { + void testPublishMessageNoHeaders() throws InterruptedException { // given + int clientId = connection.getServerInfo().getClientId(); NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + + // when + testing.runWithSpan("testPublishMessage", () -> connection.publish(message)); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("testPublishMessage").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "publish"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))))); + + // and + Message published = subscription.nextMessage(Duration.ofSeconds(1)); + assertThat(published.getHeaders()).isNull(); + } + + @Test + void testPublishMessageWithHeaders() throws InterruptedException { + // given int clientId = connection.getServerInfo().getClientId(); + NatsMessage message = + NatsMessage.builder().subject("sub").data("x").headers(new Headers()).build(); // when testing.runWithSpan("testPublishMessage", () -> connection.publish(message)); diff --git a/instrumentation/nats/nats-2.21/library/build.gradle.kts b/instrumentation/nats/nats-2.21/library/build.gradle.kts index 0814f79c75df..14496c845003 100644 --- a/instrumentation/nats/nats-2.21/library/build.gradle.kts +++ b/instrumentation/nats/nats-2.21/library/build.gradle.kts @@ -5,5 +5,8 @@ plugins { dependencies { library("io.nats:jnats:2.21.0") + compileOnly("com.google.auto.value:auto-value-annotations") + annotationProcessor("com.google.auto.value:auto-value") + testImplementation(project(":instrumentation:nats:nats-2.21:testing")) } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java index 99bacce26d3f..3ac82d451343 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java @@ -6,9 +6,9 @@ package io.opentelemetry.instrumentation.nats.v2_21; import io.nats.client.Connection; -import io.nats.client.Message; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; public final class NatsTelemetry { @@ -20,9 +20,9 @@ public static NatsTelemetryBuilder builder(OpenTelemetry openTelemetry) { return new NatsTelemetryBuilder(openTelemetry); } - private final Instrumenter producerInstrumenter; + private final Instrumenter producerInstrumenter; - public NatsTelemetry(Instrumenter producerInstrumenter) { + public NatsTelemetry(Instrumenter producerInstrumenter) { this.producerInstrumenter = producerInstrumenter; } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java index 07972c234de9..22630d523659 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java @@ -28,11 +28,11 @@ import io.nats.client.Subscription; import io.nats.client.api.ServerInfo; import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsMessage; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; import java.io.IOException; import java.net.InetAddress; import java.time.Duration; @@ -43,58 +43,42 @@ public class OpenTelemetryConnection implements Connection { private final Connection delegate; - private final Instrumenter producerInstrumenter; + private final Instrumenter producerInstrumenter; public OpenTelemetryConnection( - Connection connection, Instrumenter producerInstrumenter) { + Connection connection, Instrumenter producerInstrumenter) { this.delegate = connection; this.producerInstrumenter = producerInstrumenter; } @Override public void publish(String subject, byte[] body) { - this.publish(NatsMessage.builder().subject(subject).data(body).build()); + wrapPublish(NatsRequest.create(this, subject, body), () -> delegate.publish(subject, body)); } @Override public void publish(String subject, Headers headers, byte[] body) { - this.publish(NatsMessage.builder().subject(subject).headers(headers).data(body).build()); + wrapPublish( + NatsRequest.create(this, subject, headers, body), + () -> delegate.publish(subject, headers, body)); } @Override public void publish(String subject, String replyTo, byte[] body) { - this.publish(NatsMessage.builder().subject(subject).replyTo(replyTo).data(body).build()); + wrapPublish( + NatsRequest.create(this, subject, body), () -> delegate.publish(subject, replyTo, body)); } @Override public void publish(String subject, String replyTo, Headers headers, byte[] body) { - this.publish( - NatsMessage.builder() - .subject(subject) - .replyTo(replyTo) - .headers(headers) - .data(body) - .build()); + wrapPublish( + NatsRequest.create(this, subject, headers, body), + () -> delegate.publish(subject, replyTo, headers, body)); } @Override public void publish(Message message) { - Context parentContext = Context.current(); - - if (!Span.fromContext(parentContext).getSpanContext().isValid() - || !producerInstrumenter.shouldStart(parentContext, message)) { - delegate.publish(message); - return; - } - - Message otelMessage = new OpenTelemetryMessage(this, message); - Context context = producerInstrumenter.start(parentContext, otelMessage); - - try (Scope ignored = context.makeCurrent()) { - delegate.publish(otelMessage); - } finally { - producerInstrumenter.end(context, otelMessage, null, null); - } + wrapPublish(NatsRequest.create(this, message), () -> delegate.publish(message)); } @Override @@ -356,4 +340,21 @@ public ObjectStoreManagement objectStoreManagement(ObjectStoreOptions objectStor throws IOException { return delegate.objectStoreManagement(objectStoreOptions); } + + private void wrapPublish(NatsRequest natsRequest, Runnable publish) { + Context parentContext = Context.current(); + + if (!Span.fromContext(parentContext).getSpanContext().isValid() + || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { + publish.run(); + return; + } + + Context context = producerInstrumenter.start(parentContext, natsRequest); + try (Scope ignored = context.makeCurrent()) { + publish.run(); + } finally { + producerInstrumenter.end(context, natsRequest, null, null); + } + } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessage.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessage.java deleted file mode 100644 index 59edb68a95d7..000000000000 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessage.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_21; - -import io.nats.client.Connection; -import io.nats.client.Message; -import io.nats.client.Subscription; -import io.nats.client.impl.AckType; -import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsJetStreamMetaData; -import io.nats.client.support.Status; -import java.time.Duration; -import java.util.concurrent.TimeoutException; - -/** - * Wrapper with an always NotNull Headers property. - * - * @see io.opentelemetry.instrumentation.nats.v2_21.internal.MessageTextMapSetter#set - */ -public class OpenTelemetryMessage implements Message { - private final Connection connection; - private final Message delegate; - private final Headers headers; - - public OpenTelemetryMessage(Connection connection, Message delegate) { - this.connection = connection; - this.delegate = delegate; - this.headers = new Headers(delegate.getHeaders()); - } - - @Override - public String getSubject() { - return delegate.getSubject(); - } - - @Override - public String getReplyTo() { - return delegate.getReplyTo(); - } - - @Override - public boolean hasHeaders() { - return !headers.isEmpty(); - } - - @Override - public Headers getHeaders() { - return this.headers; - } - - @Override - public boolean isStatusMessage() { - return delegate.isStatusMessage(); - } - - @Override - public Status getStatus() { - return delegate.getStatus(); - } - - @Override - public byte[] getData() { - return delegate.getData(); - } - - @Override - public boolean isUtf8mode() { - return delegate.isUtf8mode(); - } - - @Override - public Subscription getSubscription() { - return delegate.getSubscription(); - } - - @Override - public String getSID() { - return delegate.getSID(); - } - - @Override - public Connection getConnection() { - // Connection is only set for received message. - // To be able to expose the connection.clientId - // in span attributes, let's link it in case of - // a message being sent. - Connection connection = delegate.getConnection(); - if (connection == null) { - connection = this.connection; - } - return connection; - } - - @Override - public NatsJetStreamMetaData metaData() { - return delegate.metaData(); - } - - @Override - public AckType lastAck() { - return delegate.lastAck(); - } - - @Override - public void ack() { - delegate.ack(); - } - - @Override - public void ackSync(Duration timeout) throws TimeoutException, InterruptedException { - delegate.ackSync(timeout); - } - - @Override - public void nak() { - delegate.nak(); - } - - @Override - public void nakWithDelay(Duration nakDelay) { - delegate.nakWithDelay(nakDelay); - } - - @SuppressWarnings("PreferJavaTimeOverload") - @Override - public void nakWithDelay(long nakDelayMillis) { - delegate.nakWithDelay(nakDelayMillis); - } - - @Override - public void term() { - delegate.term(); - } - - @Override - public void inProgress() { - delegate.inProgress(); - } - - @Override - public boolean isJetStream() { - return delegate.isJetStream(); - } -} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageMessagingAttributesGetter.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageMessagingAttributesGetter.java index b291b88fdd3b..4cbea9008313 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageMessagingAttributesGetter.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageMessagingAttributesGetter.java @@ -5,80 +5,82 @@ package io.opentelemetry.instrumentation.nats.v2_21.internal; -import io.nats.client.Message; +import io.nats.client.impl.Headers; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import java.util.Collections; import java.util.List; import javax.annotation.Nullable; -enum MessageMessagingAttributesGetter implements MessagingAttributesGetter { +enum MessageMessagingAttributesGetter implements MessagingAttributesGetter { INSTANCE; @Nullable @Override - public String getSystem(Message message) { + public String getSystem(NatsRequest request) { return "nats"; } @Nullable @Override - public String getDestination(Message message) { - return message.getSubject(); + public String getDestination(NatsRequest request) { + return request.getSubject(); } @Nullable @Override - public String getDestinationTemplate(Message message) { + public String getDestinationTemplate(NatsRequest request) { return null; } @Override - public boolean isTemporaryDestination(Message message) { + public boolean isTemporaryDestination(NatsRequest request) { return false; } @Override - public boolean isAnonymousDestination(Message message) { + public boolean isAnonymousDestination(NatsRequest request) { return false; } @Nullable @Override - public String getConversationId(Message message) { + public String getConversationId(NatsRequest request) { return null; } @Nullable @Override - public Long getMessageBodySize(Message message) { - return (long) message.getData().length; + public Long getMessageBodySize(NatsRequest request) { + return request.getDataSize(); } @Nullable @Override - public Long getMessageEnvelopeSize(Message message) { + public Long getMessageEnvelopeSize(NatsRequest request) { return null; } @Nullable @Override - public String getMessageId(Message message, @Nullable Void unused) { + public String getMessageId(NatsRequest request, @Nullable Void unused) { return null; } @Nullable @Override - public String getClientId(Message message) { - return String.valueOf(message.getConnection().getServerInfo().getClientId()); + public String getClientId(NatsRequest request) { + return String.valueOf(request.getConnection().getServerInfo().getClientId()); } @Nullable @Override - public Long getBatchMessageCount(Message message, @Nullable Void unused) { + public Long getBatchMessageCount(NatsRequest request, @Nullable Void unused) { return null; } @Override - public List getMessageHeader(Message message, String name) { - return message.getHeaders().get(name); + public List getMessageHeader(NatsRequest request, String name) { + Headers headers = request.getHeaders(); + return headers == null ? Collections.emptyList() : headers.get(name); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageTextMapSetter.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageTextMapSetter.java index d8bae4f22bc5..247b190dabe1 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageTextMapSetter.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageTextMapSetter.java @@ -5,22 +5,19 @@ package io.opentelemetry.instrumentation.nats.v2_21.internal; -import io.nats.client.Message; import io.opentelemetry.context.propagation.TextMapSetter; import javax.annotation.Nullable; -enum MessageTextMapSetter implements TextMapSetter { +enum MessageTextMapSetter implements TextMapSetter { INSTANCE; @Override /* Can not work if getHeaders doesn't return a writable structure. */ - public void set(@Nullable Message carrier, String key, String value) { - if (carrier == null) { + public void set(@Nullable NatsRequest request, String key, String value) { + if (request == null || request.getHeaders() == null || request.getHeaders().isReadOnly()) { return; } - if (carrier.getHeaders() != null && !carrier.getHeaders().isReadOnly()) { - carrier.getHeaders().put(key, value); - } + request.getHeaders().put(key, value); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java index dcd57fd298ae..00b4ab1ec54b 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java @@ -5,7 +5,6 @@ package io.opentelemetry.instrumentation.nats.v2_21.internal; -import io.nats.client.Message; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; @@ -23,17 +22,17 @@ public final class NatsInstrumenterFactory { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.nats-2.21"; - public static final SpanNameExtractor PRODUCER_SPAN_NAME_EXTRACTOR = + public static final SpanNameExtractor PRODUCER_SPAN_NAME_EXTRACTOR = MessagingSpanNameExtractor.create( MessageMessagingAttributesGetter.INSTANCE, MessageOperation.PUBLISH); - public static final AttributesExtractor PUBLISH_ATTRIBUTES_EXTRACTOR = + public static final AttributesExtractor PUBLISH_ATTRIBUTES_EXTRACTOR = MessagingAttributesExtractor.create( MessageMessagingAttributesGetter.INSTANCE, MessageOperation.PUBLISH); - public static Instrumenter createProducerInstrumenter( + public static Instrumenter createProducerInstrumenter( OpenTelemetry openTelemetry) { - return Instrumenter.builder( + return Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, PRODUCER_SPAN_NAME_EXTRACTOR) .addAttributesExtractor(PUBLISH_ATTRIBUTES_EXTRACTOR) .buildProducerInstrumenter(MessageTextMapSetter.INSTANCE); diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java new file mode 100644 index 000000000000..7cf2acd050ed --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21.internal; + +import com.google.auto.value.AutoValue; +import io.nats.client.Connection; +import io.nats.client.Message; +import io.nats.client.impl.Headers; +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time.", or "This class is internal and experimental. Its APIs are unstable and can change at + * any time. Its APIs (or a version of them) may be promoted to the public stable API in the future, + * but no guarantees are made. + */ +@AutoValue +public abstract class NatsRequest { + + public static NatsRequest create(Connection connection, String subject, byte[] data) { + return new AutoValue_NatsRequest(connection, subject, null, getDataSize(data)); + } + + public static NatsRequest create( + Connection connection, String subject, Headers headers, byte[] data) { + return new AutoValue_NatsRequest(connection, subject, headers, getDataSize(data)); + } + + public static NatsRequest create(Connection connection, Message message) { + return new AutoValue_NatsRequest( + message.getConnection() == null ? connection : message.getConnection(), + message.getSubject(), + message.getHeaders(), + getDataSize(message.getData())); + } + + public abstract Connection getConnection(); + + public abstract String getSubject(); + + @Nullable + public abstract Headers getHeaders(); + + public abstract long getDataSize(); + + private static long getDataSize(byte[] data) { + return data == null ? 0 : data.length; + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsPublishTest.java similarity index 64% rename from instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryTest.java rename to instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsPublishTest.java index 5b35e0d46501..53e5723553fd 100644 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryTest.java +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsPublishTest.java @@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import io.nats.client.Message; +import io.nats.client.impl.Headers; import io.nats.client.impl.NatsMessage; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanKind; @@ -23,13 +24,13 @@ import org.junit.jupiter.api.extension.RegisterExtension; @SuppressWarnings("deprecation") // using deprecated semconv -class NatsTelemetryTest { +class NatsPublishTest { @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); @Test - void testPublishMessage() { + void testPublishMessageNoHeaders() { // given NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); TestConnection testConnection = new TestConnection(); @@ -39,6 +40,40 @@ void testPublishMessage() { // when testing.runWithSpan("testPublishMessage", () -> connection.publish(message)); + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("testPublishMessage").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "publish"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + + // and + Message published = testConnection.publishedMessages.peekLast(); + assertNotNull(published); + assertThat(published.getHeaders()).isNull(); + } + + @Test + void testPublishMessageWithHeaders() { + // given + NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + NatsMessage message = + NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + + // when + testing.runWithSpan("testPublishMessage", () -> connection.publish(message)); + // then testing.waitAndAssertTraces( trace -> From 830bf689cab2bd7c895b5341d17b48431f45cb84 Mon Sep 17 00:00:00 2001 From: Alix Date: Fri, 2 May 2025 18:04:10 +0200 Subject: [PATCH 03/34] cover all publish methods with tests --- .../nats/v2_21/ConnectionInstrumentation.java | 80 ----- .../ConnectionPublishInstrumentation.java | 277 ++++++++++++++++++ .../nats/v2_21/NatsInstrumentationModule.java | 2 +- ...va => NatsInstrumentationPublishTest.java} | 113 ++++--- ...est.java => NatsTelemetryPublishTest.java} | 106 +++++-- 5 files changed, 433 insertions(+), 145 deletions(-) delete mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionInstrumentation.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java rename instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/{NatsInstrumentationTest.java => NatsInstrumentationPublishTest.java} (65%) rename instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/{NatsPublishTest.java => NatsTelemetryPublishTest.java} (55%) diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionInstrumentation.java deleted file mode 100644 index f39570d40037..000000000000 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionInstrumentation.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; - -import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.PRODUCER_INSTRUMENTER; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; -import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import io.nats.client.Connection; -import io.nats.client.Message; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public class ConnectionInstrumentation implements TypeInstrumentation { - - @Override - public ElementMatcher typeMatcher() { - return implementsInterface(named("io.nats.client.Connection")); - } - - @Override - public void transform(TypeTransformer transformer) { - transformer.applyAdviceToMethod( - isPublic() - .and(named("publish")) - .and(takesArguments(1)) - .and(takesArgument(0, named("io.nats.client.Message"))), - ConnectionInstrumentation.class.getName() + "$PublishMessageAdvice"); - } - - @SuppressWarnings("unused") - public static class PublishMessageAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( - @Advice.This Connection connection, - @Advice.Argument(0) Message message, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - Context parentContext = Context.current(); - natsRequest = NatsRequest.create(connection, message); - - if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); - otelScope = otelContext.makeCurrent(); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.Thrown Throwable throwable, - @Advice.This Connection connection, - @Advice.Argument(0) Message message, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - otelScope.close(); - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); - } - } -} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java new file mode 100644 index 000000000000..82c1cf8a7d1d --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java @@ -0,0 +1,277 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.PRODUCER_INSTRUMENTER; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.nats.client.Connection; +import io.nats.client.Message; +import io.nats.client.impl.Headers; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class ConnectionPublishInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("io.nats.client.Connection")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isPublic() + .and(named("publish")) + .and(takesArguments(2)) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, byte[].class)), + ConnectionPublishInstrumentation.class.getName() + "$PublishBodyAdvice"); + transformer.applyAdviceToMethod( + isPublic() + .and(named("publish")) + .and(takesArguments(3)) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, named("io.nats.client.impl.Headers"))) + .and(takesArgument(2, byte[].class)), + ConnectionPublishInstrumentation.class.getName() + "$PublishBodyHeadersAdvice"); + transformer.applyAdviceToMethod( + isPublic() + .and(named("publish")) + .and(takesArguments(3)) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, String.class)) + .and(takesArgument(2, byte[].class)), + ConnectionPublishInstrumentation.class.getName() + "$PublishBodyReplyToAdvice"); + transformer.applyAdviceToMethod( + isPublic() + .and(named("publish")) + .and(takesArguments(4)) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, String.class)) + .and(takesArgument(2, named("io.nats.client.impl.Headers"))) + .and(takesArgument(3, byte[].class)), + ConnectionPublishInstrumentation.class.getName() + "$PublishBodyReplyToHeadersAdvice"); + transformer.applyAdviceToMethod( + isPublic() + .and(named("publish")) + .and(takesArguments(1)) + .and(takesArgument(0, named("io.nats.client.Message"))), + ConnectionPublishInstrumentation.class.getName() + "$PublishMessageAdvice"); + } + + @SuppressWarnings("unused") + public static class PublishBodyAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) String subject, + @Advice.Argument(1) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + Context parentContext = Context.current(); + natsRequest = NatsRequest.create(connection, subject, body); + + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + return; + } + + otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Thrown Throwable throwable, + @Advice.Argument(0) String subject, + @Advice.Argument(1) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + otelScope.close(); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + } + } + + @SuppressWarnings("unused") + public static class PublishBodyHeadersAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) String subject, + @Advice.Argument(1) Headers headers, + @Advice.Argument(2) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + Context parentContext = Context.current(); + natsRequest = NatsRequest.create(connection, subject, headers, body); + + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + return; + } + + otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Thrown Throwable throwable, + @Advice.Argument(0) String subject, + @Advice.Argument(1) Headers headers, + @Advice.Argument(2) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + otelScope.close(); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + } + } + + @SuppressWarnings("unused") + public static class PublishBodyReplyToAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) String subject, + @Advice.Argument(1) String replyTo, + @Advice.Argument(2) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + Context parentContext = Context.current(); + natsRequest = NatsRequest.create(connection, subject, body); + + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + return; + } + + otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Thrown Throwable throwable, + @Advice.Argument(0) String subject, + @Advice.Argument(1) String replyTo, + @Advice.Argument(2) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + otelScope.close(); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + } + } + + @SuppressWarnings("unused") + public static class PublishBodyReplyToHeadersAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) String subject, + @Advice.Argument(1) String replyTo, + @Advice.Argument(2) Headers headers, + @Advice.Argument(3) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + Context parentContext = Context.current(); + natsRequest = NatsRequest.create(connection, subject, headers, body); + + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + return; + } + + otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Thrown Throwable throwable, + @Advice.Argument(0) String subject, + @Advice.Argument(1) String replyTo, + @Advice.Argument(2) Headers headers, + @Advice.Argument(3) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + otelScope.close(); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + } + } + + @SuppressWarnings("unused") + public static class PublishMessageAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) Message message, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + Context parentContext = Context.current(); + natsRequest = NatsRequest.create(connection, message); + + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + return; + } + + otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Thrown Throwable throwable, + @Advice.This Connection connection, + @Advice.Argument(0) Message message, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + otelScope.close(); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + } + } +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java index 1cf3ba651e13..71388a8cfa25 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java @@ -22,6 +22,6 @@ public NatsInstrumentationModule() { @Override public List typeInstrumentations() { - return Collections.singletonList(new ConnectionInstrumentation()); + return Collections.singletonList(new ConnectionPublishInstrumentation()); } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java similarity index 65% rename from instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationTest.java rename to instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java index 2dba5ff5c8d0..b68d08c4edf6 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationTest.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java @@ -24,16 +24,17 @@ import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import java.io.IOException; import java.time.Duration; -import java.util.LinkedList; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; @SuppressWarnings("deprecation") // using deprecated semconv -class NatsInstrumentationTest { +class NatsInstrumentationPublishTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); @@ -46,71 +47,103 @@ class NatsInstrumentationTest { static String natsUrl; static Connection connection; static Subscription subscription; - - static LinkedList publishedMessages = new LinkedList<>(); + static int clientId; @BeforeAll - static void beforeAll() throws IOException, InterruptedException { + static void beforeAll() { natsContainer.start(); natsUrl = "nats://" + natsContainer.getHost() + ":" + natsContainer.getMappedPort(4222); + } + + @AfterAll + static void afterAll() { + natsContainer.close(); + } + + @BeforeEach + void beforeEach() throws IOException, InterruptedException { connection = Nats.connect(natsUrl); subscription = connection.subscribe("*"); + clientId = connection.getServerInfo().getClientId(); } - @AfterAll - static void afterAll() throws InterruptedException { - subscription.drain(Duration.ofSeconds(10)); + @AfterEach + void afterEach() throws InterruptedException { + subscription.drain(Duration.ofSeconds(1)); connection.close(); - natsContainer.close(); + } + + @Test + void testPublishBodyNoHeaders() throws InterruptedException { + // when + testing.runWithSpan("parent", () -> connection.publish("sub", new byte[] {0})); + + // then + assertPublishSpan(); + assertNoHeaders(); + } + + @Test + void testPublishBodyWithHeaders() throws InterruptedException { + // when + testing.runWithSpan("parent", () -> connection.publish("sub", new Headers(), new byte[] {0})); + + // then + assertPublishSpan(); + assertTraceparentHeader(); + } + + @Test + void testPublishBodyReplyToNoHeaders() throws InterruptedException { + // when + testing.runWithSpan("parent", () -> connection.publish("sub", "rt", new byte[] {0})); + + // then + assertPublishSpan(); + assertNoHeaders(); + } + + @Test + void testPublishBodyReplyToWithHeaders() throws InterruptedException { + // when + testing.runWithSpan( + "parent", () -> connection.publish("sub", "rt", new Headers(), new byte[] {0})); + + // then + assertPublishSpan(); + assertTraceparentHeader(); } @Test void testPublishMessageNoHeaders() throws InterruptedException { - // given - int clientId = connection.getServerInfo().getClientId(); NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when - testing.runWithSpan("testPublishMessage", () -> connection.publish(message)); + testing.runWithSpan("parent", () -> connection.publish(message)); // then - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("testPublishMessage").hasNoParent(), - span -> - span.hasName("sub publish") - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "publish"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))))); - - // and - Message published = subscription.nextMessage(Duration.ofSeconds(1)); - assertThat(published.getHeaders()).isNull(); + assertPublishSpan(); + assertNoHeaders(); } @Test void testPublishMessageWithHeaders() throws InterruptedException { - // given - int clientId = connection.getServerInfo().getClientId(); NatsMessage message = NatsMessage.builder().subject("sub").data("x").headers(new Headers()).build(); // when - testing.runWithSpan("testPublishMessage", () -> connection.publish(message)); + testing.runWithSpan("parent", () -> connection.publish(message)); // then + assertPublishSpan(); + assertTraceparentHeader(); + } + + private static void assertPublishSpan() { testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( - span -> span.hasName("testPublishMessage").hasNoParent(), + span -> span.hasName("parent").hasNoParent(), span -> span.hasName("sub publish") .hasKind(SpanKind.PRODUCER) @@ -123,8 +156,14 @@ void testPublishMessageWithHeaders() throws InterruptedException { equalTo( AttributeKey.stringKey("messaging.client_id"), String.valueOf(clientId))))); + } + + private static void assertNoHeaders() throws InterruptedException { + Message published = subscription.nextMessage(Duration.ofSeconds(1)); + assertThat(published.getHeaders()).isNull(); + } - // and + private static void assertTraceparentHeader() throws InterruptedException { Message published = subscription.nextMessage(Duration.ofSeconds(1)); assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); } diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsPublishTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java similarity index 55% rename from instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsPublishTest.java rename to instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java index 53e5723553fd..478a49e1953f 100644 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsPublishTest.java +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java @@ -24,61 +24,106 @@ import org.junit.jupiter.api.extension.RegisterExtension; @SuppressWarnings("deprecation") // using deprecated semconv -class NatsPublishTest { +class NatsTelemetryPublishTest { @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + static final NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); + + @Test + void testPublishBodyNoHeaders() { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + + // when + testing.runWithSpan("parent", () -> connection.publish("sub", new byte[] {0})); + + // then + assertPublishSpan(); + assertNoHeaders(testConnection); + } + + @Test + void testPublishBodyWithHeaders() { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + + // when + testing.runWithSpan("parent", () -> connection.publish("sub", new Headers(), new byte[] {0})); + + // then + assertPublishSpan(); + assertTraceparentHeader(testConnection); + } + + @Test + void testPublishBodyReplyToNoHeaders() { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + + // when + testing.runWithSpan("parent", () -> connection.publish("sub", "rt", new byte[] {0})); + + // then + assertPublishSpan(); + assertNoHeaders(testConnection); + } + + @Test + void testPublishBodyReplyToWithHeaders() { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + + // when + testing.runWithSpan( + "parent", () -> connection.publish("sub", "rt", new Headers(), new byte[] {0})); + + // then + assertPublishSpan(); + assertTraceparentHeader(testConnection); + } + @Test void testPublishMessageNoHeaders() { // given - NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when - testing.runWithSpan("testPublishMessage", () -> connection.publish(message)); + testing.runWithSpan("parent", () -> connection.publish(message)); // then - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("testPublishMessage").hasNoParent(), - span -> - span.hasName("sub publish") - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "publish"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); - - // and - Message published = testConnection.publishedMessages.peekLast(); - assertNotNull(published); - assertThat(published.getHeaders()).isNull(); + assertPublishSpan(); + assertNoHeaders(testConnection); } @Test void testPublishMessageWithHeaders() { // given - NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); NatsMessage message = NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); // when - testing.runWithSpan("testPublishMessage", () -> connection.publish(message)); + testing.runWithSpan("parent", () -> connection.publish(message)); // then + assertPublishSpan(); + assertTraceparentHeader(testConnection); + } + + private static void assertPublishSpan() { testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( - span -> span.hasName("testPublishMessage").hasNoParent(), + span -> span.hasName("parent").hasNoParent(), span -> span.hasName("sub publish") .hasKind(SpanKind.PRODUCER) @@ -89,9 +134,16 @@ void testPublishMessageWithHeaders() { equalTo(MESSAGING_DESTINATION_NAME, "sub"), equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + } + + private static void assertNoHeaders(TestConnection connection) { + Message published = connection.publishedMessages.peekLast(); + assertNotNull(published); + assertThat(published.getHeaders()).isNull(); + } - // and - Message published = testConnection.publishedMessages.peekLast(); + private static void assertTraceparentHeader(TestConnection connection) { + Message published = connection.publishedMessages.peekLast(); assertNotNull(published); assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); } From c4f11a619fa827401fdb68fb8c7c49d92a7443f0 Mon Sep 17 00:00:00 2001 From: Alix Date: Sun, 4 May 2025 10:32:44 +0200 Subject: [PATCH 04/34] Instrument NATS Subscription.nextMessage --- .../ConnectionSubscribeInstrumentation.java | 48 +++++ .../nats/v2_21/NatsInstrumentationModule.java | 7 +- .../nats/v2_21/NatsSingletons.java | 4 + .../v2_21/SubscriptionInstrumentation.java | 104 +++++++++ .../nats/v2_21/internal/NatsData.java | 36 ++++ .../v2_21/NatsInstrumentationPublishTest.java | 2 +- .../NatsInstrumentationSubscribeTest.java | 200 ++++++++++++++++++ .../nats/v2_21/NatsTelemetry.java | 9 +- .../nats/v2_21/NatsTelemetryBuilder.java | 4 +- .../nats/v2_21/OpenTelemetryConnection.java | 88 ++++---- .../nats/v2_21/OpenTelemetrySubscription.java | 158 ++++++++++++++ .../internal/NatsInstrumenterFactory.java | 31 ++- .../nats/v2_21/internal/NatsRequest.java | 23 +- ...NatsRequestMessagingAttributesGetter.java} | 4 +- .../internal/NatsRequestTextMapGetter.java | 36 ++++ ...ter.java => NatsRequestTextMapSetter.java} | 2 +- .../nats/v2_21/internal/ThrowingSupplier.java | 16 ++ .../nats/v2_21/NatsTelemetryPublishTest.java | 4 +- .../v2_21/NatsTelemetrySubscribeTest.java | 131 ++++++++++++ .../nats/v2_21/TestConnection.java | 24 ++- .../nats/v2_21/TestMessage.java | 135 ++++++++++++ .../nats/v2_21/TestSubscription.java | 132 ++++++++++++ 22 files changed, 1133 insertions(+), 65 deletions(-) create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/NatsData.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscribeTest.java create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java rename instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/{MessageMessagingAttributesGetter.java => NatsRequestMessagingAttributesGetter.java} (90%) create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapGetter.java rename instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/{MessageTextMapSetter.java => NatsRequestTextMapSetter.java} (89%) create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier.java create mode 100644 instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscribeTest.java create mode 100644 instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestMessage.java create mode 100644 instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java new file mode 100644 index 000000000000..a24719c94844 --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.nats.client.Connection; +import io.nats.client.Subscription; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.nats.v2_21.internal.NatsData; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class ConnectionSubscribeInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("io.nats.client.Connection")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isPublic() + .and(named("subscribe")) + .and(takesArgument(0, String.class)) + .and(returns(named("io.nats.client.Subscription"))), + ConnectionSubscribeInstrumentation.class.getName() + "$SubscribeAdvice"); + } + + @SuppressWarnings("unused") + public static class SubscribeAdvice { + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.This Connection connection, @Advice.Return Subscription subscription) { + NatsData.addSubscription(subscription, connection); + } + } +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java index 71388a8cfa25..332040465fff 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java @@ -8,7 +8,7 @@ import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -import java.util.Collections; +import java.util.Arrays; import java.util.List; @AutoService(InstrumentationModule.class) @@ -22,6 +22,9 @@ public NatsInstrumentationModule() { @Override public List typeInstrumentations() { - return Collections.singletonList(new ConnectionPublishInstrumentation()); + return Arrays.asList( + new ConnectionSubscribeInstrumentation(), + new ConnectionPublishInstrumentation(), + new SubscriptionInstrumentation()); } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java index 9b5053dbec6d..c6f23b4d74ae 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createConsumerInstrumenter; import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createProducerInstrumenter; import io.opentelemetry.api.GlobalOpenTelemetry; @@ -16,5 +17,8 @@ public final class NatsSingletons { public static final Instrumenter PRODUCER_INSTRUMENTER = createProducerInstrumenter(GlobalOpenTelemetry.get()); + public static final Instrumenter CONSUMER_INSTRUMENTER = + createConsumerInstrumenter(GlobalOpenTelemetry.get()); + private NatsSingletons() {} } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java new file mode 100644 index 000000000000..f935f12d4154 --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java @@ -0,0 +1,104 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.CONSUMER_INSTRUMENTER; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.nats.client.Connection; +import io.nats.client.Message; +import io.nats.client.Subscription; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; +import io.opentelemetry.instrumentation.api.internal.Timer; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.nats.v2_21.internal.NatsData; +import java.util.concurrent.TimeoutException; +import javax.annotation.Nullable; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class SubscriptionInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("io.nats.client.Subscription")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isPublic() + .and(named("nextMessage")) + .and(takesArguments(1)) + .and(returns(named("io.nats.client.Message"))), + SubscriptionInstrumentation.class.getName() + "$NextMessageAdvice"); + transformer.applyAdviceToMethod( + isPublic().and(named("unsubscribe")), + SubscriptionInstrumentation.class.getName() + "$UnsubscribeAdvice"); + } + + @SuppressWarnings("unused") + public static class NextMessageAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static Timer onEnter() { + return Timer.start(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Thrown Throwable throwable, + @Advice.This Subscription subscription, + @Advice.Enter Timer timer, + @Advice.Return @Nullable Message message) { + Context parentContext = Context.current(); + TimeoutException timeout = null; + + Connection connection = NatsData.getConnection(subscription); + + // connection should always be non-null at this stage + if (connection == null) { + return; + } + + NatsRequest natsRequest = NatsRequest.create(connection, subscription.getSubject()); + if (message == null) { + timeout = new TimeoutException("Timed out waiting for message"); + } else { + natsRequest = NatsRequest.create(connection, message); + } + + if (!CONSUMER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + return; + } + + InstrumenterUtil.startAndEnd( + CONSUMER_INSTRUMENTER, + parentContext, + natsRequest, + null, + timeout, + timer.startTime(), + timer.now()); + } + } + + @SuppressWarnings("unused") + public static class UnsubscribeAdvice { + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit(@Advice.This Subscription subscription) { + NatsData.removeSubscription(subscription); + } + } +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/NatsData.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/NatsData.java new file mode 100644 index 000000000000..b0ae3d3b5c04 --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/NatsData.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21.internal; + +import io.nats.client.Connection; +import io.nats.client.Subscription; +import io.opentelemetry.instrumentation.api.util.VirtualField; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time.", or "This class is internal and experimental. Its APIs are unstable and can change at + * any time. Its APIs (or a version of them) may be promoted to the public stable API in the future, + * but no guarantees are made. + */ +public final class NatsData { + + private static final VirtualField subscriptionConnection = + VirtualField.find(Subscription.class, Connection.class); + + public static void addSubscription(Subscription subscription, Connection connection) { + subscriptionConnection.set(subscription, connection); + } + + public static void removeSubscription(Subscription subscription) { + subscriptionConnection.set(subscription, null); + } + + public static Connection getConnection(Subscription subscription) { + return subscriptionConnection.get(subscription); + } + + private NatsData() {} +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java index b68d08c4edf6..4a4a08ef3830 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java @@ -63,7 +63,7 @@ static void afterAll() { @BeforeEach void beforeEach() throws IOException, InterruptedException { connection = Nats.connect(natsUrl); - subscription = connection.subscribe("*"); + subscription = connection.subscribe("sub"); clientId = connection.getServerInfo().getClientId(); } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscribeTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscribeTest.java new file mode 100644 index 000000000000..48fe2edb7a4b --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscribeTest.java @@ -0,0 +1,200 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; +import static org.assertj.core.api.Assertions.assertThat; + +import io.nats.client.Connection; +import io.nats.client.Nats; +import io.nats.client.Subscription; +import io.nats.client.impl.Headers; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +@SuppressWarnings("deprecation") // using deprecated semconv +class NatsInstrumentationSubscribeTest { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + static final DockerImageName natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); + + static final GenericContainer natsContainer = + new GenericContainer<>(natsImage).withExposedPorts(4222); + + static String natsUrl; + static Connection connection; + static Subscription subscription; + static int clientId; + + @BeforeAll + static void beforeAll() { + natsContainer.start(); + natsUrl = "nats://" + natsContainer.getHost() + ":" + natsContainer.getMappedPort(4222); + } + + @AfterAll + static void afterAll() { + natsContainer.close(); + } + + @BeforeEach + void beforeEach() throws IOException, InterruptedException { + connection = Nats.connect(natsUrl); + subscription = connection.subscribe("sub"); + clientId = connection.getServerInfo().getClientId(); + } + + @AfterEach + void afterEach() throws InterruptedException { + subscription.drain(Duration.ofSeconds(1)); + connection.close(); + } + + @Test + void testSubscribeTimeout() throws InterruptedException { + // when + testing.runWithSpan("parent", () -> subscription.nextMessage(Duration.ofSeconds(1))); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasException(new TimeoutException("Timed out waiting for message")) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 0), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))))); + } + + @Test + void testSubscribeNoLink() throws InterruptedException { + // given + Headers headers = new Headers(new Headers(), /* readOnly= */ true); + + // when + testing.runWithSpan( + "parent", + () -> { + connection.publish("sub", headers, new byte[] {0}); + subscription.nextMessage(Duration.ofSeconds(1)); + }); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "publish"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))), + span -> + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))))); + } + + @SuppressWarnings("PreferJavaTimeOverload") + @Test + void testSubscribeWithLink() throws InterruptedException { + // given + String linkTraceId = "0af7651916cd43dd8448eb211c80319c"; + Headers headers = + new Headers( + new Headers().put("traceparent", "00-" + linkTraceId + "-b7ad6b7169203331-01"), + /* readOnly= */ true); + + // when + testing.runWithSpan( + "parent", + () -> { + connection.publish("sub", headers, new byte[] {0}); + subscription.nextMessage(Duration.ofSeconds(1)); + }); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "publish"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))), + span -> + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasLinksSatisfying( + links -> + assertThat(links) + .singleElement() + .satisfies( + link -> + assertThat(link.getSpanContext().getTraceId()) + .isEqualTo(linkTraceId))) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))))); + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java index 3ac82d451343..3d38d476d557 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java @@ -21,12 +21,17 @@ public static NatsTelemetryBuilder builder(OpenTelemetry openTelemetry) { } private final Instrumenter producerInstrumenter; + private final Instrumenter consumerInstrumenter; - public NatsTelemetry(Instrumenter producerInstrumenter) { + public NatsTelemetry( + Instrumenter producerInstrumenter, + Instrumenter consumerInstrumenter) { this.producerInstrumenter = producerInstrumenter; + this.consumerInstrumenter = consumerInstrumenter; } public OpenTelemetryConnection wrap(Connection connection) { - return new OpenTelemetryConnection(connection, this.producerInstrumenter); + return new OpenTelemetryConnection( + connection, this.producerInstrumenter, this.consumerInstrumenter); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java index 79de839074f6..59da60c37180 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java @@ -17,6 +17,8 @@ public final class NatsTelemetryBuilder { } public NatsTelemetry build() { - return new NatsTelemetry(NatsInstrumenterFactory.createProducerInstrumenter(openTelemetry)); + return new NatsTelemetry( + NatsInstrumenterFactory.createProducerInstrumenter(openTelemetry), + NatsInstrumenterFactory.createConsumerInstrumenter(openTelemetry)); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java index 22630d523659..24e547c42f8b 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java @@ -44,11 +44,15 @@ public class OpenTelemetryConnection implements Connection { private final Connection delegate; private final Instrumenter producerInstrumenter; + private final Instrumenter consumerInstrumenter; public OpenTelemetryConnection( - Connection connection, Instrumenter producerInstrumenter) { + Connection connection, + Instrumenter producerInstrumenter, + Instrumenter consumerInstrumenter) { this.delegate = connection; this.producerInstrumenter = producerInstrumenter; + this.consumerInstrumenter = consumerInstrumenter; } @Override @@ -82,24 +86,25 @@ public void publish(Message message) { } @Override - public CompletableFuture request(String s, byte[] bytes) { - return delegate.request(s, bytes); + public CompletableFuture request(String subject, byte[] body) { + return delegate.request(subject, body); } @Override - public Message request(String s, byte[] bytes, Duration duration) throws InterruptedException { - return delegate.request(s, bytes, duration); + public Message request(String subject, byte[] body, Duration timeout) + throws InterruptedException { + return delegate.request(subject, body, timeout); } @Override - public CompletableFuture request(String s, Headers headers, byte[] bytes) { - return delegate.request(s, headers, bytes); + public CompletableFuture request(String subject, Headers headers, byte[] body) { + return delegate.request(subject, headers, body); } @Override - public Message request(String s, Headers headers, byte[] bytes, Duration duration) + public Message request(String subject, Headers headers, byte[] body, Duration timeout) throws InterruptedException { - return delegate.request(s, headers, bytes, duration); + return delegate.request(subject, headers, body, timeout); } @Override @@ -108,34 +113,37 @@ public CompletableFuture request(Message message) { } @Override - public Message request(Message message, Duration duration) throws InterruptedException { - return delegate.request(message, duration); + public Message request(Message message, Duration timeout) throws InterruptedException { + return delegate.request(message, timeout); } @Override - public CompletableFuture requestWithTimeout(String s, byte[] bytes, Duration duration) { - return delegate.requestWithTimeout(s, bytes, duration); + public CompletableFuture requestWithTimeout( + String subject, byte[] body, Duration timeout) { + return delegate.requestWithTimeout(subject, body, timeout); } @Override public CompletableFuture requestWithTimeout( - String s, Headers headers, byte[] bytes, Duration duration) { - return delegate.requestWithTimeout(s, headers, bytes, duration); + String subject, Headers headers, byte[] body, Duration timeout) { + return delegate.requestWithTimeout(subject, headers, body, timeout); } @Override - public CompletableFuture requestWithTimeout(Message message, Duration duration) { - return delegate.requestWithTimeout(message, duration); + public CompletableFuture requestWithTimeout(Message message, Duration timeout) { + return delegate.requestWithTimeout(message, timeout); } @Override - public Subscription subscribe(String s) { - return delegate.subscribe(s); + public Subscription subscribe(String subject) { + return new OpenTelemetrySubscription( + this, delegate.subscribe(subject), this.consumerInstrumenter); } @Override - public Subscription subscribe(String s, String s1) { - return delegate.subscribe(s, s1); + public Subscription subscribe(String subject, String queueName) { + return new OpenTelemetrySubscription( + this, delegate.subscribe(subject, queueName), this.consumerInstrumenter); } @Override @@ -164,14 +172,14 @@ public void removeConnectionListener(ConnectionListener connectionListener) { } @Override - public void flush(Duration duration) throws TimeoutException, InterruptedException { - delegate.flush(duration); + public void flush(Duration timeout) throws TimeoutException, InterruptedException { + delegate.flush(timeout); } @Override - public CompletableFuture drain(Duration duration) + public CompletableFuture drain(Duration timeout) throws TimeoutException, InterruptedException { - return delegate.drain(duration); + return delegate.drain(timeout); } @Override @@ -256,26 +264,28 @@ public Duration RTT() throws IOException { } @Override - public StreamContext getStreamContext(String s) throws IOException, JetStreamApiException { - return delegate.getStreamContext(s); + public StreamContext getStreamContext(String streamName) + throws IOException, JetStreamApiException { + return delegate.getStreamContext(streamName); } @Override - public StreamContext getStreamContext(String s, JetStreamOptions jetStreamOptions) + public StreamContext getStreamContext(String streamName, JetStreamOptions jetStreamOptions) throws IOException, JetStreamApiException { - return delegate.getStreamContext(s, jetStreamOptions); + return delegate.getStreamContext(streamName, jetStreamOptions); } @Override - public ConsumerContext getConsumerContext(String s, String s1) + public ConsumerContext getConsumerContext(String streamName, String consumerName) throws IOException, JetStreamApiException { - return delegate.getConsumerContext(s, s1); + return delegate.getConsumerContext(streamName, consumerName); } @Override - public ConsumerContext getConsumerContext(String s, String s1, JetStreamOptions jetStreamOptions) + public ConsumerContext getConsumerContext( + String streamName, String consumerName, JetStreamOptions jetStreamOptions) throws IOException, JetStreamApiException { - return delegate.getConsumerContext(s, s1, jetStreamOptions); + return delegate.getConsumerContext(streamName, consumerName, jetStreamOptions); } @Override @@ -300,8 +310,8 @@ public JetStreamManagement jetStreamManagement(JetStreamOptions jetStreamOptions } @Override - public KeyValue keyValue(String s) throws IOException { - return delegate.keyValue(s); + public KeyValue keyValue(String bucketName) throws IOException { + return delegate.keyValue(bucketName); } @Override @@ -320,14 +330,14 @@ public KeyValueManagement keyValueManagement(KeyValueOptions keyValueOptions) th } @Override - public ObjectStore objectStore(String s) throws IOException { - return delegate.objectStore(s); + public ObjectStore objectStore(String bucketName) throws IOException { + return delegate.objectStore(bucketName); } @Override - public ObjectStore objectStore(String s, ObjectStoreOptions objectStoreOptions) + public ObjectStore objectStore(String bucketName, ObjectStoreOptions objectStoreOptions) throws IOException { - return delegate.objectStore(s, objectStoreOptions); + return delegate.objectStore(bucketName, objectStoreOptions); } @Override diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java new file mode 100644 index 000000000000..e00cf1c9fc5b --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java @@ -0,0 +1,158 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.nats.client.Dispatcher; +import io.nats.client.Message; +import io.nats.client.Subscription; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; +import io.opentelemetry.instrumentation.api.internal.Timer; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_21.internal.ThrowingSupplier; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; + +public class OpenTelemetrySubscription implements Subscription { + + private final Connection connection; + private final Subscription delegate; + private final Instrumenter consumerInstrumenter; + + public OpenTelemetrySubscription( + Connection connection, + Subscription subscription, + Instrumenter consumerInstrumenter) { + this.connection = connection; + this.delegate = subscription; + this.consumerInstrumenter = consumerInstrumenter; + } + + @Override + public String getSubject() { + return delegate.getSubject(); + } + + @Override + public String getQueueName() { + return delegate.getQueueName(); + } + + @Override + public Dispatcher getDispatcher() { + return delegate.getDispatcher(); + } + + @SuppressWarnings("ThrowsUncheckedException") + @Override + public Message nextMessage(Duration timeout) throws InterruptedException, IllegalStateException { + return wrapNextMessage(() -> delegate.nextMessage(timeout)); + } + + @SuppressWarnings({"PreferJavaTimeOverload", "ThrowsUncheckedException"}) + @Override + public Message nextMessage(long timeoutMillis) + throws InterruptedException, IllegalStateException { + return wrapNextMessage(() -> delegate.nextMessage(timeoutMillis)); + } + + @Override + public void unsubscribe() { + delegate.unsubscribe(); + } + + @Override + public Subscription unsubscribe(int after) { + return delegate.unsubscribe(after); + } + + @Override + public void setPendingLimits(long maxMessages, long maxBytes) { + delegate.setPendingLimits(maxMessages, maxBytes); + } + + @Override + public long getPendingMessageLimit() { + return delegate.getPendingMessageLimit(); + } + + @Override + public long getPendingByteLimit() { + return delegate.getPendingByteLimit(); + } + + @Override + public long getPendingMessageCount() { + return delegate.getPendingMessageCount(); + } + + @Override + public long getPendingByteCount() { + return delegate.getPendingByteCount(); + } + + @Override + public long getDeliveredCount() { + return delegate.getDeliveredCount(); + } + + @Override + public long getDroppedCount() { + return delegate.getDroppedCount(); + } + + @Override + public void clearDroppedCount() { + delegate.clearDroppedCount(); + } + + @Override + public boolean isActive() { + return delegate.isActive(); + } + + @Override + public CompletableFuture drain(Duration timeout) throws InterruptedException { + return delegate.drain(timeout); + } + + private Message wrapNextMessage( + ThrowingSupplier nextMessage) + throws InterruptedException { + Timer timer = Timer.start(); + Message message = nextMessage.call(); + + Context parentContext = Context.current(); + TimeoutException timeout = null; + NatsRequest natsRequest = NatsRequest.create(this.connection, this.getSubject()); + + if (message == null) { + timeout = new TimeoutException("Timed out waiting for message"); + } else { + natsRequest = NatsRequest.create(this.connection, message); + } + + if (!Span.fromContext(parentContext).getSpanContext().isValid() + || !consumerInstrumenter.shouldStart(parentContext, natsRequest)) { + return message; + } + + InstrumenterUtil.startAndEnd( + consumerInstrumenter, + parentContext, + natsRequest, + null, + timeout, + timer.startTime(), + timer.now()); + + return message; + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java index 00b4ab1ec54b..ed9d0ec3ea48 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java @@ -12,6 +12,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -24,18 +25,38 @@ public final class NatsInstrumenterFactory { public static final SpanNameExtractor PRODUCER_SPAN_NAME_EXTRACTOR = MessagingSpanNameExtractor.create( - MessageMessagingAttributesGetter.INSTANCE, MessageOperation.PUBLISH); + NatsRequestMessagingAttributesGetter.INSTANCE, MessageOperation.PUBLISH); - public static final AttributesExtractor PUBLISH_ATTRIBUTES_EXTRACTOR = + public static final AttributesExtractor PRODUCER_ATTRIBUTES_EXTRACTOR = MessagingAttributesExtractor.create( - MessageMessagingAttributesGetter.INSTANCE, MessageOperation.PUBLISH); + NatsRequestMessagingAttributesGetter.INSTANCE, MessageOperation.PUBLISH); + + public static final SpanNameExtractor CONSUMER_SPAN_NAME_EXTRACTOR = + MessagingSpanNameExtractor.create( + NatsRequestMessagingAttributesGetter.INSTANCE, MessageOperation.RECEIVE); + + public static final AttributesExtractor CONSUMER_ATTRIBUTES_EXTRACTOR = + MessagingAttributesExtractor.create( + NatsRequestMessagingAttributesGetter.INSTANCE, MessageOperation.RECEIVE); public static Instrumenter createProducerInstrumenter( OpenTelemetry openTelemetry) { return Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, PRODUCER_SPAN_NAME_EXTRACTOR) - .addAttributesExtractor(PUBLISH_ATTRIBUTES_EXTRACTOR) - .buildProducerInstrumenter(MessageTextMapSetter.INSTANCE); + .addAttributesExtractor(PRODUCER_ATTRIBUTES_EXTRACTOR) + .buildProducerInstrumenter(NatsRequestTextMapSetter.INSTANCE); + } + + public static Instrumenter createConsumerInstrumenter( + OpenTelemetry openTelemetry) { + return Instrumenter.builder( + openTelemetry, INSTRUMENTATION_NAME, CONSUMER_SPAN_NAME_EXTRACTOR) + .addAttributesExtractor(CONSUMER_ATTRIBUTES_EXTRACTOR) + .addSpanLinksExtractor( + new PropagatorBasedSpanLinksExtractor<>( + openTelemetry.getPropagators().getTextMapPropagator(), + NatsRequestTextMapGetter.INSTANCE)) + .buildConsumerInstrumenter(NatsRequestTextMapGetter.INSTANCE); } private NatsInstrumenterFactory() {} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java index 7cf2acd050ed..1eed737cf97d 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java @@ -20,24 +20,33 @@ @AutoValue public abstract class NatsRequest { + public static NatsRequest create(Connection connection, String subject) { + return create(connection, subject, null); + } + public static NatsRequest create(Connection connection, String subject, byte[] data) { - return new AutoValue_NatsRequest(connection, subject, null, getDataSize(data)); + return create(connection, subject, null, data); } - public static NatsRequest create( - Connection connection, String subject, Headers headers, byte[] data) { - return new AutoValue_NatsRequest(connection, subject, headers, getDataSize(data)); + public static NatsRequest create(Message message) { + return create(message.getConnection(), message); } public static NatsRequest create(Connection connection, Message message) { - return new AutoValue_NatsRequest( + return create( message.getConnection() == null ? connection : message.getConnection(), message.getSubject(), message.getHeaders(), - getDataSize(message.getData())); + message.getData()); + } + + public static NatsRequest create( + Connection connection, String subject, Headers headers, byte[] data) { + return new AutoValue_NatsRequest( + connection.getServerInfo().getClientId(), subject, headers, getDataSize(data)); } - public abstract Connection getConnection(); + public abstract int getClientId(); public abstract String getSubject(); diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageMessagingAttributesGetter.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetter.java similarity index 90% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageMessagingAttributesGetter.java rename to instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetter.java index 4cbea9008313..71d2d0700e60 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageMessagingAttributesGetter.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetter.java @@ -11,7 +11,7 @@ import java.util.List; import javax.annotation.Nullable; -enum MessageMessagingAttributesGetter implements MessagingAttributesGetter { +enum NatsRequestMessagingAttributesGetter implements MessagingAttributesGetter { INSTANCE; @Nullable @@ -69,7 +69,7 @@ public String getMessageId(NatsRequest request, @Nullable Void unused) { @Nullable @Override public String getClientId(NatsRequest request) { - return String.valueOf(request.getConnection().getServerInfo().getClientId()); + return String.valueOf(request.getClientId()); } @Nullable diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapGetter.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapGetter.java new file mode 100644 index 000000000000..ada95ecf0a38 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapGetter.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21.internal; + +import io.nats.client.impl.Headers; +import io.opentelemetry.context.propagation.TextMapGetter; +import java.util.Collections; +import javax.annotation.Nullable; + +enum NatsRequestTextMapGetter implements TextMapGetter { + INSTANCE; + + @Override + public Iterable keys(NatsRequest request) { + Headers headers = request.getHeaders(); + + if (headers == null) { + return Collections.emptyList(); + } + + return headers.keySet(); + } + + @Nullable + @Override + public String get(@Nullable NatsRequest request, String key) { + if (request == null || request.getHeaders() == null) { + return null; + } + + return request.getHeaders().getFirst(key); + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageTextMapSetter.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapSetter.java similarity index 89% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageTextMapSetter.java rename to instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapSetter.java index 247b190dabe1..7d7ab7dad183 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/MessageTextMapSetter.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapSetter.java @@ -8,7 +8,7 @@ import io.opentelemetry.context.propagation.TextMapSetter; import javax.annotation.Nullable; -enum MessageTextMapSetter implements TextMapSetter { +enum NatsRequestTextMapSetter implements TextMapSetter { INSTANCE; @Override diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier.java new file mode 100644 index 000000000000..62cb2bbc0e2f --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21.internal; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@FunctionalInterface +public interface ThrowingSupplier { + + T call() throws E, E2; +} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java index 478a49e1953f..89e0d70c6f3a 100644 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java @@ -137,13 +137,13 @@ private static void assertPublishSpan() { } private static void assertNoHeaders(TestConnection connection) { - Message published = connection.publishedMessages.peekLast(); + Message published = connection.publishedMessages.remove(); assertNotNull(published); assertThat(published.getHeaders()).isNull(); } private static void assertTraceparentHeader(TestConnection connection) { - Message published = connection.publishedMessages.peekLast(); + Message published = connection.publishedMessages.remove(); assertNotNull(published); assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); } diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscribeTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscribeTest.java new file mode 100644 index 000000000000..ff7ede2b4225 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscribeTest.java @@ -0,0 +1,131 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; +import static org.assertj.core.api.Assertions.assertThat; + +import io.nats.client.Subscription; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.time.Duration; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +@SuppressWarnings("deprecation") // using deprecated semconv +class NatsTelemetrySubscribeTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + static final NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); + + @Test + void testSubscribeTimeout() throws InterruptedException { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + Subscription subscription = connection.subscribe("sub"); + + // when + testing.runWithSpan("parent", () -> subscription.nextMessage(Duration.ofSeconds(1))); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasException(new TimeoutException("Timed out waiting for message")) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 0), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + } + + @Test + void testSubscribeNoLink() throws InterruptedException { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + Subscription subscription = connection.subscribe("sub"); + + // when + testConnection.deliver(NatsMessage.builder().subject("sub").data("x").build()); + testing.runWithSpan("parent", () -> subscription.nextMessage(Duration.ofSeconds(1))); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + } + + @SuppressWarnings("PreferJavaTimeOverload") + @Test + void testSubscribeWithLink() throws InterruptedException { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + Subscription subscription = connection.subscribe("sub"); + String linkTraceId = "0af7651916cd43dd8448eb211c80319c"; + Headers headers = + new Headers( + new Headers().put("traceparent", "00-" + linkTraceId + "-b7ad6b7169203331-01"), true); + + // when + testConnection.deliver(NatsMessage.builder().subject("sub").data("x").headers(headers).build()); + testing.runWithSpan("parent", () -> subscription.nextMessage(1000)); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasLinksSatisfying( + links -> + assertThat(links) + .singleElement() + .satisfies( + link -> + assertThat(link.getSpanContext().getTraceId()) + .isEqualTo(linkTraceId))) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + } +} diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java index 858138518c9f..597b7a5f7b15 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java @@ -35,12 +35,26 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedList; +import java.util.List; +import java.util.Queue; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeoutException; public class TestConnection implements Connection { - public final LinkedList publishedMessages = new LinkedList<>(); + private final List subscriptions = + Collections.synchronizedList(new LinkedList<>()); + + public final Queue publishedMessages = new ConcurrentLinkedQueue<>(); + + public void deliver(Message message) { + subscriptions.stream() + .filter(subscription -> message.getSubject().equalsIgnoreCase(subscription.getSubject())) + .forEach( + subscription -> + subscription.messages.add(new TestMessage(this, subscription, message))); + } @Override public void publish(String subject, byte[] body) { @@ -124,12 +138,16 @@ public CompletableFuture requestWithTimeout(Message message, Duration t @Override public Subscription subscribe(String subject) { - return null; + TestSubscription subscription = new TestSubscription(subject); + subscriptions.add(subscription); + return subscription; } @Override public Subscription subscribe(String subject, String queueName) { - return null; + TestSubscription subscription = new TestSubscription(subject, queueName); + subscriptions.add(subscription); + return subscription; } @Override diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestMessage.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestMessage.java new file mode 100644 index 000000000000..1c28bb17a119 --- /dev/null +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestMessage.java @@ -0,0 +1,135 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.nats.client.Message; +import io.nats.client.Subscription; +import io.nats.client.impl.AckType; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsJetStreamMetaData; +import io.nats.client.support.Status; +import java.time.Duration; +import java.util.concurrent.TimeoutException; + +public class TestMessage implements Message { + + private final Connection connection; + private final Subscription subscription; + private final Message message; + + public TestMessage(Connection connection, Subscription subscription, Message message) { + this.connection = connection; + this.subscription = subscription; + this.message = message; + } + + @Override + public String getSubject() { + return message.getSubject(); + } + + @Override + public String getReplyTo() { + return message.getReplyTo(); + } + + @Override + public boolean hasHeaders() { + return message.hasHeaders(); + } + + @Override + public Headers getHeaders() { + return message.getHeaders(); + } + + @Override + public boolean isStatusMessage() { + return message.isStatusMessage(); + } + + @Override + public Status getStatus() { + return message.getStatus(); + } + + @Override + public byte[] getData() { + return message.getData(); + } + + @Override + public boolean isUtf8mode() { + return message.isUtf8mode(); + } + + @Override + public Subscription getSubscription() { + return subscription; + } + + @Override + public String getSID() { + return subscription.toString(); + } + + @Override + public Connection getConnection() { + return connection; + } + + @Override + public NatsJetStreamMetaData metaData() { + return message.metaData(); + } + + @Override + public AckType lastAck() { + return message.lastAck(); + } + + @Override + public void ack() { + message.ack(); + } + + @Override + public void ackSync(Duration timeout) throws TimeoutException, InterruptedException { + message.ackSync(timeout); + } + + @Override + public void nak() { + message.nak(); + } + + @Override + public void nakWithDelay(Duration nakDelay) { + message.nakWithDelay(nakDelay); + } + + @SuppressWarnings("PreferJavaTimeOverload") + @Override + public void nakWithDelay(long nakDelayMillis) { + message.nakWithDelay(nakDelayMillis); + } + + @Override + public void term() { + message.term(); + } + + @Override + public void inProgress() { + message.inProgress(); + } + + @Override + public boolean isJetStream() { + return message.isJetStream(); + } +} diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java new file mode 100644 index 000000000000..154b62a50011 --- /dev/null +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java @@ -0,0 +1,132 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Dispatcher; +import io.nats.client.Message; +import io.nats.client.Subscription; +import java.time.Duration; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class TestSubscription implements Subscription { + private final String subject; + private final String queueName; + private final Dispatcher dispatcher; + + public final Queue messages = new ConcurrentLinkedQueue<>(); + + public TestSubscription(String subject) { + this.subject = subject; + this.queueName = null; + this.dispatcher = null; + } + + public TestSubscription(String subject, String queueName) { + this.subject = subject; + this.queueName = queueName; + this.dispatcher = null; + } + + public TestSubscription(String subject, String queueName, Dispatcher dispatcher) { + this.subject = subject; + this.queueName = queueName; + this.dispatcher = dispatcher; + } + + @Override + public String getSubject() { + return subject; + } + + @Override + public String getQueueName() { + return queueName; + } + + @Override + public Dispatcher getDispatcher() { + return dispatcher; + } + + @SuppressWarnings("ThrowsUncheckedException") + @Override + public Message nextMessage(Duration timeout) throws InterruptedException, IllegalStateException { + if (dispatcher != null) { + throw new IllegalStateException( + "Subscriptions that belong to a dispatcher cannot respond to nextMessage directly."); + } + + return messages.poll(); + } + + @SuppressWarnings("ThrowsUncheckedException") + @Override + public Message nextMessage(long timeoutMillis) + throws InterruptedException, IllegalStateException { + if (dispatcher != null) { + throw new IllegalStateException( + "Subscriptions that belong to a dispatcher cannot respond to nextMessage directly."); + } + + return messages.poll(); + } + + @Override + public void unsubscribe() {} + + @Override + public Subscription unsubscribe(int after) { + return null; + } + + @Override + public void setPendingLimits(long maxMessages, long maxBytes) {} + + @Override + public long getPendingMessageLimit() { + return 0; + } + + @Override + public long getPendingByteLimit() { + return 0; + } + + @Override + public long getPendingMessageCount() { + return 0; + } + + @Override + public long getPendingByteCount() { + return 0; + } + + @Override + public long getDeliveredCount() { + return 0; + } + + @Override + public long getDroppedCount() { + return 0; + } + + @Override + public void clearDroppedCount() {} + + @Override + public boolean isActive() { + return false; + } + + @Override + public CompletableFuture drain(Duration timeout) throws InterruptedException { + return null; + } +} From 56e36566ea71b0069e2c0069214d924f7bb2e9f5 Mon Sep 17 00:00:00 2001 From: Alix Date: Mon, 5 May 2025 10:09:56 +0200 Subject: [PATCH 05/34] Instrument NATS library 'request' method --- .../nats/v2_21/NatsTelemetry.java | 7 +- .../nats/v2_21/NatsTelemetryBuilder.java | 3 +- .../nats/v2_21/OpenTelemetryConnection.java | 91 +++++- .../nats/v2_21/OpenTelemetrySubscription.java | 4 +- .../internal/NatsInstrumenterFactory.java | 24 +- .../nats/v2_21/internal/NatsRequest.java | 4 - .../NatsRequestMessagingAttributesGetter.java | 79 +---- ...questMessagingAttributesGetterFactory.java | 91 ++++++ .../nats/v2_21/internal/ThrowingSupplier.java | 4 +- .../v2_21/internal/ThrowingSupplier2.java | 16 + .../nats/v2_21/NatsTelemetryRequestTest.java | 273 ++++++++++++++++++ .../nats/v2_21/TestConnection.java | 56 +++- 12 files changed, 538 insertions(+), 114 deletions(-) create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetterFactory.java create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier2.java create mode 100644 instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryRequestTest.java diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java index 3d38d476d557..6ac5fc084342 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java @@ -22,16 +22,19 @@ public static NatsTelemetryBuilder builder(OpenTelemetry openTelemetry) { private final Instrumenter producerInstrumenter; private final Instrumenter consumerInstrumenter; + private final Instrumenter clientInstrumenter; public NatsTelemetry( Instrumenter producerInstrumenter, - Instrumenter consumerInstrumenter) { + Instrumenter consumerInstrumenter, + Instrumenter clientInstrumenter) { this.producerInstrumenter = producerInstrumenter; this.consumerInstrumenter = consumerInstrumenter; + this.clientInstrumenter = clientInstrumenter; } public OpenTelemetryConnection wrap(Connection connection) { return new OpenTelemetryConnection( - connection, this.producerInstrumenter, this.consumerInstrumenter); + connection, this.producerInstrumenter, this.consumerInstrumenter, this.clientInstrumenter); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java index 59da60c37180..dd9adb6ea1b4 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java @@ -19,6 +19,7 @@ public final class NatsTelemetryBuilder { public NatsTelemetry build() { return new NatsTelemetry( NatsInstrumenterFactory.createProducerInstrumenter(openTelemetry), - NatsInstrumenterFactory.createConsumerInstrumenter(openTelemetry)); + NatsInstrumenterFactory.createConsumerInstrumenter(openTelemetry), + NatsInstrumenterFactory.createClientInstrumenter(openTelemetry)); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java index 24e547c42f8b..5041fb3ab1a4 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java @@ -33,26 +33,31 @@ import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_21.internal.ThrowingSupplier; import java.io.IOException; import java.net.InetAddress; import java.time.Duration; import java.util.Collection; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; +import java.util.function.Supplier; public class OpenTelemetryConnection implements Connection { private final Connection delegate; private final Instrumenter producerInstrumenter; private final Instrumenter consumerInstrumenter; + private final Instrumenter clientInstrumenter; public OpenTelemetryConnection( Connection connection, Instrumenter producerInstrumenter, - Instrumenter consumerInstrumenter) { + Instrumenter consumerInstrumenter, + Instrumenter clientInstrumenter) { this.delegate = connection; this.producerInstrumenter = producerInstrumenter; this.consumerInstrumenter = consumerInstrumenter; + this.clientInstrumenter = clientInstrumenter; } @Override @@ -87,51 +92,62 @@ public void publish(Message message) { @Override public CompletableFuture request(String subject, byte[] body) { - return delegate.request(subject, body); + return wrapRequest( + NatsRequest.create(this, subject, body), () -> delegate.request(subject, body)); } @Override public Message request(String subject, byte[] body, Duration timeout) throws InterruptedException { - return delegate.request(subject, body, timeout); + return wrapRequest( + NatsRequest.create(this, subject, body), () -> delegate.request(subject, body, timeout)); } @Override public CompletableFuture request(String subject, Headers headers, byte[] body) { - return delegate.request(subject, headers, body); + return wrapRequest( + NatsRequest.create(this, subject, headers, body), + () -> delegate.request(subject, headers, body)); } @Override public Message request(String subject, Headers headers, byte[] body, Duration timeout) throws InterruptedException { - return delegate.request(subject, headers, body, timeout); + return wrapRequest( + NatsRequest.create(this, subject, headers, body), + () -> delegate.request(subject, headers, body, timeout)); } @Override public CompletableFuture request(Message message) { - return delegate.request(message); + return wrapRequest(NatsRequest.create(this, message), () -> delegate.request(message)); } @Override public Message request(Message message, Duration timeout) throws InterruptedException { - return delegate.request(message, timeout); + return wrapRequest(NatsRequest.create(this, message), () -> delegate.request(message, timeout)); } @Override public CompletableFuture requestWithTimeout( String subject, byte[] body, Duration timeout) { - return delegate.requestWithTimeout(subject, body, timeout); + return wrapRequest( + NatsRequest.create(this, subject, body), + () -> delegate.requestWithTimeout(subject, body, timeout)); } @Override public CompletableFuture requestWithTimeout( String subject, Headers headers, byte[] body, Duration timeout) { - return delegate.requestWithTimeout(subject, headers, body, timeout); + return wrapRequest( + NatsRequest.create(this, subject, headers, body), + () -> delegate.requestWithTimeout(subject, headers, body, timeout)); } @Override public CompletableFuture requestWithTimeout(Message message, Duration timeout) { - return delegate.requestWithTimeout(message, timeout); + return wrapRequest( + NatsRequest.create(this, message), () -> delegate.requestWithTimeout(message, timeout)); } @Override @@ -367,4 +383,59 @@ private void wrapPublish(NatsRequest natsRequest, Runnable publish) { producerInstrumenter.end(context, natsRequest, null, null); } } + + private Message wrapRequest( + NatsRequest natsRequest, ThrowingSupplier request) + throws InterruptedException { + Context parentContext = Context.current(); + + if (!Span.fromContext(parentContext).getSpanContext().isValid() + || !clientInstrumenter.shouldStart(parentContext, natsRequest)) { + return request.call(); + } + + Context context = clientInstrumenter.start(parentContext, natsRequest); + TimeoutException timeout = null; + NatsRequest response = null; + + try (Scope ignored = context.makeCurrent()) { + Message message = request.call(); + + if (message == null) { + timeout = new TimeoutException("Timed out waiting for message"); + } else { + response = NatsRequest.create(this, message); + } + + return message; + } finally { + clientInstrumenter.end(context, natsRequest, response, timeout); + } + } + + private CompletableFuture wrapRequest( + NatsRequest natsRequest, Supplier> request) { + Context parentContext = Context.current(); + + if (!Span.fromContext(parentContext).getSpanContext().isValid() + || !clientInstrumenter.shouldStart(parentContext, natsRequest)) { + return request.get(); + } + + Context context = clientInstrumenter.start(parentContext, natsRequest); + + try (Scope ignored = context.makeCurrent()) { + return request + .get() + .whenComplete( + (message, exception) -> { + if (message != null) { + NatsRequest response = NatsRequest.create(this, message); + clientInstrumenter.end(context, natsRequest, response, exception); + } else { + clientInstrumenter.end(context, natsRequest, null, exception); + } + }); + } + } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java index e00cf1c9fc5b..b9c2d7ad0ca3 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java @@ -15,7 +15,7 @@ import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; import io.opentelemetry.instrumentation.api.internal.Timer; import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; -import io.opentelemetry.instrumentation.nats.v2_21.internal.ThrowingSupplier; +import io.opentelemetry.instrumentation.nats.v2_21.internal.ThrowingSupplier2; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; @@ -124,7 +124,7 @@ public CompletableFuture drain(Duration timeout) throws InterruptedExce } private Message wrapNextMessage( - ThrowingSupplier nextMessage) + ThrowingSupplier2 nextMessage) throws InterruptedException { Timer timer = Timer.start(); Message message = nextMessage.call(); diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java index ed9d0ec3ea48..5910634a6488 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java @@ -25,19 +25,23 @@ public final class NatsInstrumenterFactory { public static final SpanNameExtractor PRODUCER_SPAN_NAME_EXTRACTOR = MessagingSpanNameExtractor.create( - NatsRequestMessagingAttributesGetter.INSTANCE, MessageOperation.PUBLISH); + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PUBLISH); public static final AttributesExtractor PRODUCER_ATTRIBUTES_EXTRACTOR = MessagingAttributesExtractor.create( - NatsRequestMessagingAttributesGetter.INSTANCE, MessageOperation.PUBLISH); + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PUBLISH); public static final SpanNameExtractor CONSUMER_SPAN_NAME_EXTRACTOR = MessagingSpanNameExtractor.create( - NatsRequestMessagingAttributesGetter.INSTANCE, MessageOperation.RECEIVE); + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE); public static final AttributesExtractor CONSUMER_ATTRIBUTES_EXTRACTOR = MessagingAttributesExtractor.create( - NatsRequestMessagingAttributesGetter.INSTANCE, MessageOperation.RECEIVE); + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE); + + public static final AttributesExtractor CLIENT_ATTRIBUTES_EXTRACTOR = + MessagingAttributesExtractor.create( + NatsRequestMessagingAttributesGetter.NATS_REQUEST_INSTANCE, MessageOperation.PUBLISH); public static Instrumenter createProducerInstrumenter( OpenTelemetry openTelemetry) { @@ -59,5 +63,17 @@ public static Instrumenter createConsumerInstrumenter( .buildConsumerInstrumenter(NatsRequestTextMapGetter.INSTANCE); } + public static Instrumenter createClientInstrumenter( + OpenTelemetry openTelemetry) { + return Instrumenter.builder( + openTelemetry, INSTRUMENTATION_NAME, PRODUCER_SPAN_NAME_EXTRACTOR) + .addAttributesExtractor(CLIENT_ATTRIBUTES_EXTRACTOR) + .addSpanLinksExtractor( + new PropagatorBasedSpanLinksExtractor<>( + openTelemetry.getPropagators().getTextMapPropagator(), + NatsRequestTextMapGetter.INSTANCE)) + .buildClientInstrumenter(NatsRequestTextMapSetter.INSTANCE); + } + private NatsInstrumenterFactory() {} } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java index 1eed737cf97d..217b9ec327c6 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java @@ -28,10 +28,6 @@ public static NatsRequest create(Connection connection, String subject, byte[] d return create(connection, subject, null, data); } - public static NatsRequest create(Message message) { - return create(message.getConnection(), message); - } - public static NatsRequest create(Connection connection, Message message) { return create( message.getConnection() == null ? connection : message.getConnection(), diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetter.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetter.java index 71d2d0700e60..51344dcc06fb 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetter.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetter.java @@ -5,82 +5,15 @@ package io.opentelemetry.instrumentation.nats.v2_21.internal; -import io.nats.client.impl.Headers; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; -import java.util.Collections; -import java.util.List; -import javax.annotation.Nullable; -enum NatsRequestMessagingAttributesGetter implements MessagingAttributesGetter { - INSTANCE; +class NatsRequestMessagingAttributesGetter { - @Nullable - @Override - public String getSystem(NatsRequest request) { - return "nats"; - } + static final MessagingAttributesGetter VOID_INSTANCE = + NatsRequestMessagingAttributesGetterFactory.create(); - @Nullable - @Override - public String getDestination(NatsRequest request) { - return request.getSubject(); - } + static final MessagingAttributesGetter NATS_REQUEST_INSTANCE = + NatsRequestMessagingAttributesGetterFactory.create(); - @Nullable - @Override - public String getDestinationTemplate(NatsRequest request) { - return null; - } - - @Override - public boolean isTemporaryDestination(NatsRequest request) { - return false; - } - - @Override - public boolean isAnonymousDestination(NatsRequest request) { - return false; - } - - @Nullable - @Override - public String getConversationId(NatsRequest request) { - return null; - } - - @Nullable - @Override - public Long getMessageBodySize(NatsRequest request) { - return request.getDataSize(); - } - - @Nullable - @Override - public Long getMessageEnvelopeSize(NatsRequest request) { - return null; - } - - @Nullable - @Override - public String getMessageId(NatsRequest request, @Nullable Void unused) { - return null; - } - - @Nullable - @Override - public String getClientId(NatsRequest request) { - return String.valueOf(request.getClientId()); - } - - @Nullable - @Override - public Long getBatchMessageCount(NatsRequest request, @Nullable Void unused) { - return null; - } - - @Override - public List getMessageHeader(NatsRequest request, String name) { - Headers headers = request.getHeaders(); - return headers == null ? Collections.emptyList() : headers.get(name); - } + private NatsRequestMessagingAttributesGetter() {} } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetterFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetterFactory.java new file mode 100644 index 000000000000..0ed9530989f4 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetterFactory.java @@ -0,0 +1,91 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21.internal; + +import io.nats.client.impl.Headers; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +class NatsRequestMessagingAttributesGetterFactory { + + static MessagingAttributesGetter create() { + return new MessagingAttributesGetter() { + @Nullable + @Override + public String getSystem(NatsRequest request) { + return "nats"; + } + + @Nullable + @Override + public String getDestination(NatsRequest request) { + return request.getSubject(); + } + + @Nullable + @Override + public String getDestinationTemplate(NatsRequest request) { + return null; + } + + @Override + public boolean isTemporaryDestination(NatsRequest request) { + return false; + } + + @Override + public boolean isAnonymousDestination(NatsRequest request) { + return false; + } + + @Nullable + @Override + public String getConversationId(NatsRequest request) { + return null; + } + + @Nullable + @Override + public Long getMessageBodySize(NatsRequest request) { + return request.getDataSize(); + } + + @Nullable + @Override + public Long getMessageEnvelopeSize(NatsRequest request) { + return null; + } + + @Nullable + @Override + public String getMessageId(NatsRequest request, @Nullable RESPONSE unused) { + return null; + } + + @Nullable + @Override + public String getClientId(NatsRequest request) { + return String.valueOf(request.getClientId()); + } + + @Nullable + @Override + public Long getBatchMessageCount(NatsRequest request, @Nullable RESPONSE unused) { + return null; + } + + @Override + public List getMessageHeader(NatsRequest request, String name) { + Headers headers = request.getHeaders(); + return headers == null ? Collections.emptyList() : headers.get(name); + } + }; + } + + private NatsRequestMessagingAttributesGetterFactory() {} +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier.java index 62cb2bbc0e2f..3cfdd0e1ef71 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier.java @@ -10,7 +10,7 @@ * any time. */ @FunctionalInterface -public interface ThrowingSupplier { +public interface ThrowingSupplier { - T call() throws E, E2; + T call() throws E; } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier2.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier2.java new file mode 100644 index 000000000000..b07185904d65 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier2.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21.internal; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@FunctionalInterface +public interface ThrowingSupplier2 { + + T call() throws E, E2; +} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryRequestTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryRequestTest.java new file mode 100644 index 000000000000..301f18f37fe0 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryRequestTest.java @@ -0,0 +1,273 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.nats.client.Message; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.time.Duration; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +@SuppressWarnings("deprecation") // using deprecated semconv +class NatsTelemetryRequestTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + static final NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); + + @Test + void testRequestTimeout() throws InterruptedException { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + + // when + testing.runWithSpan( + "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); + + // then + assertPublishSpan(); + assertTimeoutPublishSpan(); + assertNoHeaders(testConnection); + } + + @Test + void testRequestFutureTimeout() { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + + // when + testing.runWithSpan( + "parent", + () -> connection.requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); + + // then + assertPublishSpan(); + assertTimeoutPublishSpan(); + assertNoHeaders(testConnection); + } + + @Test + void testRequestBodyNoHeaders() throws InterruptedException { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + + // when + testing.runWithSpan( + "parent", + () -> { + testConnection.requestResponseMessages.offer( + NatsMessage.builder().subject("sub").build()); + connection.request("sub", new byte[] {0}, Duration.ofSeconds(1)); + }); + + // then + assertPublishSpan(); + assertNoHeaders(testConnection); + } + + @Test + void testRequestFutureBodyNoHeaders() { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + + // when + testing.runWithSpan( + "parent", + () -> { + testConnection.requestResponseMessages.offer( + NatsMessage.builder().subject("sub").build()); + connection.request("sub", new byte[] {0}); + }); + + // then + assertPublishSpan(); + assertNoHeaders(testConnection); + } + + @Test + void testRequestBodyWithHeaders() throws InterruptedException { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + + // when + testing.runWithSpan( + "parent", + () -> { + testConnection.requestResponseMessages.offer( + NatsMessage.builder().subject("sub").build()); + connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1)); + }); + + // then + assertPublishSpan(); + assertTraceparentHeader(testConnection); + } + + @Test + void testRequestFutureBodyWithHeaders() { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + + // when + testing.runWithSpan( + "parent", + () -> { + testConnection.requestResponseMessages.offer( + NatsMessage.builder().subject("sub").build()); + connection.request("sub", new Headers(), new byte[] {0}); + }); + + // then + assertPublishSpan(); + assertTraceparentHeader(testConnection); + } + + @Test + void testRequestMessageNoHeaders() throws InterruptedException { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + + // when + testing.runWithSpan( + "parent", + () -> { + testConnection.requestResponseMessages.offer( + NatsMessage.builder().subject("sub").build()); + connection.request(message, Duration.ofSeconds(1)); + }); + + // then + assertPublishSpan(); + assertNoHeaders(testConnection); + } + + @Test + void testRequestFutureMessageNoHeaders() { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + + // when + testing.runWithSpan( + "parent", + () -> { + testConnection.requestResponseMessages.offer( + NatsMessage.builder().subject("sub").build()); + connection.request(message); + }); + + // then + assertPublishSpan(); + assertNoHeaders(testConnection); + } + + @Test + void testRequestMessageWithHeaders() throws InterruptedException { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + NatsMessage message = + NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + + // when + testing.runWithSpan( + "parent", + () -> { + testConnection.requestResponseMessages.offer( + NatsMessage.builder().subject("sub").build()); + connection.request(message, Duration.ofSeconds(1)); + }); + + // then + assertPublishSpan(); + assertTraceparentHeader(testConnection); + } + + @Test + void testRequestFutureMessageWithHeaders() { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + NatsMessage message = + NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + + // when + testing.runWithSpan( + "parent", + () -> { + testConnection.requestResponseMessages.offer( + NatsMessage.builder().subject("sub").build()); + connection.request(message); + }); + + // then + assertPublishSpan(); + assertTraceparentHeader(testConnection); + } + + private static void assertPublishSpan() { + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "publish"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + } + + private static void assertTimeoutPublishSpan() { + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent"), + span -> + span.hasName("sub publish") + .hasException(new TimeoutException("Timed out waiting for message")))); + } + + private static void assertNoHeaders(TestConnection connection) { + Message published = connection.requestedMessages.remove(); + assertNotNull(published); + assertThat(published.getHeaders()).isNull(); + } + + private static void assertTraceparentHeader(TestConnection connection) { + Message published = connection.requestedMessages.remove(); + assertNotNull(published); + assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); + } +} diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java index 597b7a5f7b15..d6fab7593526 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java @@ -47,6 +47,8 @@ public class TestConnection implements Connection { Collections.synchronizedList(new LinkedList<>()); public final Queue publishedMessages = new ConcurrentLinkedQueue<>(); + public final Queue requestedMessages = new ConcurrentLinkedQueue<>(); + public final Queue requestResponseMessages = new ConcurrentLinkedQueue<>(); public void deliver(Message message) { subscriptions.stream() @@ -88,52 +90,74 @@ public void publish(Message message) { } @Override - public CompletableFuture request(String subject, byte[] body) { - return null; + public Message request(String subject, byte[] body, Duration timeout) + throws InterruptedException { + return this.request(NatsMessage.builder().subject(subject).data(body).build(), timeout); } @Override - public Message request(String subject, byte[] body, Duration timeout) + public Message request(String subject, Headers headers, byte[] body, Duration timeout) throws InterruptedException { - return null; + return this.request( + NatsMessage.builder().subject(subject).headers(headers).data(body).build(), timeout); } @Override - public CompletableFuture request(String subject, Headers headers, byte[] body) { - return null; + public Message request(Message message, Duration timeout) throws InterruptedException { + this.requestedMessages.add(message); + return requestResponseMessages.peek(); } @Override - public Message request(String subject, Headers headers, byte[] body, Duration timeout) - throws InterruptedException { - return null; + public CompletableFuture request(String subject, byte[] body) { + return this.request(NatsMessage.builder().subject(subject).data(body).build()); } @Override - public CompletableFuture request(Message message) { - return null; + public CompletableFuture request(String subject, Headers headers, byte[] body) { + return this.request(NatsMessage.builder().subject(subject).headers(headers).data(body).build()); } @Override - public Message request(Message message, Duration timeout) throws InterruptedException { - return null; + public CompletableFuture request(Message message) { + this.requestedMessages.add(message); + Message response = requestResponseMessages.peek(); + + if (response != null) { + return CompletableFuture.completedFuture(message); + } + + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(new TimeoutException("Timed out waiting for message")); + return future; } @Override public CompletableFuture requestWithTimeout( String subject, byte[] body, Duration timeout) { - return null; + return this.requestWithTimeout( + NatsMessage.builder().subject(subject).data(body).build(), timeout); } @Override public CompletableFuture requestWithTimeout( String subject, Headers headers, byte[] body, Duration timeout) { - return null; + return this.requestWithTimeout( + NatsMessage.builder().subject(subject).headers(headers).data(body).build(), timeout); } @Override public CompletableFuture requestWithTimeout(Message message, Duration timeout) { - return null; + this.requestedMessages.add(message); + Message response = requestResponseMessages.peek(); + + if (response != null) { + return CompletableFuture.completedFuture(message); + } + + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(new TimeoutException("Timed out waiting for message")); + return future; } @Override From 2bb53a2ae55b308a59477077052a72ee870008a8 Mon Sep 17 00:00:00 2001 From: Alix Date: Thu, 29 May 2025 19:32:58 +0200 Subject: [PATCH 06/34] Instrument NATS agent 'request' method --- .../nats/v2_21/CompletableFutureWrapper.java | 31 + .../ConnectionRequestInstrumentation.java | 543 ++++++++++++++++++ .../nats/v2_21/NatsInstrumentationModule.java | 1 + .../nats/v2_21/NatsSingletons.java | 4 + .../nats/v2_21/internal/MessageConsumer.java | 55 ++ .../v2_21/NatsInstrumentationRequestTest.java | 341 +++++++++++ .../nats/v2_21/OpenTelemetryConnection.java | 30 +- 7 files changed, 990 insertions(+), 15 deletions(-) create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/CompletableFutureWrapper.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/CompletableFutureWrapper.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/CompletableFutureWrapper.java new file mode 100644 index 000000000000..fe42c0576fed --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/CompletableFutureWrapper.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import java.util.concurrent.CompletableFuture; + +public final class CompletableFutureWrapper { + + private CompletableFutureWrapper() {} + + public static CompletableFuture wrap(CompletableFuture future, Context context) { + CompletableFuture result = new CompletableFuture<>(); + future.whenComplete( + (T value, Throwable throwable) -> { + try (Scope ignored = context.makeCurrent()) { + if (throwable != null) { + result.completeExceptionally(throwable); + } else { + result.complete(value); + } + } + }); + + return result; + } +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java new file mode 100644 index 000000000000..8ae59792961b --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java @@ -0,0 +1,543 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.CLIENT_INSTRUMENTER; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.nats.client.Connection; +import io.nats.client.Message; +import io.nats.client.impl.Headers; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.nats.v2_21.internal.MessageConsumer; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class ConnectionRequestInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("io.nats.client.Connection")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isPublic() + .and(named("request")) + .and(takesArguments(3)) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, byte[].class)) + .and(takesArgument(2, Duration.class)) + .and(returns(named("io.nats.client.Message"))), + ConnectionRequestInstrumentation.class.getName() + "$RequestBodyAdvice"); + transformer.applyAdviceToMethod( + isPublic() + .and(named("request")) + .and(takesArguments(4)) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, named("io.nats.client.impl.Headers"))) + .and(takesArgument(2, byte[].class)) + .and(takesArgument(3, Duration.class)) + .and(returns(named("io.nats.client.Message"))), + ConnectionRequestInstrumentation.class.getName() + "$RequestBodyHeadersAdvice"); + transformer.applyAdviceToMethod( + isPublic() + .and(named("request")) + .and(takesArguments(2)) + .and(takesArgument(0, named("io.nats.client.Message"))) + .and(takesArgument(1, Duration.class)) + .and(returns(named("io.nats.client.Message"))), + ConnectionRequestInstrumentation.class.getName() + "$RequestMessageAdvice"); + transformer.applyAdviceToMethod( + isPublic() + .and(named("request")) + .and(takesArguments(2)) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, byte[].class)) + .and(returns(named("java.util.concurrent.CompletableFuture"))), + ConnectionRequestInstrumentation.class.getName() + "$RequestFutureBodyAdvice"); + transformer.applyAdviceToMethod( + isPublic() + .and(named("request")) + .and(takesArguments(3)) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, named("io.nats.client.impl.Headers"))) + .and(takesArgument(2, byte[].class)) + .and(returns(named("java.util.concurrent.CompletableFuture"))), + ConnectionRequestInstrumentation.class.getName() + "$RequestFutureBodyHeadersAdvice"); + transformer.applyAdviceToMethod( + isPublic() + .and(named("request")) + .and(takesArguments(1)) + .and(takesArgument(0, named("io.nats.client.Message"))) + .and(returns(named("java.util.concurrent.CompletableFuture"))), + ConnectionRequestInstrumentation.class.getName() + "$RequestFutureMessageAdvice"); + transformer.applyAdviceToMethod( + isPublic() + .and(named("requestWithTimeout")) + .and(takesArguments(3)) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, byte[].class)) + .and(takesArgument(2, Duration.class)) + .and(returns(named("java.util.concurrent.CompletableFuture"))), + ConnectionRequestInstrumentation.class.getName() + "$RequestFutureTimeoutBodyAdvice"); + transformer.applyAdviceToMethod( + isPublic() + .and(named("requestWithTimeout")) + .and(takesArguments(4)) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, named("io.nats.client.impl.Headers"))) + .and(takesArgument(2, byte[].class)) + .and(takesArgument(3, Duration.class)) + .and(returns(named("java.util.concurrent.CompletableFuture"))), + ConnectionRequestInstrumentation.class.getName() + "$RequestFutureTimeoutBodyHeadersAdvice"); + transformer.applyAdviceToMethod( + isPublic() + .and(named("requestWithTimeout")) + .and(takesArguments(2)) + .and(takesArgument(0, named("io.nats.client.Message"))) + .and(takesArgument(1, Duration.class)) + .and(returns(named("java.util.concurrent.CompletableFuture"))), + ConnectionRequestInstrumentation.class.getName() + "$RequestFutureTimeoutMessageAdvice"); + } + + @SuppressWarnings("unused") + public static class RequestBodyAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) String subject, + @Advice.Argument(1) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + natsRequest = NatsRequest.create(connection, subject, body); + Context parentContext = Context.current(); + + if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + return; + } + + otelContext = CLIENT_INSTRUMENTER.start(parentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.This Connection connection, + @Advice.Thrown Throwable throwable, + @Advice.Return Message message, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + Throwable error = throwable; + NatsRequest natsResponse = null; + if (message == null) { + error = new TimeoutException("Timed out waiting for message"); + } else { + natsResponse = NatsRequest.create(connection, message); + } + + otelScope.close(); + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, error); + } + } + + @SuppressWarnings("unused") + public static class RequestBodyHeadersAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) String subject, + @Advice.Argument(1) Headers headers, + @Advice.Argument(2) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + natsRequest = NatsRequest.create(connection, subject, headers, body); + Context parentContext = Context.current(); + + if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + return; + } + + otelContext = CLIENT_INSTRUMENTER.start(parentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.This Connection connection, + @Advice.Thrown Throwable throwable, + @Advice.Return Message message, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + Throwable error = throwable; + NatsRequest natsResponse = null; + if (message == null) { + error = new TimeoutException("Timed out waiting for message"); + } else { + natsResponse = NatsRequest.create(connection, message); + } + + otelScope.close(); + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, error); + } + } + + @SuppressWarnings("unused") + public static class RequestMessageAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) Message message, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + natsRequest = NatsRequest.create(connection, message); + Context parentContext = Context.current(); + + if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + return; + } + + otelContext = CLIENT_INSTRUMENTER.start(parentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.This Connection connection, + @Advice.Thrown Throwable throwable, + @Advice.Return Message message, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + Throwable error = throwable; + NatsRequest natsResponse = null; + if (message == null) { + error = new TimeoutException("Timed out waiting for message"); + } else { + natsResponse = NatsRequest.create(connection, message); + } + + otelScope.close(); + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, error); + } + } + + @SuppressWarnings("unused") + public static class RequestFutureBodyAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) String subject, + @Advice.Argument(1) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelParentContext") Context otelParentContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + natsRequest = NatsRequest.create(connection, subject, body); + otelParentContext = Context.current(); + + if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { + return; + } + + otelContext = CLIENT_INSTRUMENTER.start(otelParentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.This Connection connection, + @Advice.Thrown Throwable throwable, + @Advice.Return(readOnly = false) CompletableFuture messageFuture, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelParentContext") Context otelParentContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + otelScope.close(); + if (throwable != null) { + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + } else { + messageFuture.whenComplete( + new MessageConsumer(CLIENT_INSTRUMENTER, otelContext, connection, natsRequest)); + messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); + } + } + } + + @SuppressWarnings("unused") + public static class RequestFutureBodyHeadersAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) String subject, + @Advice.Argument(1) Headers headers, + @Advice.Argument(2) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelParentContext") Context otelParentContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + natsRequest = NatsRequest.create(connection, subject, headers, body); + otelParentContext = Context.current(); + + if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { + return; + } + + otelContext = CLIENT_INSTRUMENTER.start(otelParentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.This Connection connection, + @Advice.Thrown Throwable throwable, + @Advice.Return(readOnly = false) CompletableFuture messageFuture, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelParentContext") Context otelParentContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + otelScope.close(); + if (throwable != null) { + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + } else { + messageFuture.whenComplete( + new MessageConsumer(CLIENT_INSTRUMENTER, otelContext, connection, natsRequest)); + messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); + } + } + } + + @SuppressWarnings("unused") + public static class RequestFutureMessageAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) Message message, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelParentContext") Context otelParentContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + natsRequest = NatsRequest.create(connection, message); + otelParentContext = Context.current(); + + if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { + return; + } + + otelContext = CLIENT_INSTRUMENTER.start(otelParentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.This Connection connection, + @Advice.Thrown Throwable throwable, + @Advice.Return(readOnly = false) CompletableFuture messageFuture, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelParentContext") Context otelParentContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + otelScope.close(); + if (throwable != null) { + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + } else { + messageFuture.whenComplete( + new MessageConsumer(CLIENT_INSTRUMENTER, otelContext, connection, natsRequest)); + messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); + } + } + } + + @SuppressWarnings("unused") + public static class RequestFutureTimeoutBodyAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) String subject, + @Advice.Argument(1) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelParentContext") Context otelParentContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + natsRequest = NatsRequest.create(connection, subject, body); + otelParentContext = Context.current(); + + if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { + return; + } + + otelContext = CLIENT_INSTRUMENTER.start(otelParentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.This Connection connection, + @Advice.Thrown Throwable throwable, + @Advice.Return(readOnly = false) CompletableFuture messageFuture, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelParentContext") Context otelParentContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + otelScope.close(); + if (throwable != null) { + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + } else { + messageFuture.whenComplete( + new MessageConsumer(CLIENT_INSTRUMENTER, otelContext, connection, natsRequest)); + messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); + } + } + } + + @SuppressWarnings("unused") + public static class RequestFutureTimeoutBodyHeadersAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) String subject, + @Advice.Argument(1) Headers headers, + @Advice.Argument(2) byte[] body, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelParentContext") Context otelParentContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + natsRequest = NatsRequest.create(connection, subject, headers, body); + otelParentContext = Context.current(); + + if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { + return; + } + + otelContext = CLIENT_INSTRUMENTER.start(otelParentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.This Connection connection, + @Advice.Thrown Throwable throwable, + @Advice.Return(readOnly = false) CompletableFuture messageFuture, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelParentContext") Context otelParentContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + otelScope.close(); + if (throwable != null) { + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + } else { + messageFuture.whenComplete( + new MessageConsumer(CLIENT_INSTRUMENTER, otelContext, connection, natsRequest)); + messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); + } + } + } + + @SuppressWarnings("unused") + public static class RequestFutureTimeoutMessageAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) Message message, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelParentContext") Context otelParentContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + natsRequest = NatsRequest.create(connection, message); + otelParentContext = Context.current(); + + if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { + return; + } + + otelContext = CLIENT_INSTRUMENTER.start(otelParentContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.This Connection connection, + @Advice.Thrown Throwable throwable, + @Advice.Return(readOnly = false) CompletableFuture messageFuture, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelParentContext") Context otelParentContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest) { + if (otelScope == null) { + return; + } + + otelScope.close(); + if (throwable != null) { + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + } else { + messageFuture.whenComplete( + new MessageConsumer(CLIENT_INSTRUMENTER, otelContext, connection, natsRequest)); + messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); + } + } + } +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java index 332040465fff..7afef4544331 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java @@ -25,6 +25,7 @@ public List typeInstrumentations() { return Arrays.asList( new ConnectionSubscribeInstrumentation(), new ConnectionPublishInstrumentation(), + new ConnectionRequestInstrumentation(), new SubscriptionInstrumentation()); } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java index c6f23b4d74ae..293de0f52a0b 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createClientInstrumenter; import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createConsumerInstrumenter; import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createProducerInstrumenter; @@ -20,5 +21,8 @@ public final class NatsSingletons { public static final Instrumenter CONSUMER_INSTRUMENTER = createConsumerInstrumenter(GlobalOpenTelemetry.get()); + public static final Instrumenter CLIENT_INSTRUMENTER = + createClientInstrumenter(GlobalOpenTelemetry.get()); + private NatsSingletons() {} } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java new file mode 100644 index 000000000000..5b8628b64d72 --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java @@ -0,0 +1,55 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21.internal; + +import io.nats.client.Connection; +import io.nats.client.Message; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import java.util.function.BiConsumer; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class MessageConsumer implements BiConsumer { + private final Instrumenter instrumenter; + private final Context context; + private final Connection connection; + private final NatsRequest request; + + public MessageConsumer( + Instrumenter instrumenter, + Context context, + Connection connection, + NatsRequest request) { + this.instrumenter = instrumenter; + this.context = context; + this.connection = connection; + this.request = request; + } + + @Override + public void accept(Message message, Throwable throwable) { + if (message != null) { + NatsRequest response = NatsRequest.create(connection, message); + instrumenter.end(context, request, response, throwable); + } else { + instrumenter.end(context, request, null, throwable); + } + } + + + /*messageFuture = messageFuture.whenComplete((message, exception) -> { + if (message != null) { + NatsRequest response = NatsRequest.create(connection, message); + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, response, throwable); + } else { + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, exception); + } + });*/ +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java new file mode 100644 index 000000000000..68ad4a672eb3 --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java @@ -0,0 +1,341 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; +import static org.assertj.core.api.Assertions.assertThat; + +import io.nats.client.Connection; +import io.nats.client.Dispatcher; +import io.nats.client.Message; +import io.nats.client.Nats; +import io.nats.client.Subscription; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.CancellationException; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +@SuppressWarnings("deprecation") // using deprecated semconv +class NatsInstrumentationRequestTest { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + static final DockerImageName natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); + + static final GenericContainer natsContainer = + new GenericContainer<>(natsImage).withExposedPorts(4222); + + static String natsUrl; + static Connection connection; + static Subscription subscription; + static int clientId; + + @BeforeAll + static void beforeAll() { + natsContainer.start(); + natsUrl = "nats://" + natsContainer.getHost() + ":" + natsContainer.getMappedPort(4222); + } + + @AfterAll + static void afterAll() { + natsContainer.close(); + } + + @BeforeEach + void beforeEach() throws IOException, InterruptedException { + connection = Nats.connect(natsUrl); + subscription = connection.subscribe("sub"); + clientId = connection.getServerInfo().getClientId(); + } + + @AfterEach + void afterEach() throws InterruptedException, TimeoutException { + subscription.drain(Duration.ofSeconds(1)); + subscription.unsubscribe(); + connection.drain(Duration.ZERO); + connection.close(); + } + + + @Test + void testRequestTimeout() throws InterruptedException { + // when + testing.runWithSpan( + "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); + + // then + assertTimeoutPublishSpan(); + assertNoHeaders(); + } + + @Test + void testRequestFutureTimeoutBodyNoHeaders() throws InterruptedException { + // when + testing.runWithSpan( + "parent", + () -> connection.requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); + + // then + assertCancellationPublishSpan(); + assertNoHeaders(); + } + + @Test + void testRequestFutureTimeoutBodyWithHeaders() throws InterruptedException { + // when + testing.runWithSpan( + "parent", + () -> connection.requestWithTimeout("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + + // then + assertCancellationPublishSpan(); + assertTraceparentHeader(); + } + + @Test + void testRequestFutureTimeoutMessageNoHeaders() throws InterruptedException { + // given + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + + // when + testing.runWithSpan( "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); + + // then + assertCancellationPublishSpan(); + assertNoHeaders(); + } + + @Test + void testRequestFutureTimeoutMessageWithHeaders() throws InterruptedException { + // given + NatsMessage message = NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + + // when + testing.runWithSpan( "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); + + // then + assertCancellationPublishSpan(); + assertTraceparentHeader(); + } + + @Test + void testRequestBodyNoHeaders() throws InterruptedException { + // given + Dispatcher dispatcher = connection.createDispatcher( + m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + + // when + testing.runWithSpan("parent", + () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); + connection.closeDispatcher(dispatcher); + + // then + assertPublishSpan(); + assertNoHeaders(); + } + + @Test + void testRequestFutureBodyNoHeaders() throws InterruptedException { + // given + Dispatcher dispatcher = connection.createDispatcher( + m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + + // when + testing.runWithSpan("parent", () -> connection.request("sub", new byte[] {0})) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); + + // then + assertPublishSpan(); + assertNoHeaders(); + } + + @Test + void testRequestBodyWithHeaders() throws InterruptedException { + // given + Dispatcher dispatcher = connection.createDispatcher( + m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + + // when + testing.runWithSpan("parent", + () -> connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + connection.closeDispatcher(dispatcher); + + // then + assertPublishSpan(); + assertTraceparentHeader(); + } + + @Test + void testRequestFutureBodyWithHeaders() throws InterruptedException { + // given + Dispatcher dispatcher = connection.createDispatcher( + m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + + // when + testing.runWithSpan("parent", () -> connection.request("sub", new Headers(), new byte[] {0})) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); + + // then + assertPublishSpan(); + assertTraceparentHeader(); + } + + @Test + void testRequestMessageNoHeaders() throws InterruptedException { + // given + Dispatcher dispatcher = connection.createDispatcher( + m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + + // when + testing.runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); + connection.closeDispatcher(dispatcher); + + // then + assertPublishSpan(); + assertNoHeaders(); + } + + @Test + void testRequestFutureMessageNoHeaders() throws InterruptedException { + // given + Dispatcher dispatcher = connection.createDispatcher( + m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + + // when + testing.runWithSpan("parent", () -> connection.request(message)) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); + + // then + assertPublishSpan(); + assertNoHeaders(); + } + + @Test + void testRequestMessageWithHeaders() throws InterruptedException { + // given + Dispatcher dispatcher = connection.createDispatcher( + m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + NatsMessage message = + NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + + // when + testing.runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); + connection.closeDispatcher(dispatcher); + + // then + assertPublishSpan(); + assertTraceparentHeader(); + } + + @Test + void testRequestFutureMessageWithHeaders() throws InterruptedException { + // given + Dispatcher dispatcher = connection.createDispatcher( + m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + NatsMessage message = + NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + + // when + testing.runWithSpan("parent", () -> connection.request(message)) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); + + // then + assertPublishSpan(); + assertTraceparentHeader(); + } + + private static void assertPublishSpan() { + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "publish"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId)))), + // dispatcher publish + trace -> trace.hasSpansSatisfyingExactly(span -> span.hasKind(SpanKind.PRODUCER))); + } + + private static void assertTimeoutPublishSpan() { + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasException(new TimeoutException("Timed out waiting for message")) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "publish"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))))); + } + + private static void assertCancellationPublishSpan() { + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasException(new CancellationException("Future cancelled, response not registered in time, check connection status.")) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "publish"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))))); + } + + private static void assertNoHeaders() throws InterruptedException { + Message published = subscription.nextMessage(Duration.ofSeconds(1)); + assertThat(published.getHeaders()).isNull(); + } + + private static void assertTraceparentHeader() throws InterruptedException { + Message published = subscription.nextMessage(Duration.ofSeconds(1)); + assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java index 5041fb3ab1a4..4a101b5932e6 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java @@ -91,31 +91,36 @@ public void publish(Message message) { } @Override - public CompletableFuture request(String subject, byte[] body) { + public Message request(String subject, byte[] body, Duration timeout) + throws InterruptedException { return wrapRequest( - NatsRequest.create(this, subject, body), () -> delegate.request(subject, body)); + NatsRequest.create(this, subject, body), () -> delegate.request(subject, body, timeout)); } @Override - public Message request(String subject, byte[] body, Duration timeout) + public Message request(String subject, Headers headers, byte[] body, Duration timeout) throws InterruptedException { return wrapRequest( - NatsRequest.create(this, subject, body), () -> delegate.request(subject, body, timeout)); + NatsRequest.create(this, subject, headers, body), + () -> delegate.request(subject, headers, body, timeout)); } @Override - public CompletableFuture request(String subject, Headers headers, byte[] body) { + public Message request(Message message, Duration timeout) throws InterruptedException { + return wrapRequest(NatsRequest.create(this, message), () -> delegate.request(message, timeout)); + } + + @Override + public CompletableFuture request(String subject, byte[] body) { return wrapRequest( - NatsRequest.create(this, subject, headers, body), - () -> delegate.request(subject, headers, body)); + NatsRequest.create(this, subject, body), () -> delegate.request(subject, body)); } @Override - public Message request(String subject, Headers headers, byte[] body, Duration timeout) - throws InterruptedException { + public CompletableFuture request(String subject, Headers headers, byte[] body) { return wrapRequest( NatsRequest.create(this, subject, headers, body), - () -> delegate.request(subject, headers, body, timeout)); + () -> delegate.request(subject, headers, body)); } @Override @@ -123,11 +128,6 @@ public CompletableFuture request(Message message) { return wrapRequest(NatsRequest.create(this, message), () -> delegate.request(message)); } - @Override - public Message request(Message message, Duration timeout) throws InterruptedException { - return wrapRequest(NatsRequest.create(this, message), () -> delegate.request(message, timeout)); - } - @Override public CompletableFuture requestWithTimeout( String subject, byte[] body, Duration timeout) { From c2ca73ab4d904fa717bb7464f396941f2295d8d5 Mon Sep 17 00:00:00 2001 From: Alix Date: Sun, 1 Jun 2025 20:08:46 +0200 Subject: [PATCH 07/34] Instrument NATS library 'dispatcher/subscribe' methods --- .../ConnectionRequestInstrumentation.java | 3 +- .../nats/v2_21/NatsSingletons.java | 4 +- .../nats/v2_21/internal/MessageConsumer.java | 17 +- .../v2_21/NatsInstrumentationRequestTest.java | 85 ++++--- .../nats/v2_21/NatsTelemetry.java | 15 +- .../nats/v2_21/NatsTelemetryBuilder.java | 3 +- .../nats/v2_21/OpenTelemetryConnection.java | 28 ++- .../nats/v2_21/OpenTelemetryDispatcher.java | 149 +++++++++++++ .../v2_21/OpenTelemetryMessageHandler.java | 74 +++++++ .../nats/v2_21/OpenTelemetrySubscription.java | 14 +- .../internal/NatsInstrumenterFactory.java | 31 ++- .../v2_21/NatsTelemetryDispatcherTest.java | 208 ++++++++++++++++++ .../nats/v2_21/TestConnection.java | 21 +- .../nats/v2_21/TestDispatcher.java | 158 +++++++++++++ .../nats/v2_21/TestSubscription.java | 17 +- 15 files changed, 752 insertions(+), 75 deletions(-) create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java create mode 100644 instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryDispatcherTest.java create mode 100644 instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java index 8ae59792961b..28ccf2b4effe 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java @@ -107,7 +107,8 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(2, byte[].class)) .and(takesArgument(3, Duration.class)) .and(returns(named("java.util.concurrent.CompletableFuture"))), - ConnectionRequestInstrumentation.class.getName() + "$RequestFutureTimeoutBodyHeadersAdvice"); + ConnectionRequestInstrumentation.class.getName() + + "$RequestFutureTimeoutBodyHeadersAdvice"); transformer.applyAdviceToMethod( isPublic() .and(named("requestWithTimeout")) diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java index 293de0f52a0b..b37923157bd4 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java @@ -6,7 +6,7 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_21; import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createClientInstrumenter; -import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createConsumerInstrumenter; +import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createConsumerReceiveInstrumenter; import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createProducerInstrumenter; import io.opentelemetry.api.GlobalOpenTelemetry; @@ -19,7 +19,7 @@ public final class NatsSingletons { createProducerInstrumenter(GlobalOpenTelemetry.get()); public static final Instrumenter CONSUMER_INSTRUMENTER = - createConsumerInstrumenter(GlobalOpenTelemetry.get()); + createConsumerReceiveInstrumenter(GlobalOpenTelemetry.get()); public static final Instrumenter CLIENT_INSTRUMENTER = createClientInstrumenter(GlobalOpenTelemetry.get()); diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java index 5b8628b64d72..6f60c0e271df 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java @@ -43,13 +43,12 @@ public void accept(Message message, Throwable throwable) { } } - - /*messageFuture = messageFuture.whenComplete((message, exception) -> { - if (message != null) { - NatsRequest response = NatsRequest.create(connection, message); - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, response, throwable); - } else { - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, exception); - } - });*/ + /*messageFuture = messageFuture.whenComplete((message, exception) -> { + if (message != null) { + NatsRequest response = NatsRequest.create(connection, message); + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, response, throwable); + } else { + CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, exception); + } + });*/ } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java index 68ad4a672eb3..fa472a19a6d2 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java @@ -78,7 +78,6 @@ void afterEach() throws InterruptedException, TimeoutException { connection.close(); } - @Test void testRequestTimeout() throws InterruptedException { // when @@ -107,7 +106,9 @@ void testRequestFutureTimeoutBodyWithHeaders() throws InterruptedException { // when testing.runWithSpan( "parent", - () -> connection.requestWithTimeout("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + () -> + connection.requestWithTimeout( + "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); @@ -120,7 +121,8 @@ void testRequestFutureTimeoutMessageNoHeaders() throws InterruptedException { NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when - testing.runWithSpan( "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); + testing.runWithSpan( + "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); @@ -130,10 +132,12 @@ void testRequestFutureTimeoutMessageNoHeaders() throws InterruptedException { @Test void testRequestFutureTimeoutMessageWithHeaders() throws InterruptedException { // given - NatsMessage message = NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + NatsMessage message = + NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); // when - testing.runWithSpan( "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); + testing.runWithSpan( + "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); @@ -143,12 +147,14 @@ void testRequestFutureTimeoutMessageWithHeaders() throws InterruptedException { @Test void testRequestBodyNoHeaders() throws InterruptedException { // given - Dispatcher dispatcher = connection.createDispatcher( - m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + Dispatcher dispatcher = + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); // when - testing.runWithSpan("parent", - () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); + testing.runWithSpan( + "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); connection.closeDispatcher(dispatcher); // then @@ -159,11 +165,14 @@ void testRequestBodyNoHeaders() throws InterruptedException { @Test void testRequestFutureBodyNoHeaders() throws InterruptedException { // given - Dispatcher dispatcher = connection.createDispatcher( - m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + Dispatcher dispatcher = + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); // when - testing.runWithSpan("parent", () -> connection.request("sub", new byte[] {0})) + testing + .runWithSpan("parent", () -> connection.request("sub", new byte[] {0})) .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then @@ -174,11 +183,14 @@ void testRequestFutureBodyNoHeaders() throws InterruptedException { @Test void testRequestBodyWithHeaders() throws InterruptedException { // given - Dispatcher dispatcher = connection.createDispatcher( - m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + Dispatcher dispatcher = + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); // when - testing.runWithSpan("parent", + testing.runWithSpan( + "parent", () -> connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); connection.closeDispatcher(dispatcher); @@ -190,11 +202,14 @@ void testRequestBodyWithHeaders() throws InterruptedException { @Test void testRequestFutureBodyWithHeaders() throws InterruptedException { // given - Dispatcher dispatcher = connection.createDispatcher( - m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + Dispatcher dispatcher = + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); // when - testing.runWithSpan("parent", () -> connection.request("sub", new Headers(), new byte[] {0})) + testing + .runWithSpan("parent", () -> connection.request("sub", new Headers(), new byte[] {0})) .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then @@ -205,8 +220,10 @@ void testRequestFutureBodyWithHeaders() throws InterruptedException { @Test void testRequestMessageNoHeaders() throws InterruptedException { // given - Dispatcher dispatcher = connection.createDispatcher( - m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + Dispatcher dispatcher = + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when @@ -221,12 +238,15 @@ void testRequestMessageNoHeaders() throws InterruptedException { @Test void testRequestFutureMessageNoHeaders() throws InterruptedException { // given - Dispatcher dispatcher = connection.createDispatcher( - m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + Dispatcher dispatcher = + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when - testing.runWithSpan("parent", () -> connection.request(message)) + testing + .runWithSpan("parent", () -> connection.request(message)) .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then @@ -237,8 +257,10 @@ void testRequestFutureMessageNoHeaders() throws InterruptedException { @Test void testRequestMessageWithHeaders() throws InterruptedException { // given - Dispatcher dispatcher = connection.createDispatcher( - m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + Dispatcher dispatcher = + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); NatsMessage message = NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); @@ -254,13 +276,16 @@ void testRequestMessageWithHeaders() throws InterruptedException { @Test void testRequestFutureMessageWithHeaders() throws InterruptedException { // given - Dispatcher dispatcher = connection.createDispatcher( - m -> connection.publish(m.getReplyTo(), m.getData())).subscribe("sub"); + Dispatcher dispatcher = + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); NatsMessage message = NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); // when - testing.runWithSpan("parent", () -> connection.request(message)) + testing + .runWithSpan("parent", () -> connection.request(message)) .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then @@ -318,7 +343,9 @@ private static void assertCancellationPublishSpan() { span.hasName("sub publish") .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) - .hasException(new CancellationException("Future cancelled, response not registered in time, check connection status.")) + .hasException( + new CancellationException( + "Future cancelled, response not registered in time, check connection status.")) .hasAttributesSatisfyingExactly( equalTo(MESSAGING_OPERATION, "publish"), equalTo(MESSAGING_SYSTEM, "nats"), diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java index 6ac5fc084342..81da357de4c5 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java @@ -21,20 +21,27 @@ public static NatsTelemetryBuilder builder(OpenTelemetry openTelemetry) { } private final Instrumenter producerInstrumenter; - private final Instrumenter consumerInstrumenter; + private final Instrumenter consumerReceiveInstrumenter; + private final Instrumenter consumerProcessInstrumenter; private final Instrumenter clientInstrumenter; public NatsTelemetry( Instrumenter producerInstrumenter, - Instrumenter consumerInstrumenter, + Instrumenter consumerReceiveInstrumenter, + Instrumenter consumerProcessInstrumenter, Instrumenter clientInstrumenter) { this.producerInstrumenter = producerInstrumenter; - this.consumerInstrumenter = consumerInstrumenter; + this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; + this.consumerProcessInstrumenter = consumerProcessInstrumenter; this.clientInstrumenter = clientInstrumenter; } public OpenTelemetryConnection wrap(Connection connection) { return new OpenTelemetryConnection( - connection, this.producerInstrumenter, this.consumerInstrumenter, this.clientInstrumenter); + connection, + producerInstrumenter, + consumerReceiveInstrumenter, + consumerProcessInstrumenter, + clientInstrumenter); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java index dd9adb6ea1b4..975f058129d7 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java @@ -19,7 +19,8 @@ public final class NatsTelemetryBuilder { public NatsTelemetry build() { return new NatsTelemetry( NatsInstrumenterFactory.createProducerInstrumenter(openTelemetry), - NatsInstrumenterFactory.createConsumerInstrumenter(openTelemetry), + NatsInstrumenterFactory.createConsumerReceiveInstrumenter(openTelemetry), + NatsInstrumenterFactory.createConsumerProcessInstrumenter(openTelemetry), NatsInstrumenterFactory.createClientInstrumenter(openTelemetry)); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java index 4a101b5932e6..2171c8302ecc 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java @@ -46,17 +46,20 @@ public class OpenTelemetryConnection implements Connection { private final Connection delegate; private final Instrumenter producerInstrumenter; - private final Instrumenter consumerInstrumenter; + private final Instrumenter consumerReceiveInstrumenter; + private final Instrumenter consumerProcessInstrumenter; private final Instrumenter clientInstrumenter; public OpenTelemetryConnection( Connection connection, Instrumenter producerInstrumenter, - Instrumenter consumerInstrumenter, + Instrumenter consumerReceiveInstrumenter, + Instrumenter consumerProcessInstrumenter, Instrumenter clientInstrumenter) { this.delegate = connection; this.producerInstrumenter = producerInstrumenter; - this.consumerInstrumenter = consumerInstrumenter; + this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; + this.consumerProcessInstrumenter = consumerProcessInstrumenter; this.clientInstrumenter = clientInstrumenter; } @@ -153,23 +156,34 @@ public CompletableFuture requestWithTimeout(Message message, Duration t @Override public Subscription subscribe(String subject) { return new OpenTelemetrySubscription( - this, delegate.subscribe(subject), this.consumerInstrumenter); + this, delegate.subscribe(subject), this.consumerReceiveInstrumenter); } @Override public Subscription subscribe(String subject, String queueName) { return new OpenTelemetrySubscription( - this, delegate.subscribe(subject, queueName), this.consumerInstrumenter); + this, delegate.subscribe(subject, queueName), this.consumerReceiveInstrumenter); } @Override public Dispatcher createDispatcher(MessageHandler messageHandler) { - return delegate.createDispatcher(messageHandler); + OpenTelemetryMessageHandler otelHandler = + new OpenTelemetryMessageHandler( + this, messageHandler, consumerReceiveInstrumenter, consumerProcessInstrumenter); + return new OpenTelemetryDispatcher( + this, + delegate.createDispatcher(otelHandler), + consumerReceiveInstrumenter, + consumerProcessInstrumenter); } @Override public Dispatcher createDispatcher() { - return delegate.createDispatcher(); + return new OpenTelemetryDispatcher( + this, + delegate.createDispatcher(), + consumerReceiveInstrumenter, + consumerProcessInstrumenter); } @Override diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java new file mode 100644 index 000000000000..799518662c9c --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java @@ -0,0 +1,149 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.nats.client.Dispatcher; +import io.nats.client.MessageHandler; +import io.nats.client.Subscription; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +public class OpenTelemetryDispatcher implements Dispatcher { + + private final Connection connection; + private final Dispatcher delegate; + private final Instrumenter consumerReceiveInstrumenter; + private final Instrumenter consumerProcessInstrumenter; + + public OpenTelemetryDispatcher( + Connection connection, + Dispatcher delegate, + Instrumenter consumerReceiveInstrumenter, + Instrumenter consumerProcessInstrumenter) { + this.connection = connection; + this.delegate = delegate; + this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; + this.consumerProcessInstrumenter = consumerProcessInstrumenter; + } + + @Override + public void start(String id) { + delegate.start(id); + } + + @Override + public Dispatcher subscribe(String subject) { + delegate.subscribe(subject); + // from javadoc - Returns: The Dispatcher, so calls can be chained. + return this; + } + + @Override + public Dispatcher subscribe(String subject, String queue) { + delegate.subscribe(subject, queue); + // from javadoc - Returns: The Dispatcher, so calls can be chained. + return this; + } + + @Override + public Subscription subscribe(String subject, MessageHandler handler) { + OpenTelemetryMessageHandler otelHandler = + new OpenTelemetryMessageHandler( + connection, handler, consumerReceiveInstrumenter, consumerProcessInstrumenter); + Subscription wrapped = delegate.subscribe(subject, otelHandler); + return new OpenTelemetrySubscription(connection, wrapped, consumerReceiveInstrumenter); + } + + @Override + public Subscription subscribe(String subject, String queue, MessageHandler handler) { + OpenTelemetryMessageHandler otelHandler = + new OpenTelemetryMessageHandler( + connection, handler, consumerReceiveInstrumenter, consumerProcessInstrumenter); + Subscription wrapped = delegate.subscribe(subject, queue, otelHandler); + return new OpenTelemetrySubscription(connection, wrapped, consumerReceiveInstrumenter); + } + + @Override + public Dispatcher unsubscribe(String subject) { + delegate.unsubscribe(subject); + // from javadoc - Returns: The Dispatcher, so calls can be chained. + return this; + } + + @Override + public Dispatcher unsubscribe(Subscription subscription) { + delegate.unsubscribe(subscription); + // from javadoc - Returns: The Dispatcher, so calls can be chained. + return this; + } + + @Override + public Dispatcher unsubscribe(String subject, int after) { + delegate.unsubscribe(subject, after); + // from javadoc - Returns: The Dispatcher, so calls can be chained. + return this; + } + + @Override + public Dispatcher unsubscribe(Subscription subscription, int after) { + delegate.unsubscribe(subscription, after); + // from javadoc - Returns: The Dispatcher, so calls can be chained. + return this; + } + + @Override + public void setPendingLimits(long maxMessages, long maxBytes) { + delegate.setPendingLimits(maxMessages, maxBytes); + } + + @Override + public long getPendingMessageLimit() { + return delegate.getPendingMessageLimit(); + } + + @Override + public long getPendingByteLimit() { + return delegate.getPendingByteLimit(); + } + + @Override + public long getPendingMessageCount() { + return delegate.getPendingMessageCount(); + } + + @Override + public long getPendingByteCount() { + return delegate.getPendingByteCount(); + } + + @Override + public long getDeliveredCount() { + return delegate.getDeliveredCount(); + } + + @Override + public long getDroppedCount() { + return delegate.getDroppedCount(); + } + + @Override + public void clearDroppedCount() { + delegate.clearDroppedCount(); + } + + @Override + public boolean isActive() { + return delegate.isActive(); + } + + @Override + public CompletableFuture drain(Duration timeout) throws InterruptedException { + return delegate.drain(timeout); + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java new file mode 100644 index 000000000000..14dc1ce7e120 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java @@ -0,0 +1,74 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.nats.client.Message; +import io.nats.client.MessageHandler; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; +import io.opentelemetry.instrumentation.api.internal.Timer; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; + +public class OpenTelemetryMessageHandler implements MessageHandler { + + private final Connection connection; + private final MessageHandler delegate; + private final Instrumenter consumerReceiveInstrumenter; + private final Instrumenter consumerProcessInstrumenter; + + public OpenTelemetryMessageHandler( + Connection connection, + MessageHandler delegate, + Instrumenter consumerReceiveInstrumenter, + Instrumenter consumerProcessInstrumenter) { + this.connection = connection; + this.delegate = delegate; + this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; + this.consumerProcessInstrumenter = consumerProcessInstrumenter; + } + + @Override + public void onMessage(Message message) throws InterruptedException { + Timer timer = Timer.start(); + + Context parentContext = Context.current(); + NatsRequest natsRequest = NatsRequest.create(connection, message); + + if (!Span.fromContext(parentContext).getSpanContext().isValid() + || !consumerReceiveInstrumenter.shouldStart(parentContext, natsRequest)) { + delegate.onMessage(message); + return; + } + + Context receiveContext = + InstrumenterUtil.startAndEnd( + consumerReceiveInstrumenter, + parentContext, + natsRequest, + null, + null, + timer.startTime(), + timer.now()); + + if (!consumerProcessInstrumenter.shouldStart(receiveContext, natsRequest)) { + delegate.onMessage(message); + return; + } + + Context processContext = consumerProcessInstrumenter.start(receiveContext, natsRequest); + try { + delegate.onMessage(message); + } catch (InterruptedException e) { + consumerProcessInstrumenter.end(processContext, natsRequest, null, e); + throw e; + } finally { + consumerProcessInstrumenter.end(processContext, natsRequest, null, null); + } + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java index b9c2d7ad0ca3..fa8e7799ec68 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java @@ -24,15 +24,15 @@ public class OpenTelemetrySubscription implements Subscription { private final Connection connection; private final Subscription delegate; - private final Instrumenter consumerInstrumenter; + private final Instrumenter consumerReceiveInstrumenter; public OpenTelemetrySubscription( Connection connection, Subscription subscription, - Instrumenter consumerInstrumenter) { + Instrumenter consumerReceiveInstrumenter) { this.connection = connection; this.delegate = subscription; - this.consumerInstrumenter = consumerInstrumenter; + this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; } @Override @@ -131,21 +131,21 @@ private Message wrapNextMessage( Context parentContext = Context.current(); TimeoutException timeout = null; - NatsRequest natsRequest = NatsRequest.create(this.connection, this.getSubject()); + NatsRequest natsRequest = NatsRequest.create(connection, getSubject()); if (message == null) { timeout = new TimeoutException("Timed out waiting for message"); } else { - natsRequest = NatsRequest.create(this.connection, message); + natsRequest = NatsRequest.create(connection, message); } if (!Span.fromContext(parentContext).getSpanContext().isValid() - || !consumerInstrumenter.shouldStart(parentContext, natsRequest)) { + || !consumerReceiveInstrumenter.shouldStart(parentContext, natsRequest)) { return message; } InstrumenterUtil.startAndEnd( - consumerInstrumenter, + consumerReceiveInstrumenter, parentContext, natsRequest, null, diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java index 5910634a6488..26ecb9bf8e54 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java @@ -11,6 +11,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; @@ -31,14 +32,22 @@ public final class NatsInstrumenterFactory { MessagingAttributesExtractor.create( NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PUBLISH); - public static final SpanNameExtractor CONSUMER_SPAN_NAME_EXTRACTOR = + public static final SpanNameExtractor CONSUMER_RECEIVE_SPAN_NAME_EXTRACTOR = MessagingSpanNameExtractor.create( NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE); - public static final AttributesExtractor CONSUMER_ATTRIBUTES_EXTRACTOR = + public static final AttributesExtractor CONSUMER_RECEIVE_ATTRIBUTES_EXTRACTOR = MessagingAttributesExtractor.create( NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE); + public static final SpanNameExtractor CONSUMER_PROCESS_SPAN_NAME_EXTRACTOR = + MessagingSpanNameExtractor.create( + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PROCESS); + + public static final AttributesExtractor CONSUMER_PROCESS_ATTRIBUTES_EXTRACTOR = + MessagingAttributesExtractor.create( + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PROCESS); + public static final AttributesExtractor CLIENT_ATTRIBUTES_EXTRACTOR = MessagingAttributesExtractor.create( NatsRequestMessagingAttributesGetter.NATS_REQUEST_INSTANCE, MessageOperation.PUBLISH); @@ -51,11 +60,11 @@ public static Instrumenter createProducerInstrumenter( .buildProducerInstrumenter(NatsRequestTextMapSetter.INSTANCE); } - public static Instrumenter createConsumerInstrumenter( + public static Instrumenter createConsumerReceiveInstrumenter( OpenTelemetry openTelemetry) { return Instrumenter.builder( - openTelemetry, INSTRUMENTATION_NAME, CONSUMER_SPAN_NAME_EXTRACTOR) - .addAttributesExtractor(CONSUMER_ATTRIBUTES_EXTRACTOR) + openTelemetry, INSTRUMENTATION_NAME, CONSUMER_RECEIVE_SPAN_NAME_EXTRACTOR) + .addAttributesExtractor(CONSUMER_RECEIVE_ATTRIBUTES_EXTRACTOR) .addSpanLinksExtractor( new PropagatorBasedSpanLinksExtractor<>( openTelemetry.getPropagators().getTextMapPropagator(), @@ -63,6 +72,18 @@ public static Instrumenter createConsumerInstrumenter( .buildConsumerInstrumenter(NatsRequestTextMapGetter.INSTANCE); } + public static Instrumenter createConsumerProcessInstrumenter( + OpenTelemetry openTelemetry) { + return Instrumenter.builder( + openTelemetry, INSTRUMENTATION_NAME, CONSUMER_PROCESS_SPAN_NAME_EXTRACTOR) + .addAttributesExtractor(CONSUMER_PROCESS_ATTRIBUTES_EXTRACTOR) + .addSpanLinksExtractor( + new PropagatorBasedSpanLinksExtractor<>( + openTelemetry.getPropagators().getTextMapPropagator(), + NatsRequestTextMapGetter.INSTANCE)) + .buildInstrumenter(SpanKindExtractor.alwaysConsumer()); + } + public static Instrumenter createClientInstrumenter( OpenTelemetry openTelemetry) { return Instrumenter.builder( diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryDispatcherTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryDispatcherTest.java new file mode 100644 index 000000000000..743fbb18c6d5 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryDispatcherTest.java @@ -0,0 +1,208 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; +import static org.assertj.core.api.Assertions.assertThat; + +import io.nats.client.MessageHandler; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +@SuppressWarnings("deprecation") // using deprecated semconv +class NatsTelemetryDispatcherTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + static final NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); + + private final MessageHandler handler = msg -> {}; + + @Test + void testDispatcherDefaultHandler() { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + connection.createDispatcher(handler).subscribe("sub"); + connection.createDispatcher(handler).subscribe("sub", "queue"); + + // when + testing.runWithSpan( + "parent", + () -> testConnection.deliver(NatsMessage.builder().subject("sub").data("x").build())); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> // first dispatcher + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), + span -> + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "process"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), + span -> // second dispatcher + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), + span -> + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(3)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "process"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + } + + @Test + void testDispatcherHandler() { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + connection.createDispatcher().subscribe("sub", handler); + connection.createDispatcher().subscribe("sub", "queue", handler); + + // when + testing.runWithSpan( + "parent", + () -> testConnection.deliver(NatsMessage.builder().subject("sub").data("x").build())); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> // first dispatcher + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), + span -> + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "process"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), + span -> // second dispatcher + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), + span -> + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(3)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "process"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + } + + @SuppressWarnings("PreferJavaTimeOverload") + @Test + void testDispatcherWithLink() { + // given + TestConnection testConnection = new TestConnection(); + OpenTelemetryConnection connection = telemetry.wrap(testConnection); + connection.createDispatcher(handler).subscribe("sub"); + String linkTraceId = "0af7651916cd43dd8448eb211c80319c"; + Headers headers = + new Headers( + new Headers().put("traceparent", "00-" + linkTraceId + "-b7ad6b7169203331-01"), true); + + // when + testing.runWithSpan( + "parent", + () -> + testConnection.deliver( + NatsMessage.builder().subject("sub").data("x").headers(headers).build())); + + // then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasLinksSatisfying( + links -> + assertThat(links) + .singleElement() + .satisfies( + link -> + assertThat(link.getSpanContext().getTraceId()) + .isEqualTo(linkTraceId))) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), + span -> + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "process"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + } +} diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java index d6fab7593526..026825c2d6c8 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java @@ -46,16 +46,15 @@ public class TestConnection implements Connection { private final List subscriptions = Collections.synchronizedList(new LinkedList<>()); + private final List dispatchers = Collections.synchronizedList(new LinkedList<>()); + public final Queue publishedMessages = new ConcurrentLinkedQueue<>(); public final Queue requestedMessages = new ConcurrentLinkedQueue<>(); public final Queue requestResponseMessages = new ConcurrentLinkedQueue<>(); public void deliver(Message message) { - subscriptions.stream() - .filter(subscription -> message.getSubject().equalsIgnoreCase(subscription.getSubject())) - .forEach( - subscription -> - subscription.messages.add(new TestMessage(this, subscription, message))); + subscriptions.forEach(subscription -> subscription.deliver(message)); + dispatchers.forEach(dispatcher -> dispatcher.deliver(message)); } @Override @@ -176,16 +175,22 @@ public Subscription subscribe(String subject, String queueName) { @Override public Dispatcher createDispatcher(MessageHandler handler) { - return null; + TestDispatcher dispatcher = new TestDispatcher(handler); + dispatchers.add(dispatcher); + return dispatcher; } @Override public Dispatcher createDispatcher() { - return null; + TestDispatcher dispatcher = new TestDispatcher(); + dispatchers.add(dispatcher); + return dispatcher; } @Override - public void closeDispatcher(Dispatcher dispatcher) {} + public void closeDispatcher(Dispatcher dispatcher) { + dispatchers.remove(dispatcher); + } @Override public void addConnectionListener(ConnectionListener connectionListener) {} diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java new file mode 100644 index 000000000000..9458fd872963 --- /dev/null +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java @@ -0,0 +1,158 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Dispatcher; +import io.nats.client.Message; +import io.nats.client.MessageHandler; +import io.nats.client.Subscription; +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class TestDispatcher implements Dispatcher { + + private final MessageHandler defaultHandler; + private final List subjects = Collections.synchronizedList(new LinkedList<>()); + private final Map subscriptions = + Collections.synchronizedMap(new HashMap<>()); + + public TestDispatcher() { + this.defaultHandler = msg -> {}; + } + + public TestDispatcher(MessageHandler defaultHandler) { + this.defaultHandler = defaultHandler; + } + + @SuppressWarnings("EmptyCatch") + public void deliver(Message message) { + subjects.forEach( + subject -> { + if (message.getSubject().equalsIgnoreCase(subject)) { + try { + defaultHandler.onMessage(message); + } catch (InterruptedException ignored) { + } + } + }); + subscriptions.forEach( + (subscription, handler) -> { + subscription.deliver(message); + try { + handler.onMessage(message); + } catch (InterruptedException ignored) { + } + }); + } + + @Override + public void start(String id) {} + + @Override + public Dispatcher subscribe(String subject) { + subjects.add(subject); + return this; + } + + @Override + public Dispatcher subscribe(String subject, String queue) { + subjects.add(subject); + return this; + } + + @Override + public Subscription subscribe(String subject, MessageHandler handler) { + TestSubscription subscription = new TestSubscription(subject, null, this); + subscriptions.put(subscription, handler); + return subscription; + } + + @Override + public Subscription subscribe(String subject, String queue, MessageHandler handler) { + TestSubscription subscription = new TestSubscription(subject, queue, this); + subscriptions.put(subscription, handler); + return subscription; + } + + @Override + public Dispatcher unsubscribe(String subject) { + subjects.remove(subject); + return this; + } + + @Override + public Dispatcher unsubscribe(Subscription subscription) { + if (subscription instanceof TestSubscription) { + subscriptions.remove(subscription); + } + return this; + } + + @Override + public Dispatcher unsubscribe(String subject, int after) { + subjects.remove(subject); + return this; + } + + @Override + public Dispatcher unsubscribe(Subscription subscription, int after) { + if (subscription instanceof TestSubscription) { + subscriptions.remove(subscription); + } + return this; + } + + @Override + public void setPendingLimits(long maxMessages, long maxBytes) {} + + @Override + public long getPendingMessageLimit() { + return 0; + } + + @Override + public long getPendingByteLimit() { + return 0; + } + + @Override + public long getPendingMessageCount() { + return 0; + } + + @Override + public long getPendingByteCount() { + return 0; + } + + @Override + public long getDeliveredCount() { + return 0; + } + + @Override + public long getDroppedCount() { + return 0; + } + + @Override + public void clearDroppedCount() {} + + @Override + public boolean isActive() { + return false; + } + + @Override + public CompletableFuture drain(Duration timeout) throws InterruptedException { + return null; + } +} diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java index 154b62a50011..0450d8a9ad43 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java @@ -38,6 +38,12 @@ public TestSubscription(String subject, String queueName, Dispatcher dispatcher) this.dispatcher = dispatcher; } + public void deliver(Message message) { + if (message.getSubject().equalsIgnoreCase(getSubject())) { + messages.add(message); + } + } + @Override public String getSubject() { return subject; @@ -77,11 +83,18 @@ public Message nextMessage(long timeoutMillis) } @Override - public void unsubscribe() {} + public void unsubscribe() { + if (dispatcher != null) { + dispatcher.unsubscribe(subject); + } + } @Override public Subscription unsubscribe(int after) { - return null; + if (dispatcher != null) { + dispatcher.unsubscribe(subject, after); + } + return this; } @Override From fa341b209b452de65c95b8899f77370e60e30869 Mon Sep 17 00:00:00 2001 From: Alix Date: Sun, 8 Jun 2025 19:47:40 +0200 Subject: [PATCH 08/34] Refactor tests/names & minor fixes --- .../ConnectionPublishInstrumentation.java | 20 +- .../ConnectionRequestInstrumentation.java | 32 +-- .../v2_21/SubscriptionInstrumentation.java | 4 +- .../nats/v2_21/internal/MessageConsumer.java | 9 - .../v2_21/NatsInstrumentationPublishTest.java | 10 +- .../v2_21/NatsInstrumentationRequestTest.java | 150 ++++++------- ... NatsInstrumentationSubscriptionTest.java} | 6 +- .../nats/v2_21/OpenTelemetryConnection.java | 66 +++--- .../nats/v2_21/OpenTelemetryDispatcher.java | 18 +- .../v2_21/OpenTelemetryMessageHandler.java | 13 +- .../nats/v2_21/OpenTelemetrySubscription.java | 10 +- .../internal/NatsInstrumenterFactory.java | 12 +- .../nats/v2_21/internal/NatsRequest.java | 16 +- .../NatsRequestContextCustomizer.java | 41 ++++ .../v2_21/NatsTelemetryDispatcherTest.java | 197 +++++++----------- .../nats/v2_21/NatsTelemetryPublishTest.java | 12 +- .../nats/v2_21/NatsTelemetryRequestTest.java | 60 +++--- ...ava => NatsTelemetrySubscriptionTest.java} | 8 +- .../nats/v2_21/TestConnection.java | 2 +- .../nats/v2_21/TestDispatcher.java | 29 +-- .../nats/v2_21/TestSubscription.java | 21 +- 21 files changed, 370 insertions(+), 366 deletions(-) rename instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/{NatsInstrumentationSubscribeTest.java => NatsInstrumentationSubscriptionTest.java} (98%) create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestContextCustomizer.java rename instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/{NatsTelemetrySubscribeTest.java => NatsTelemetrySubscriptionTest.java} (96%) diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java index 82c1cf8a7d1d..265ca7dfd3b7 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java @@ -47,7 +47,7 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(0, String.class)) .and(takesArgument(1, named("io.nats.client.impl.Headers"))) .and(takesArgument(2, byte[].class)), - ConnectionPublishInstrumentation.class.getName() + "$PublishBodyHeadersAdvice"); + ConnectionPublishInstrumentation.class.getName() + "$PublishHeadersBodyAdvice"); transformer.applyAdviceToMethod( isPublic() .and(named("publish")) @@ -55,7 +55,7 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(0, String.class)) .and(takesArgument(1, String.class)) .and(takesArgument(2, byte[].class)), - ConnectionPublishInstrumentation.class.getName() + "$PublishBodyReplyToAdvice"); + ConnectionPublishInstrumentation.class.getName() + "$PublishReplyToBodyAdvice"); transformer.applyAdviceToMethod( isPublic() .and(named("publish")) @@ -64,7 +64,7 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(1, String.class)) .and(takesArgument(2, named("io.nats.client.impl.Headers"))) .and(takesArgument(3, byte[].class)), - ConnectionPublishInstrumentation.class.getName() + "$PublishBodyReplyToHeadersAdvice"); + ConnectionPublishInstrumentation.class.getName() + "$PublishReplyToHeadersBodyAdvice"); transformer.applyAdviceToMethod( isPublic() .and(named("publish")) @@ -85,7 +85,7 @@ public static void onEnter( @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { Context parentContext = Context.current(); - natsRequest = NatsRequest.create(connection, subject, body); + natsRequest = NatsRequest.create(connection, null, subject, null, body); if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { return; @@ -113,7 +113,7 @@ public static void onExit( } @SuppressWarnings("unused") - public static class PublishBodyHeadersAdvice { + public static class PublishHeadersBodyAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @@ -125,7 +125,7 @@ public static void onEnter( @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { Context parentContext = Context.current(); - natsRequest = NatsRequest.create(connection, subject, headers, body); + natsRequest = NatsRequest.create(connection, null, subject, headers, body); if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { return; @@ -154,7 +154,7 @@ public static void onExit( } @SuppressWarnings("unused") - public static class PublishBodyReplyToAdvice { + public static class PublishReplyToBodyAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @@ -166,7 +166,7 @@ public static void onEnter( @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { Context parentContext = Context.current(); - natsRequest = NatsRequest.create(connection, subject, body); + natsRequest = NatsRequest.create(connection, replyTo, subject, null, body); if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { return; @@ -195,7 +195,7 @@ public static void onExit( } @SuppressWarnings("unused") - public static class PublishBodyReplyToHeadersAdvice { + public static class PublishReplyToHeadersBodyAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @@ -208,7 +208,7 @@ public static void onEnter( @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { Context parentContext = Context.current(); - natsRequest = NatsRequest.create(connection, subject, headers, body); + natsRequest = NatsRequest.create(connection, replyTo, subject, headers, body); if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { return; diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java index 28ccf2b4effe..79ade6cb433b 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java @@ -56,7 +56,7 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(2, byte[].class)) .and(takesArgument(3, Duration.class)) .and(returns(named("io.nats.client.Message"))), - ConnectionRequestInstrumentation.class.getName() + "$RequestBodyHeadersAdvice"); + ConnectionRequestInstrumentation.class.getName() + "$RequestHeadersBodyAdvice"); transformer.applyAdviceToMethod( isPublic() .and(named("request")) @@ -81,7 +81,7 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(1, named("io.nats.client.impl.Headers"))) .and(takesArgument(2, byte[].class)) .and(returns(named("java.util.concurrent.CompletableFuture"))), - ConnectionRequestInstrumentation.class.getName() + "$RequestFutureBodyHeadersAdvice"); + ConnectionRequestInstrumentation.class.getName() + "$RequestFutureHeadersBodyAdvice"); transformer.applyAdviceToMethod( isPublic() .and(named("request")) @@ -97,7 +97,7 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(1, byte[].class)) .and(takesArgument(2, Duration.class)) .and(returns(named("java.util.concurrent.CompletableFuture"))), - ConnectionRequestInstrumentation.class.getName() + "$RequestFutureTimeoutBodyAdvice"); + ConnectionRequestInstrumentation.class.getName() + "$RequestTimeoutFutureBodyAdvice"); transformer.applyAdviceToMethod( isPublic() .and(named("requestWithTimeout")) @@ -108,7 +108,7 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(3, Duration.class)) .and(returns(named("java.util.concurrent.CompletableFuture"))), ConnectionRequestInstrumentation.class.getName() - + "$RequestFutureTimeoutBodyHeadersAdvice"); + + "$RequestTimeoutFutureHeadersBodyAdvice"); transformer.applyAdviceToMethod( isPublic() .and(named("requestWithTimeout")) @@ -116,7 +116,7 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(0, named("io.nats.client.Message"))) .and(takesArgument(1, Duration.class)) .and(returns(named("java.util.concurrent.CompletableFuture"))), - ConnectionRequestInstrumentation.class.getName() + "$RequestFutureTimeoutMessageAdvice"); + ConnectionRequestInstrumentation.class.getName() + "$RequestTimeoutFutureMessageAdvice"); } @SuppressWarnings("unused") @@ -130,7 +130,7 @@ public static void onEnter( @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - natsRequest = NatsRequest.create(connection, subject, body); + natsRequest = NatsRequest.create(connection, null, subject, null, body); Context parentContext = Context.current(); if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { @@ -167,7 +167,7 @@ public static void onExit( } @SuppressWarnings("unused") - public static class RequestBodyHeadersAdvice { + public static class RequestHeadersBodyAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @@ -178,7 +178,7 @@ public static void onEnter( @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - natsRequest = NatsRequest.create(connection, subject, headers, body); + natsRequest = NatsRequest.create(connection, null, subject, headers, body); Context parentContext = Context.current(); if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { @@ -272,7 +272,7 @@ public static void onEnter( @Advice.Local("otelParentContext") Context otelParentContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - natsRequest = NatsRequest.create(connection, subject, body); + natsRequest = NatsRequest.create(connection, null, subject, null, body); otelParentContext = Context.current(); if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { @@ -308,7 +308,7 @@ public static void onExit( } @SuppressWarnings("unused") - public static class RequestFutureBodyHeadersAdvice { + public static class RequestFutureHeadersBodyAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @@ -320,7 +320,7 @@ public static void onEnter( @Advice.Local("otelParentContext") Context otelParentContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - natsRequest = NatsRequest.create(connection, subject, headers, body); + natsRequest = NatsRequest.create(connection, null, subject, headers, body); otelParentContext = Context.current(); if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { @@ -402,7 +402,7 @@ public static void onExit( } @SuppressWarnings("unused") - public static class RequestFutureTimeoutBodyAdvice { + public static class RequestTimeoutFutureBodyAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @@ -413,7 +413,7 @@ public static void onEnter( @Advice.Local("otelParentContext") Context otelParentContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - natsRequest = NatsRequest.create(connection, subject, body); + natsRequest = NatsRequest.create(connection, null, subject, null, body); otelParentContext = Context.current(); if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { @@ -449,7 +449,7 @@ public static void onExit( } @SuppressWarnings("unused") - public static class RequestFutureTimeoutBodyHeadersAdvice { + public static class RequestTimeoutFutureHeadersBodyAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @@ -461,7 +461,7 @@ public static void onEnter( @Advice.Local("otelParentContext") Context otelParentContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - natsRequest = NatsRequest.create(connection, subject, headers, body); + natsRequest = NatsRequest.create(connection, null, subject, headers, body); otelParentContext = Context.current(); if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { @@ -497,7 +497,7 @@ public static void onExit( } @SuppressWarnings("unused") - public static class RequestFutureTimeoutMessageAdvice { + public static class RequestTimeoutFutureMessageAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java index f935f12d4154..e9f30a34d93d 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java @@ -72,7 +72,9 @@ public static void onExit( return; } - NatsRequest natsRequest = NatsRequest.create(connection, subscription.getSubject()); + NatsRequest natsRequest = + NatsRequest.create(connection, null, subscription.getSubject(), null, null); + if (message == null) { timeout = new TimeoutException("Timed out waiting for message"); } else { diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java index 6f60c0e271df..89039bf64420 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java @@ -42,13 +42,4 @@ public void accept(Message message, Throwable throwable) { instrumenter.end(context, request, null, throwable); } } - - /*messageFuture = messageFuture.whenComplete((message, exception) -> { - if (message != null) { - NatsRequest response = NatsRequest.create(connection, message); - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, response, throwable); - } else { - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, exception); - } - });*/ } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java index 4a4a08ef3830..3dd46d25f7c3 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java @@ -74,7 +74,7 @@ void afterEach() throws InterruptedException { } @Test - void testPublishBodyNoHeaders() throws InterruptedException { + void testPublishBody() throws InterruptedException { // when testing.runWithSpan("parent", () -> connection.publish("sub", new byte[] {0})); @@ -84,7 +84,7 @@ void testPublishBodyNoHeaders() throws InterruptedException { } @Test - void testPublishBodyWithHeaders() throws InterruptedException { + void testPublishHeadersBody() throws InterruptedException { // when testing.runWithSpan("parent", () -> connection.publish("sub", new Headers(), new byte[] {0})); @@ -94,7 +94,7 @@ void testPublishBodyWithHeaders() throws InterruptedException { } @Test - void testPublishBodyReplyToNoHeaders() throws InterruptedException { + void testPublishReplyToBody() throws InterruptedException { // when testing.runWithSpan("parent", () -> connection.publish("sub", "rt", new byte[] {0})); @@ -104,7 +104,7 @@ void testPublishBodyReplyToNoHeaders() throws InterruptedException { } @Test - void testPublishBodyReplyToWithHeaders() throws InterruptedException { + void testPublishReplyToHeadersBody() throws InterruptedException { // when testing.runWithSpan( "parent", () -> connection.publish("sub", "rt", new Headers(), new byte[] {0})); @@ -115,7 +115,7 @@ void testPublishBodyReplyToWithHeaders() throws InterruptedException { } @Test - void testPublishMessageNoHeaders() throws InterruptedException { + void testPublishMessage() throws InterruptedException { NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java index fa472a19a6d2..60559d5f4b5d 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java @@ -90,71 +90,53 @@ void testRequestTimeout() throws InterruptedException { } @Test - void testRequestFutureTimeoutBodyNoHeaders() throws InterruptedException { - // when - testing.runWithSpan( - "parent", - () -> connection.requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); - - // then - assertCancellationPublishSpan(); - assertNoHeaders(); - } - - @Test - void testRequestFutureTimeoutBodyWithHeaders() throws InterruptedException { - // when - testing.runWithSpan( - "parent", - () -> - connection.requestWithTimeout( - "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); - - // then - assertCancellationPublishSpan(); - assertTraceparentHeader(); - } - - @Test - void testRequestFutureTimeoutMessageNoHeaders() throws InterruptedException { + void testRequestBody() throws InterruptedException { // given - NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + Dispatcher dispatcher = + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); // when testing.runWithSpan( - "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); + "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); + connection.closeDispatcher(dispatcher); // then - assertCancellationPublishSpan(); + assertPublishSpan(); assertNoHeaders(); } @Test - void testRequestFutureTimeoutMessageWithHeaders() throws InterruptedException { + void testRequestHeadersBody() throws InterruptedException { // given - NatsMessage message = - NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + Dispatcher dispatcher = + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); // when testing.runWithSpan( - "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); + "parent", + () -> connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + connection.closeDispatcher(dispatcher); // then - assertCancellationPublishSpan(); + assertPublishSpan(); assertTraceparentHeader(); } @Test - void testRequestBodyNoHeaders() throws InterruptedException { + void testRequestMessage() throws InterruptedException { // given Dispatcher dispatcher = connection .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) .subscribe("sub"); + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when - testing.runWithSpan( - "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); + testing.runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); connection.closeDispatcher(dispatcher); // then @@ -163,25 +145,26 @@ void testRequestBodyNoHeaders() throws InterruptedException { } @Test - void testRequestFutureBodyNoHeaders() throws InterruptedException { + void testRequestMessageHeaders() throws InterruptedException { // given Dispatcher dispatcher = connection .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) .subscribe("sub"); + NatsMessage message = + NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); // when - testing - .runWithSpan("parent", () -> connection.request("sub", new byte[] {0})) - .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); + testing.runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); + connection.closeDispatcher(dispatcher); // then assertPublishSpan(); - assertNoHeaders(); + assertTraceparentHeader(); } @Test - void testRequestBodyWithHeaders() throws InterruptedException { + void testRequestFutureBody() throws InterruptedException { // given Dispatcher dispatcher = connection @@ -189,18 +172,17 @@ void testRequestBodyWithHeaders() throws InterruptedException { .subscribe("sub"); // when - testing.runWithSpan( - "parent", - () -> connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); - connection.closeDispatcher(dispatcher); + testing + .runWithSpan("parent", () -> connection.request("sub", new byte[] {0})) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then assertPublishSpan(); - assertTraceparentHeader(); + assertNoHeaders(); } @Test - void testRequestFutureBodyWithHeaders() throws InterruptedException { + void testRequestFutureHeadersBody() throws InterruptedException { // given Dispatcher dispatcher = connection @@ -218,7 +200,7 @@ void testRequestFutureBodyWithHeaders() throws InterruptedException { } @Test - void testRequestMessageNoHeaders() throws InterruptedException { + void testRequestFutureMessage() throws InterruptedException { // given Dispatcher dispatcher = connection @@ -227,8 +209,9 @@ void testRequestMessageNoHeaders() throws InterruptedException { NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when - testing.runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); - connection.closeDispatcher(dispatcher); + testing + .runWithSpan("parent", () -> connection.request(message)) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then assertPublishSpan(); @@ -236,13 +219,14 @@ void testRequestMessageNoHeaders() throws InterruptedException { } @Test - void testRequestFutureMessageNoHeaders() throws InterruptedException { + void testRequestFutureMessageHeaders() throws InterruptedException { // given Dispatcher dispatcher = connection .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) .subscribe("sub"); - NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + NatsMessage message = + NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); // when testing @@ -251,45 +235,61 @@ void testRequestFutureMessageNoHeaders() throws InterruptedException { // then assertPublishSpan(); + assertTraceparentHeader(); + } + + @Test + void testRequestTimeoutFutureBody() throws InterruptedException { + // when + testing.runWithSpan( + "parent", + () -> connection.requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); + + // then + assertCancellationPublishSpan(); assertNoHeaders(); } @Test - void testRequestMessageWithHeaders() throws InterruptedException { + void testRequestTimeoutFutureHeadersBody() throws InterruptedException { + // when + testing.runWithSpan( + "parent", + () -> + connection.requestWithTimeout( + "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + + // then + assertCancellationPublishSpan(); + assertTraceparentHeader(); + } + + @Test + void testRequestTimeoutFutureMessage() throws InterruptedException { // given - Dispatcher dispatcher = - connection - .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) - .subscribe("sub"); - NatsMessage message = - NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when - testing.runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); - connection.closeDispatcher(dispatcher); + testing.runWithSpan( + "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); // then - assertPublishSpan(); - assertTraceparentHeader(); + assertCancellationPublishSpan(); + assertNoHeaders(); } @Test - void testRequestFutureMessageWithHeaders() throws InterruptedException { + void testRequestTimeoutFutureMessageHeaders() throws InterruptedException { // given - Dispatcher dispatcher = - connection - .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) - .subscribe("sub"); NatsMessage message = NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); // when - testing - .runWithSpan("parent", () -> connection.request(message)) - .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); + testing.runWithSpan( + "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); // then - assertPublishSpan(); + assertCancellationPublishSpan(); assertTraceparentHeader(); } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscribeTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscriptionTest.java similarity index 98% rename from instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscribeTest.java rename to instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscriptionTest.java index 48fe2edb7a4b..828d8dcbca73 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscribeTest.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscriptionTest.java @@ -33,7 +33,7 @@ import org.testcontainers.utility.DockerImageName; @SuppressWarnings("deprecation") // using deprecated semconv -class NatsInstrumentationSubscribeTest { +class NatsInstrumentationSubscriptionTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); @@ -98,7 +98,7 @@ void testSubscribeTimeout() throws InterruptedException { } @Test - void testSubscribeNoLink() throws InterruptedException { + void testNextMessage() throws InterruptedException { // given Headers headers = new Headers(new Headers(), /* readOnly= */ true); @@ -143,7 +143,7 @@ void testSubscribeNoLink() throws InterruptedException { @SuppressWarnings("PreferJavaTimeOverload") @Test - void testSubscribeWithLink() throws InterruptedException { + void testNextMessageLink() throws InterruptedException { // given String linkTraceId = "0af7651916cd43dd8448eb211c80319c"; Headers headers = diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java index 2171c8302ecc..6eb0579cdaf4 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java @@ -28,7 +28,6 @@ import io.nats.client.Subscription; import io.nats.client.api.ServerInfo; import io.nats.client.impl.Headers; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -65,26 +64,28 @@ public OpenTelemetryConnection( @Override public void publish(String subject, byte[] body) { - wrapPublish(NatsRequest.create(this, subject, body), () -> delegate.publish(subject, body)); + wrapPublish( + NatsRequest.create(this, null, subject, null, body), () -> delegate.publish(subject, body)); } @Override public void publish(String subject, Headers headers, byte[] body) { wrapPublish( - NatsRequest.create(this, subject, headers, body), + NatsRequest.create(this, null, subject, headers, body), () -> delegate.publish(subject, headers, body)); } @Override public void publish(String subject, String replyTo, byte[] body) { wrapPublish( - NatsRequest.create(this, subject, body), () -> delegate.publish(subject, replyTo, body)); + NatsRequest.create(this, replyTo, subject, null, body), + () -> delegate.publish(subject, replyTo, body)); } @Override public void publish(String subject, String replyTo, Headers headers, byte[] body) { wrapPublish( - NatsRequest.create(this, subject, headers, body), + NatsRequest.create(this, replyTo, subject, headers, body), () -> delegate.publish(subject, replyTo, headers, body)); } @@ -97,14 +98,15 @@ public void publish(Message message) { public Message request(String subject, byte[] body, Duration timeout) throws InterruptedException { return wrapRequest( - NatsRequest.create(this, subject, body), () -> delegate.request(subject, body, timeout)); + NatsRequest.create(this, null, subject, null, body), + () -> delegate.request(subject, body, timeout)); } @Override public Message request(String subject, Headers headers, byte[] body, Duration timeout) throws InterruptedException { return wrapRequest( - NatsRequest.create(this, subject, headers, body), + NatsRequest.create(this, null, subject, headers, body), () -> delegate.request(subject, headers, body, timeout)); } @@ -116,13 +118,13 @@ public Message request(Message message, Duration timeout) throws InterruptedExce @Override public CompletableFuture request(String subject, byte[] body) { return wrapRequest( - NatsRequest.create(this, subject, body), () -> delegate.request(subject, body)); + NatsRequest.create(this, null, subject, null, body), () -> delegate.request(subject, body)); } @Override public CompletableFuture request(String subject, Headers headers, byte[] body) { return wrapRequest( - NatsRequest.create(this, subject, headers, body), + NatsRequest.create(this, null, subject, headers, body), () -> delegate.request(subject, headers, body)); } @@ -135,7 +137,7 @@ public CompletableFuture request(Message message) { public CompletableFuture requestWithTimeout( String subject, byte[] body, Duration timeout) { return wrapRequest( - NatsRequest.create(this, subject, body), + NatsRequest.create(this, null, subject, null, body), () -> delegate.requestWithTimeout(subject, body, timeout)); } @@ -143,7 +145,7 @@ public CompletableFuture requestWithTimeout( public CompletableFuture requestWithTimeout( String subject, Headers headers, byte[] body, Duration timeout) { return wrapRequest( - NatsRequest.create(this, subject, headers, body), + NatsRequest.create(this, null, subject, headers, body), () -> delegate.requestWithTimeout(subject, headers, body, timeout)); } @@ -156,13 +158,13 @@ public CompletableFuture requestWithTimeout(Message message, Duration t @Override public Subscription subscribe(String subject) { return new OpenTelemetrySubscription( - this, delegate.subscribe(subject), this.consumerReceiveInstrumenter); + this, delegate.subscribe(subject), consumerReceiveInstrumenter); } @Override public Subscription subscribe(String subject, String queueName) { return new OpenTelemetrySubscription( - this, delegate.subscribe(subject, queueName), this.consumerReceiveInstrumenter); + this, delegate.subscribe(subject, queueName), consumerReceiveInstrumenter); } @Override @@ -188,6 +190,11 @@ public Dispatcher createDispatcher() { @Override public void closeDispatcher(Dispatcher dispatcher) { + if (dispatcher instanceof OpenTelemetryDispatcher) { + delegate.closeDispatcher(((OpenTelemetryDispatcher) dispatcher).getDelegate()); + return; + } + delegate.closeDispatcher(dispatcher); } @@ -384,8 +391,7 @@ public ObjectStoreManagement objectStoreManagement(ObjectStoreOptions objectStor private void wrapPublish(NatsRequest natsRequest, Runnable publish) { Context parentContext = Context.current(); - if (!Span.fromContext(parentContext).getSpanContext().isValid() - || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { + if (!producerInstrumenter.shouldStart(parentContext, natsRequest)) { publish.run(); return; } @@ -403,8 +409,7 @@ private Message wrapRequest( throws InterruptedException { Context parentContext = Context.current(); - if (!Span.fromContext(parentContext).getSpanContext().isValid() - || !clientInstrumenter.shouldStart(parentContext, natsRequest)) { + if (!clientInstrumenter.shouldStart(parentContext, natsRequest)) { return request.call(); } @@ -431,25 +436,22 @@ private CompletableFuture wrapRequest( NatsRequest natsRequest, Supplier> request) { Context parentContext = Context.current(); - if (!Span.fromContext(parentContext).getSpanContext().isValid() - || !clientInstrumenter.shouldStart(parentContext, natsRequest)) { + if (!clientInstrumenter.shouldStart(parentContext, natsRequest)) { return request.get(); } Context context = clientInstrumenter.start(parentContext, natsRequest); - try (Scope ignored = context.makeCurrent()) { - return request - .get() - .whenComplete( - (message, exception) -> { - if (message != null) { - NatsRequest response = NatsRequest.create(this, message); - clientInstrumenter.end(context, natsRequest, response, exception); - } else { - clientInstrumenter.end(context, natsRequest, null, exception); - } - }); - } + return request + .get() + .whenComplete( + (message, exception) -> { + if (message != null) { + NatsRequest response = NatsRequest.create(this, message); + clientInstrumenter.end(context, natsRequest, response, exception); + } else { + clientInstrumenter.end(context, natsRequest, null, exception); + } + }); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java index 799518662c9c..82eedbacf550 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java @@ -78,7 +78,12 @@ public Dispatcher unsubscribe(String subject) { @Override public Dispatcher unsubscribe(Subscription subscription) { - delegate.unsubscribe(subscription); + if (subscription instanceof OpenTelemetrySubscription) { + delegate.unsubscribe(((OpenTelemetrySubscription) subscription).getDelegate()); + } else { + delegate.unsubscribe(subscription); + } + // from javadoc - Returns: The Dispatcher, so calls can be chained. return this; } @@ -92,7 +97,12 @@ public Dispatcher unsubscribe(String subject, int after) { @Override public Dispatcher unsubscribe(Subscription subscription, int after) { - delegate.unsubscribe(subscription, after); + if (subscription instanceof OpenTelemetrySubscription) { + delegate.unsubscribe(((OpenTelemetrySubscription) subscription).getDelegate(), after); + } else { + delegate.unsubscribe(subscription, after); + } + // from javadoc - Returns: The Dispatcher, so calls can be chained. return this; } @@ -146,4 +156,8 @@ public boolean isActive() { public CompletableFuture drain(Duration timeout) throws InterruptedException { return delegate.drain(timeout); } + + protected Dispatcher getDelegate() { + return delegate; + } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java index 14dc1ce7e120..ae1811da8df0 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java @@ -8,8 +8,8 @@ import io.nats.client.Connection; import io.nats.client.Message; import io.nats.client.MessageHandler; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; import io.opentelemetry.instrumentation.api.internal.Timer; @@ -40,8 +40,7 @@ public void onMessage(Message message) throws InterruptedException { Context parentContext = Context.current(); NatsRequest natsRequest = NatsRequest.create(connection, message); - if (!Span.fromContext(parentContext).getSpanContext().isValid() - || !consumerReceiveInstrumenter.shouldStart(parentContext, natsRequest)) { + if (!consumerReceiveInstrumenter.shouldStart(parentContext, natsRequest)) { delegate.onMessage(message); return; } @@ -62,13 +61,15 @@ public void onMessage(Message message) throws InterruptedException { } Context processContext = consumerProcessInstrumenter.start(receiveContext, natsRequest); - try { + InterruptedException exception = null; + + try (Scope ignored = processContext.makeCurrent()) { delegate.onMessage(message); } catch (InterruptedException e) { - consumerProcessInstrumenter.end(processContext, natsRequest, null, e); + exception = e; throw e; } finally { - consumerProcessInstrumenter.end(processContext, natsRequest, null, null); + consumerProcessInstrumenter.end(processContext, natsRequest, null, exception); } } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java index fa8e7799ec68..7270f4e81e70 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java @@ -9,7 +9,6 @@ import io.nats.client.Dispatcher; import io.nats.client.Message; import io.nats.client.Subscription; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; @@ -123,6 +122,10 @@ public CompletableFuture drain(Duration timeout) throws InterruptedExce return delegate.drain(timeout); } + protected Subscription getDelegate() { + return delegate; + } + private Message wrapNextMessage( ThrowingSupplier2 nextMessage) throws InterruptedException { @@ -131,7 +134,7 @@ private Message wrapNextMessage( Context parentContext = Context.current(); TimeoutException timeout = null; - NatsRequest natsRequest = NatsRequest.create(connection, getSubject()); + NatsRequest natsRequest = NatsRequest.create(connection, null, getSubject(), null, null); if (message == null) { timeout = new TimeoutException("Timed out waiting for message"); @@ -139,8 +142,7 @@ private Message wrapNextMessage( natsRequest = NatsRequest.create(connection, message); } - if (!Span.fromContext(parentContext).getSpanContext().isValid() - || !consumerReceiveInstrumenter.shouldStart(parentContext, natsRequest)) { + if (!consumerReceiveInstrumenter.shouldStart(parentContext, natsRequest)) { return message; } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java index 26ecb9bf8e54..e9d226343aa5 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java @@ -69,6 +69,8 @@ public static Instrumenter createConsumerReceiveInstrumenter( new PropagatorBasedSpanLinksExtractor<>( openTelemetry.getPropagators().getTextMapPropagator(), NatsRequestTextMapGetter.INSTANCE)) + .addContextCustomizer( + new NatsRequestContextCustomizer(openTelemetry.getPropagators().getTextMapPropagator())) .buildConsumerInstrumenter(NatsRequestTextMapGetter.INSTANCE); } @@ -77,11 +79,7 @@ public static Instrumenter createConsumerProcessInstrumenter( return Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, CONSUMER_PROCESS_SPAN_NAME_EXTRACTOR) .addAttributesExtractor(CONSUMER_PROCESS_ATTRIBUTES_EXTRACTOR) - .addSpanLinksExtractor( - new PropagatorBasedSpanLinksExtractor<>( - openTelemetry.getPropagators().getTextMapPropagator(), - NatsRequestTextMapGetter.INSTANCE)) - .buildInstrumenter(SpanKindExtractor.alwaysConsumer()); + .buildInstrumenter(SpanKindExtractor.alwaysInternal()); } public static Instrumenter createClientInstrumenter( @@ -89,10 +87,6 @@ public static Instrumenter createClientInstrumenter( return Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, PRODUCER_SPAN_NAME_EXTRACTOR) .addAttributesExtractor(CLIENT_ATTRIBUTES_EXTRACTOR) - .addSpanLinksExtractor( - new PropagatorBasedSpanLinksExtractor<>( - openTelemetry.getPropagators().getTextMapPropagator(), - NatsRequestTextMapGetter.INSTANCE)) .buildClientInstrumenter(NatsRequestTextMapSetter.INSTANCE); } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java index 217b9ec327c6..dc8bcac8f358 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java @@ -20,28 +20,24 @@ @AutoValue public abstract class NatsRequest { - public static NatsRequest create(Connection connection, String subject) { - return create(connection, subject, null); - } - - public static NatsRequest create(Connection connection, String subject, byte[] data) { - return create(connection, subject, null, data); - } - public static NatsRequest create(Connection connection, Message message) { return create( message.getConnection() == null ? connection : message.getConnection(), + message.getReplyTo(), message.getSubject(), message.getHeaders(), message.getData()); } public static NatsRequest create( - Connection connection, String subject, Headers headers, byte[] data) { + Connection connection, String replyTo, String subject, Headers headers, byte[] data) { return new AutoValue_NatsRequest( - connection.getServerInfo().getClientId(), subject, headers, getDataSize(data)); + replyTo, connection.getServerInfo().getClientId(), subject, headers, getDataSize(data)); } + @Nullable + public abstract String getReplyTo(); + public abstract int getClientId(); public abstract String getSubject(); diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestContextCustomizer.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestContextCustomizer.java new file mode 100644 index 000000000000..e0ed0639b1d6 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestContextCustomizer.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21.internal; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; + +/** + * This * class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time.", or "This class is internal and experimental. Its APIs are unstable and can change at + * any time. Its APIs (or a version of them) may be promoted to the public stable API in the future, + * but no guarantees are made. + */ +public class NatsRequestContextCustomizer implements ContextCustomizer { + private final TextMapPropagator propagator; + + public NatsRequestContextCustomizer(TextMapPropagator propagator) { + this.propagator = propagator; + } + + @Override + /* In case of a `connection.request`, the replyTo will be set to an internal buffer prefixed + * _INBOX.*. In this case we can consider a request-response pattern and a synchronous/short timed + * window. This Customize will set the span as CHILD_OF and inside the same trace + */ + public Context onStart( + Context parentContext, NatsRequest natsRequest, Attributes startAttributes) { + if (!Span.fromContext(parentContext).getSpanContext().isValid() + && natsRequest.getReplyTo() != null && natsRequest.getReplyTo().startsWith("_INBOX.")) { + return propagator.extract(parentContext, natsRequest, NatsRequestTextMapGetter.INSTANCE); + } + + return parentContext; + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryDispatcherTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryDispatcherTest.java index 743fbb18c6d5..c696d860df64 100644 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryDispatcherTest.java +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryDispatcherTest.java @@ -11,14 +11,18 @@ import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import io.nats.client.Dispatcher; import io.nats.client.MessageHandler; +import io.nats.client.Subscription; import io.nats.client.impl.Headers; import io.nats.client.impl.NatsMessage; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.TraceAssert; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -33,153 +37,87 @@ class NatsTelemetryDispatcherTest { private final MessageHandler handler = msg -> {}; @Test - void testDispatcherDefaultHandler() { + void testSubscribeDefaultHandler() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); - connection.createDispatcher(handler).subscribe("sub"); - connection.createDispatcher(handler).subscribe("sub", "queue"); + Dispatcher d1 = connection.createDispatcher(handler); + d1.subscribe("sub1"); + d1.subscribe("sub2", "queue"); + connection.createDispatcher(handler).subscribe("sub3"); // when - testing.runWithSpan( - "parent", - () -> testConnection.deliver(NatsMessage.builder().subject("sub").data("x").build())); + testConnection.deliver(NatsMessage.builder().subject("sub1").data("x").build()); + testConnection.deliver(NatsMessage.builder().subject("sub2").data("x").build()); + testConnection.deliver(NatsMessage.builder().subject("sub3").data("x").build()); // then testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> // first dispatcher - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), - span -> - span.hasName("sub process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "process"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), - span -> // second dispatcher - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), - span -> - span.hasName("sub process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(3)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "process"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + trace -> assertReceiveProcessSpans(trace, "sub1"), + trace -> assertReceiveProcessSpans(trace, "sub2"), + trace -> assertReceiveProcessSpans(trace, "sub3")); + + assertThatNoException() + .isThrownBy( + () -> { + d1.unsubscribe("sub1"); + d1.unsubscribe("sub2", 1); + }); } @Test - void testDispatcherHandler() { + void testSubscribeCustomHandler() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); - connection.createDispatcher().subscribe("sub", handler); - connection.createDispatcher().subscribe("sub", "queue", handler); + Dispatcher dispatcher = connection.createDispatcher(); + Subscription s1 = dispatcher.subscribe("sub1", handler); + Subscription s2 = dispatcher.subscribe("sub2", "queue", handler); + Subscription s3 = connection.createDispatcher(handler).subscribe("sub3", handler); // when - testing.runWithSpan( - "parent", - () -> testConnection.deliver(NatsMessage.builder().subject("sub").data("x").build())); + testConnection.deliver(NatsMessage.builder().subject("sub1").data("x").build()); + testConnection.deliver(NatsMessage.builder().subject("sub2").data("x").build()); + testConnection.deliver(NatsMessage.builder().subject("sub3").data("x").build()); // then testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> // first dispatcher - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), - span -> - span.hasName("sub process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "process"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), - span -> // second dispatcher - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), - span -> - span.hasName("sub process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(3)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "process"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + trace -> assertReceiveProcessSpans(trace, "sub1"), + trace -> assertReceiveProcessSpans(trace, "sub2"), + trace -> assertReceiveProcessSpans(trace, "sub3")); + + // and + assertThatNoException() + .isThrownBy( + () -> { + s1.unsubscribe(); + dispatcher.unsubscribe(s2); + s3.unsubscribe(1); + }); } - @SuppressWarnings("PreferJavaTimeOverload") @Test - void testDispatcherWithLink() { + void testSpanLink() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); connection.createDispatcher(handler).subscribe("sub"); + String linkTraceId = "0af7651916cd43dd8448eb211c80319c"; Headers headers = new Headers( new Headers().put("traceparent", "00-" + linkTraceId + "-b7ad6b7169203331-01"), true); // when - testing.runWithSpan( - "parent", - () -> - testConnection.deliver( - NatsMessage.builder().subject("sub").data("x").headers(headers).build())); + testConnection.deliver(NatsMessage.builder().subject("sub").data("x").headers(headers).build()); // then testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), span -> span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) + .hasNoParent() .hasLinksSatisfying( links -> assertThat(links) @@ -187,22 +125,31 @@ void testDispatcherWithLink() { .satisfies( link -> assertThat(link.getSpanContext().getTraceId()) - .isEqualTo(linkTraceId))) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), - span -> - span.hasName("sub process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "process"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); + .isEqualTo(linkTraceId))), + span -> span.hasName("sub process").hasParent(trace.getSpan(0)))); + } + + private static void assertReceiveProcessSpans(TraceAssert trace, String subject) { + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(subject + " receive") + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, subject), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), + span -> + span.hasName(subject + " process") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "process"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, subject), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), "1"))); } } diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java index 89e0d70c6f3a..86374ed466f0 100644 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java @@ -32,7 +32,7 @@ class NatsTelemetryPublishTest { static final NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); @Test - void testPublishBodyNoHeaders() { + void testPublishBody() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); @@ -46,7 +46,7 @@ void testPublishBodyNoHeaders() { } @Test - void testPublishBodyWithHeaders() { + void testPublishHeadersBody() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); @@ -60,7 +60,7 @@ void testPublishBodyWithHeaders() { } @Test - void testPublishBodyReplyToNoHeaders() { + void testPublishReplyToBody() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); @@ -74,7 +74,7 @@ void testPublishBodyReplyToNoHeaders() { } @Test - void testPublishBodyReplyToWithHeaders() { + void testPublishReplyToHeadersBody() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); @@ -89,7 +89,7 @@ void testPublishBodyReplyToWithHeaders() { } @Test - void testPublishMessageNoHeaders() { + void testPublishMessage() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); @@ -104,7 +104,7 @@ void testPublishMessageNoHeaders() { } @Test - void testPublishMessageWithHeaders() { + void testPublishMessageHeaders() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryRequestTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryRequestTest.java index 301f18f37fe0..e04691ecc774 100644 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryRequestTest.java +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryRequestTest.java @@ -50,7 +50,7 @@ void testRequestTimeout() throws InterruptedException { } @Test - void testRequestFutureTimeout() { + void testRequestBody() throws InterruptedException { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); @@ -58,16 +58,19 @@ void testRequestFutureTimeout() { // when testing.runWithSpan( "parent", - () -> connection.requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); + () -> { + testConnection.requestResponseMessages.offer( + NatsMessage.builder().subject("sub").build()); + connection.request("sub", new byte[] {0}, Duration.ofSeconds(1)); + }); // then assertPublishSpan(); - assertTimeoutPublishSpan(); assertNoHeaders(testConnection); } @Test - void testRequestBodyNoHeaders() throws InterruptedException { + void testRequestHeadersBody() throws InterruptedException { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); @@ -78,19 +81,20 @@ void testRequestBodyNoHeaders() throws InterruptedException { () -> { testConnection.requestResponseMessages.offer( NatsMessage.builder().subject("sub").build()); - connection.request("sub", new byte[] {0}, Duration.ofSeconds(1)); + connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1)); }); // then assertPublishSpan(); - assertNoHeaders(testConnection); + assertTraceparentHeader(testConnection); } @Test - void testRequestFutureBodyNoHeaders() { + void testRequestMessage() throws InterruptedException { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when testing.runWithSpan( @@ -98,7 +102,7 @@ void testRequestFutureBodyNoHeaders() { () -> { testConnection.requestResponseMessages.offer( NatsMessage.builder().subject("sub").build()); - connection.request("sub", new byte[] {0}); + connection.request(message, Duration.ofSeconds(1)); }); // then @@ -107,10 +111,12 @@ void testRequestFutureBodyNoHeaders() { } @Test - void testRequestBodyWithHeaders() throws InterruptedException { + void testRequestMessageHeaders() throws InterruptedException { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); + NatsMessage message = + NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); // when testing.runWithSpan( @@ -118,7 +124,7 @@ void testRequestBodyWithHeaders() throws InterruptedException { () -> { testConnection.requestResponseMessages.offer( NatsMessage.builder().subject("sub").build()); - connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1)); + connection.request(message, Duration.ofSeconds(1)); }); // then @@ -127,7 +133,7 @@ void testRequestBodyWithHeaders() throws InterruptedException { } @Test - void testRequestFutureBodyWithHeaders() { + void testRequestFutureTimeout() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); @@ -135,23 +141,19 @@ void testRequestFutureBodyWithHeaders() { // when testing.runWithSpan( "parent", - () -> { - testConnection.requestResponseMessages.offer( - NatsMessage.builder().subject("sub").build()); - connection.request("sub", new Headers(), new byte[] {0}); - }); + () -> connection.requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); // then assertPublishSpan(); - assertTraceparentHeader(testConnection); + assertTimeoutPublishSpan(); + assertNoHeaders(testConnection); } @Test - void testRequestMessageNoHeaders() throws InterruptedException { + void testRequestFutureBody() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); - NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when testing.runWithSpan( @@ -159,7 +161,7 @@ void testRequestMessageNoHeaders() throws InterruptedException { () -> { testConnection.requestResponseMessages.offer( NatsMessage.builder().subject("sub").build()); - connection.request(message, Duration.ofSeconds(1)); + connection.request("sub", new byte[] {0}); }); // then @@ -168,11 +170,10 @@ void testRequestMessageNoHeaders() throws InterruptedException { } @Test - void testRequestFutureMessageNoHeaders() { + void testRequestFutureHeadersBody() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); - NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when testing.runWithSpan( @@ -180,21 +181,20 @@ void testRequestFutureMessageNoHeaders() { () -> { testConnection.requestResponseMessages.offer( NatsMessage.builder().subject("sub").build()); - connection.request(message); + connection.request("sub", new Headers(), new byte[] {0}); }); // then assertPublishSpan(); - assertNoHeaders(testConnection); + assertTraceparentHeader(testConnection); } @Test - void testRequestMessageWithHeaders() throws InterruptedException { + void testRequestFutureMessage() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); - NatsMessage message = - NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when testing.runWithSpan( @@ -202,16 +202,16 @@ void testRequestMessageWithHeaders() throws InterruptedException { () -> { testConnection.requestResponseMessages.offer( NatsMessage.builder().subject("sub").build()); - connection.request(message, Duration.ofSeconds(1)); + connection.request(message); }); // then assertPublishSpan(); - assertTraceparentHeader(testConnection); + assertNoHeaders(testConnection); } @Test - void testRequestFutureMessageWithHeaders() { + void testRequestFutureMessageHeaders() { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscribeTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscriptionTest.java similarity index 96% rename from instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscribeTest.java rename to instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscriptionTest.java index ff7ede2b4225..8a28240e6861 100644 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscribeTest.java +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscriptionTest.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; @SuppressWarnings("deprecation") // using deprecated semconv -class NatsTelemetrySubscribeTest { +class NatsTelemetrySubscriptionTest { @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); @@ -33,7 +33,7 @@ class NatsTelemetrySubscribeTest { static final NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); @Test - void testSubscribeTimeout() throws InterruptedException { + void testNextMessageTimeout() throws InterruptedException { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); @@ -61,7 +61,7 @@ void testSubscribeTimeout() throws InterruptedException { } @Test - void testSubscribeNoLink() throws InterruptedException { + void testNextMessage() throws InterruptedException { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); @@ -90,7 +90,7 @@ void testSubscribeNoLink() throws InterruptedException { @SuppressWarnings("PreferJavaTimeOverload") @Test - void testSubscribeWithLink() throws InterruptedException { + void testNextMessageLink() throws InterruptedException { // given TestConnection testConnection = new TestConnection(); OpenTelemetryConnection connection = telemetry.wrap(testConnection); diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java index 026825c2d6c8..4359794887b7 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java @@ -53,7 +53,7 @@ public class TestConnection implements Connection { public final Queue requestResponseMessages = new ConcurrentLinkedQueue<>(); public void deliver(Message message) { - subscriptions.forEach(subscription -> subscription.deliver(message)); + subscriptions.forEach(subscription -> subscription.deliver(message, null)); dispatchers.forEach(dispatcher -> dispatcher.deliver(message)); } diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java index 9458fd872963..fb30f4d832d1 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java @@ -43,14 +43,7 @@ public void deliver(Message message) { } } }); - subscriptions.forEach( - (subscription, handler) -> { - subscription.deliver(message); - try { - handler.onMessage(message); - } catch (InterruptedException ignored) { - } - }); + subscriptions.forEach((subscription, handler) -> subscription.deliver(message, handler)); } @Override @@ -84,30 +77,38 @@ public Subscription subscribe(String subject, String queue, MessageHandler handl @Override public Dispatcher unsubscribe(String subject) { - subjects.remove(subject); - return this; + if (subjects.contains(subject)) { + subjects.remove(subject); + return this; + } + throw new IllegalStateException("Cannot unsubscribe to " + subject); } @Override public Dispatcher unsubscribe(Subscription subscription) { if (subscription instanceof TestSubscription) { subscriptions.remove(subscription); + return this; } - return this; + throw new IllegalArgumentException("Unexpected subscription: " + subscription); } @Override public Dispatcher unsubscribe(String subject, int after) { - subjects.remove(subject); - return this; + if (subjects.contains(subject)) { + subjects.remove(subject); + return this; + } + throw new IllegalStateException("Cannot unsubscribe to " + subject); } @Override public Dispatcher unsubscribe(Subscription subscription, int after) { if (subscription instanceof TestSubscription) { subscriptions.remove(subscription); + return this; } - return this; + throw new IllegalArgumentException("Unexpected subscription: " + subscription); } @Override diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java index 0450d8a9ad43..b3efcd9ee99b 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java @@ -7,6 +7,7 @@ import io.nats.client.Dispatcher; import io.nats.client.Message; +import io.nats.client.MessageHandler; import io.nats.client.Subscription; import java.time.Duration; import java.util.Queue; @@ -38,9 +39,16 @@ public TestSubscription(String subject, String queueName, Dispatcher dispatcher) this.dispatcher = dispatcher; } - public void deliver(Message message) { + @SuppressWarnings("EmptyCatch") + public void deliver(Message message, MessageHandler handler) { if (message.getSubject().equalsIgnoreCase(getSubject())) { messages.add(message); + if (handler != null) { + try { + handler.onMessage(message); + } catch (InterruptedException e) { + } + } } } @@ -85,16 +93,21 @@ public Message nextMessage(long timeoutMillis) @Override public void unsubscribe() { if (dispatcher != null) { - dispatcher.unsubscribe(subject); + dispatcher.unsubscribe(this); + return; } + throw new IllegalStateException( + "Subscriptions that belong to a dispatcher cannot be unsubscribed."); } @Override public Subscription unsubscribe(int after) { if (dispatcher != null) { - dispatcher.unsubscribe(subject, after); + dispatcher.unsubscribe(this, after); + return this; } - return this; + throw new IllegalStateException( + "Subscriptions that belong to a dispatcher cannot be unsubscribed."); } @Override From b02272ea75f3160bb8c9498fd345d312b98a86ea Mon Sep 17 00:00:00 2001 From: Alix Date: Sun, 8 Jun 2025 20:47:04 +0200 Subject: [PATCH 09/34] Instrument NATS agent 'dispatcher' methods --- .../ConnectionPublishInstrumentation.java | 14 --- .../ConnectionSubscribeInstrumentation.java | 4 +- .../v2_21/MessageHandlerInstrumentation.java | 91 ++++++++++++++ .../nats/v2_21/NatsInstrumentationModule.java | 3 +- .../nats/v2_21/NatsSingletons.java | 6 +- .../v2_21/SubscriptionInstrumentation.java | 6 +- .../v2_21/NatsInstrumentationRequestTest.java | 113 ++++++++++++++++-- .../v2_21/OpenTelemetryMessageHandler.java | 11 +- 8 files changed, 212 insertions(+), 36 deletions(-) create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java index 265ca7dfd3b7..d9b5ac58d5e8 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java @@ -98,8 +98,6 @@ public static void onEnter( @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void onExit( @Advice.Thrown Throwable throwable, - @Advice.Argument(0) String subject, - @Advice.Argument(1) byte[] body, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { @@ -138,9 +136,6 @@ public static void onEnter( @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void onExit( @Advice.Thrown Throwable throwable, - @Advice.Argument(0) String subject, - @Advice.Argument(1) Headers headers, - @Advice.Argument(2) byte[] body, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { @@ -179,9 +174,6 @@ public static void onEnter( @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void onExit( @Advice.Thrown Throwable throwable, - @Advice.Argument(0) String subject, - @Advice.Argument(1) String replyTo, - @Advice.Argument(2) byte[] body, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { @@ -221,10 +213,6 @@ public static void onEnter( @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void onExit( @Advice.Thrown Throwable throwable, - @Advice.Argument(0) String subject, - @Advice.Argument(1) String replyTo, - @Advice.Argument(2) Headers headers, - @Advice.Argument(3) byte[] body, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { @@ -261,8 +249,6 @@ public static void onEnter( @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void onExit( @Advice.Thrown Throwable throwable, - @Advice.This Connection connection, - @Advice.Argument(0) Message message, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java index a24719c94844..04936ec14345 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java @@ -41,7 +41,9 @@ public void transform(TypeTransformer transformer) { public static class SubscribeAdvice { @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void onExit( - @Advice.This Connection connection, @Advice.Return Subscription subscription) { + @Advice.This Connection connection, + @Advice.Return Subscription subscription + ) { NatsData.addSubscription(subscription, connection); } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java new file mode 100644 index 000000000000..33bab88a8e82 --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java @@ -0,0 +1,91 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.CONSUMER_PROCESS_INSTRUMENTER; +import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.CONSUMER_RECEIVE_INSTRUMENTER; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.nats.client.Message; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; +import io.opentelemetry.instrumentation.api.internal.Timer; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class MessageHandlerInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("io.nats.client.MessageHandler")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isPublic() + .and(named("onMessage")) + .and(takesArguments(1)) + .and(takesArgument(0, named("io.nats.client.Message"))), + MessageHandlerInstrumentation.class.getName() + "$OnMessageAdvice"); + } + + @SuppressWarnings("unused") + public static class OnMessageAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) Message message, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest + ) { + Timer timer = Timer.start(); + + Context parentContext = Context.current(); + natsRequest = NatsRequest.create(message.getConnection(), message); + + if (!CONSUMER_RECEIVE_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + return; + } + + Context receiveContext = InstrumenterUtil.startAndEnd(CONSUMER_RECEIVE_INSTRUMENTER, parentContext, natsRequest, null, + null, timer.startTime(), timer.now()); + + if (!CONSUMER_PROCESS_INSTRUMENTER.shouldStart(receiveContext, natsRequest)) { + return; + } + + otelContext = CONSUMER_PROCESS_INSTRUMENTER.start(receiveContext, natsRequest); + otelScope = otelContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Thrown Throwable throwable, + @Advice.Local("otelContext") Context otelContext, + @Advice.Local("otelScope") Scope otelScope, + @Advice.Local("natsRequest") NatsRequest natsRequest + ) { + if (otelScope == null) { + return; + } + + otelScope.close(); + CONSUMER_PROCESS_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + } + } + +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java index 7afef4544331..f195065add67 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java @@ -26,6 +26,7 @@ public List typeInstrumentations() { new ConnectionSubscribeInstrumentation(), new ConnectionPublishInstrumentation(), new ConnectionRequestInstrumentation(), - new SubscriptionInstrumentation()); + new SubscriptionInstrumentation(), + new MessageHandlerInstrumentation()); } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java index b37923157bd4..ac4a315c2772 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java @@ -6,6 +6,7 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_21; import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createClientInstrumenter; +import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createConsumerProcessInstrumenter; import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createConsumerReceiveInstrumenter; import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createProducerInstrumenter; @@ -18,9 +19,12 @@ public final class NatsSingletons { public static final Instrumenter PRODUCER_INSTRUMENTER = createProducerInstrumenter(GlobalOpenTelemetry.get()); - public static final Instrumenter CONSUMER_INSTRUMENTER = + public static final Instrumenter CONSUMER_RECEIVE_INSTRUMENTER = createConsumerReceiveInstrumenter(GlobalOpenTelemetry.get()); + public static final Instrumenter CONSUMER_PROCESS_INSTRUMENTER = + createConsumerProcessInstrumenter(GlobalOpenTelemetry.get()); + public static final Instrumenter CLIENT_INSTRUMENTER = createClientInstrumenter(GlobalOpenTelemetry.get()); diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java index e9f30a34d93d..c745769ea1c0 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java @@ -6,7 +6,7 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_21; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.CONSUMER_INSTRUMENTER; +import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.CONSUMER_RECEIVE_INSTRUMENTER; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; @@ -81,12 +81,12 @@ public static void onExit( natsRequest = NatsRequest.create(connection, message); } - if (!CONSUMER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + if (!CONSUMER_RECEIVE_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { return; } InstrumenterUtil.startAndEnd( - CONSUMER_INSTRUMENTER, + CONSUMER_RECEIVE_INSTRUMENTER, parentContext, natsRequest, null, diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java index 60559d5f4b5d..cb0905bb3b8a 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_21; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; @@ -27,6 +28,7 @@ import java.time.Duration; import java.util.concurrent.CancellationException; import java.util.concurrent.TimeoutException; +import org.assertj.core.api.Condition; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -122,7 +124,7 @@ void testRequestHeadersBody() throws InterruptedException { connection.closeDispatcher(dispatcher); // then - assertPublishSpan(); + assertPublishSpanSameTrace(); assertTraceparentHeader(); } @@ -159,7 +161,7 @@ void testRequestMessageHeaders() throws InterruptedException { connection.closeDispatcher(dispatcher); // then - assertPublishSpan(); + assertPublishSpanSameTrace(); assertTraceparentHeader(); } @@ -195,7 +197,7 @@ void testRequestFutureHeadersBody() throws InterruptedException { .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then - assertPublishSpan(); + assertPublishSpanSameTrace(); assertTraceparentHeader(); } @@ -234,7 +236,7 @@ void testRequestFutureMessageHeaders() throws InterruptedException { .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then - assertPublishSpan(); + assertPublishSpanSameTrace(); assertTraceparentHeader(); } @@ -309,9 +311,106 @@ private static void assertPublishSpan() { equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), equalTo( AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId)))), - // dispatcher publish - trace -> trace.hasSpansSatisfyingExactly(span -> span.hasKind(SpanKind.PRODUCER))); + String.valueOf(clientId))), + span -> span + .has(new Condition<>( + data -> data.getName().startsWith("_INBOX.") && data.getName() + .endsWith(" receive"), "Name condition")) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + satisfies(MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))), + span -> span + .has(new Condition<>( + data -> data.getName().startsWith("_INBOX.") && data.getName() + .endsWith(" process"), "Name condition")) + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(2)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "process"), + equalTo(MESSAGING_SYSTEM, "nats"), + satisfies(MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId)))), + // dispatcher receive, process, publish, not retesting all properties + trace -> trace.hasSpansSatisfyingExactly( + span -> span.hasName("sub receive").hasKind(SpanKind.CONSUMER).hasNoParent(), + span -> span.hasName("sub process").hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)), + span -> span + .has(new Condition<>( + data -> data.getName().startsWith("_INBOX.") && data.getName() + .endsWith(" publish"), "Name condition")) + .hasKind(SpanKind.PRODUCER).hasParent(trace.getSpan(1)) + )); + } + + private static void assertPublishSpanSameTrace() { + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "publish"), + equalTo(MESSAGING_SYSTEM, "nats"), + equalTo(MESSAGING_DESTINATION_NAME, "sub"), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))), + + // dispatcher receive, process, publish, not retesting all properties + span -> span.hasName("sub receive").hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)), + span -> span.hasName("sub process").hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(2)), + span -> span + .has(new Condition<>( + data -> data.getName().startsWith("_INBOX.") && data.getName() + .endsWith(" publish"), "Name condition")) + .hasKind(SpanKind.PRODUCER).hasParent(trace.getSpan(3)), + // end dispatcher + + span -> span + .has(new Condition<>( + data -> data.getName().startsWith("_INBOX.") && data.getName() + .endsWith(" receive"), "Name condition")) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + satisfies(MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))), + span -> span + .has(new Condition<>( + data -> data.getName().startsWith("_INBOX.") && data.getName() + .endsWith(" process"), "Name condition")) + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(5)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "process"), + equalTo(MESSAGING_SYSTEM, "nats"), + satisfies(MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))))); } private static void assertTimeoutPublishSpan() { diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java index ae1811da8df0..ff87a01ac4cb 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java @@ -45,15 +45,8 @@ public void onMessage(Message message) throws InterruptedException { return; } - Context receiveContext = - InstrumenterUtil.startAndEnd( - consumerReceiveInstrumenter, - parentContext, - natsRequest, - null, - null, - timer.startTime(), - timer.now()); + Context receiveContext = InstrumenterUtil.startAndEnd(consumerReceiveInstrumenter, + parentContext, natsRequest, null, null, timer.startTime(), timer.now()); if (!consumerProcessInstrumenter.shouldStart(receiveContext, natsRequest)) { delegate.onMessage(message); From 2f78f45d3da5df4aed3e9c3c31bcdfedc9da3642 Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:14:47 +0000 Subject: [PATCH 10/34] ./gradlew spotlessApply --- .../ConnectionSubscribeInstrumentation.java | 4 +- .../v2_21/MessageHandlerInstrumentation.java | 18 +- .../v2_21/NatsInstrumentationRequestTest.java | 180 ++++++++++-------- .../v2_21/OpenTelemetryMessageHandler.java | 11 +- .../NatsRequestContextCustomizer.java | 3 +- 5 files changed, 128 insertions(+), 88 deletions(-) diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java index 04936ec14345..a24719c94844 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java @@ -41,9 +41,7 @@ public void transform(TypeTransformer transformer) { public static class SubscribeAdvice { @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void onExit( - @Advice.This Connection connection, - @Advice.Return Subscription subscription - ) { + @Advice.This Connection connection, @Advice.Return Subscription subscription) { NatsData.addSubscription(subscription, connection); } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java index 33bab88a8e82..d5c9b1aea019 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java @@ -50,8 +50,7 @@ public static void onEnter( @Advice.Argument(0) Message message, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest - ) { + @Advice.Local("natsRequest") NatsRequest natsRequest) { Timer timer = Timer.start(); Context parentContext = Context.current(); @@ -61,8 +60,15 @@ public static void onEnter( return; } - Context receiveContext = InstrumenterUtil.startAndEnd(CONSUMER_RECEIVE_INSTRUMENTER, parentContext, natsRequest, null, - null, timer.startTime(), timer.now()); + Context receiveContext = + InstrumenterUtil.startAndEnd( + CONSUMER_RECEIVE_INSTRUMENTER, + parentContext, + natsRequest, + null, + null, + timer.startTime(), + timer.now()); if (!CONSUMER_PROCESS_INSTRUMENTER.shouldStart(receiveContext, natsRequest)) { return; @@ -77,8 +83,7 @@ public static void onExit( @Advice.Thrown Throwable throwable, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest - ) { + @Advice.Local("natsRequest") NatsRequest natsRequest) { if (otelScope == null) { return; } @@ -87,5 +92,4 @@ public static void onExit( CONSUMER_PROCESS_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); } } - } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java index cb0905bb3b8a..67df04c0550d 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java @@ -312,45 +312,59 @@ private static void assertPublishSpan() { equalTo( AttributeKey.stringKey("messaging.client_id"), String.valueOf(clientId))), - span -> span - .has(new Condition<>( - data -> data.getName().startsWith("_INBOX.") && data.getName() - .endsWith(" receive"), "Name condition")) - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - satisfies(MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))), - span -> span - .has(new Condition<>( - data -> data.getName().startsWith("_INBOX.") && data.getName() - .endsWith(" process"), "Name condition")) - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(2)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "process"), - equalTo(MESSAGING_SYSTEM, "nats"), - satisfies(MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId)))), + span -> + span.has( + new Condition<>( + data -> + data.getName().startsWith("_INBOX.") + && data.getName().endsWith(" receive"), + "Name condition")) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + satisfies( + MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))), + span -> + span.has( + new Condition<>( + data -> + data.getName().startsWith("_INBOX.") + && data.getName().endsWith(" process"), + "Name condition")) + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(2)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "process"), + equalTo(MESSAGING_SYSTEM, "nats"), + satisfies( + MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId)))), // dispatcher receive, process, publish, not retesting all properties - trace -> trace.hasSpansSatisfyingExactly( - span -> span.hasName("sub receive").hasKind(SpanKind.CONSUMER).hasNoParent(), - span -> span.hasName("sub process").hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)), - span -> span - .has(new Condition<>( - data -> data.getName().startsWith("_INBOX.") && data.getName() - .endsWith(" publish"), "Name condition")) - .hasKind(SpanKind.PRODUCER).hasParent(trace.getSpan(1)) - )); + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("sub receive").hasKind(SpanKind.CONSUMER).hasNoParent(), + span -> + span.hasName("sub process") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)), + span -> + span.has( + new Condition<>( + data -> + data.getName().startsWith("_INBOX.") + && data.getName().endsWith(" publish"), + "Name condition")) + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(1)))); } private static void assertPublishSpanSameTrace() { @@ -372,45 +386,61 @@ private static void assertPublishSpanSameTrace() { String.valueOf(clientId))), // dispatcher receive, process, publish, not retesting all properties - span -> span.hasName("sub receive").hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)), - span -> span.hasName("sub process").hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(2)), - span -> span - .has(new Condition<>( - data -> data.getName().startsWith("_INBOX.") && data.getName() - .endsWith(" publish"), "Name condition")) - .hasKind(SpanKind.PRODUCER).hasParent(trace.getSpan(3)), + span -> + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)), + span -> + span.hasName("sub process") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(2)), + span -> + span.has( + new Condition<>( + data -> + data.getName().startsWith("_INBOX.") + && data.getName().endsWith(" publish"), + "Name condition")) + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(3)), // end dispatcher - span -> span - .has(new Condition<>( - data -> data.getName().startsWith("_INBOX.") && data.getName() - .endsWith(" receive"), "Name condition")) - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - satisfies(MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))), - span -> span - .has(new Condition<>( - data -> data.getName().startsWith("_INBOX.") && data.getName() - .endsWith(" process"), "Name condition")) - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(5)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "process"), - equalTo(MESSAGING_SYSTEM, "nats"), - satisfies(MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))))); + span -> + span.has( + new Condition<>( + data -> + data.getName().startsWith("_INBOX.") + && data.getName().endsWith(" receive"), + "Name condition")) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "receive"), + equalTo(MESSAGING_SYSTEM, "nats"), + satisfies( + MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))), + span -> + span.has( + new Condition<>( + data -> + data.getName().startsWith("_INBOX.") + && data.getName().endsWith(" process"), + "Name condition")) + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(5)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_OPERATION, "process"), + equalTo(MESSAGING_SYSTEM, "nats"), + satisfies( + MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo( + AttributeKey.stringKey("messaging.client_id"), + String.valueOf(clientId))))); } private static void assertTimeoutPublishSpan() { diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java index ff87a01ac4cb..ae1811da8df0 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java @@ -45,8 +45,15 @@ public void onMessage(Message message) throws InterruptedException { return; } - Context receiveContext = InstrumenterUtil.startAndEnd(consumerReceiveInstrumenter, - parentContext, natsRequest, null, null, timer.startTime(), timer.now()); + Context receiveContext = + InstrumenterUtil.startAndEnd( + consumerReceiveInstrumenter, + parentContext, + natsRequest, + null, + null, + timer.startTime(), + timer.now()); if (!consumerProcessInstrumenter.shouldStart(receiveContext, natsRequest)) { delegate.onMessage(message); diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestContextCustomizer.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestContextCustomizer.java index e0ed0639b1d6..dad1bf6a8b7a 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestContextCustomizer.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestContextCustomizer.java @@ -32,7 +32,8 @@ public NatsRequestContextCustomizer(TextMapPropagator propagator) { public Context onStart( Context parentContext, NatsRequest natsRequest, Attributes startAttributes) { if (!Span.fromContext(parentContext).getSpanContext().isValid() - && natsRequest.getReplyTo() != null && natsRequest.getReplyTo().startsWith("_INBOX.")) { + && natsRequest.getReplyTo() != null + && natsRequest.getReplyTo().startsWith("_INBOX.")) { return propagator.extract(parentContext, natsRequest, NatsRequestTextMapGetter.INSTANCE); } From 487dffbdf9ab4db6c606dcd153bc5388bb6a5629 Mon Sep 17 00:00:00 2001 From: Alix Date: Sat, 21 Jun 2025 16:04:14 +0200 Subject: [PATCH 11/34] review & fixes --- docs/supported-libraries.md | 1 + .../nats/nats-2.21/javaagent/README.md | 20 + .../nats/nats-2.21/javaagent/build.gradle.kts | 16 + .../ConnectionRequestInstrumentation.java | 93 +-- .../ConnectionSubscribeInstrumentation.java | 48 -- .../nats/v2_21/DispatcherInstrumentation.java | 57 ++ .../v2_21/MessageHandlerInstrumentation.java | 26 +- .../nats/v2_21/NatsInstrumentationModule.java | 10 +- .../nats/v2_21/NatsSingletons.java | 17 +- ...MessageConsumer.java => SpanFinisher.java} | 10 +- .../v2_21/SubscriptionInstrumentation.java | 106 ---- .../nats/v2_21/internal/NatsData.java | 36 -- .../NatsInstrumentationDispatcherTest.java | 60 ++ ...tsInstrumentationMessagingReceiveTest.java | 61 ++ .../v2_21/NatsInstrumentationPublishTest.java | 160 +---- .../v2_21/NatsInstrumentationRequestTest.java | 486 +--------------- .../NatsInstrumentationSubscriptionTest.java | 200 ------- .../nats/nats-2.21/library/README.md | 85 +++ .../nats/v2_21/NatsTelemetry.java | 39 +- .../nats/v2_21/NatsTelemetryBuilder.java | 15 +- .../nats/v2_21/OpenTelemetryConnection.java | 548 ++++++------------ .../nats/v2_21/OpenTelemetryDispatcher.java | 164 ++---- .../v2_21/OpenTelemetryDispatcherFactory.java | 76 +++ .../v2_21/OpenTelemetryMessageHandler.java | 35 +- .../nats/v2_21/OpenTelemetrySubscription.java | 160 ----- .../internal/NatsInstrumenterFactory.java | 95 ++- .../NatsRequestContextCustomizer.java | 42 -- .../NatsInstrumentationDispatcherTest.java | 35 ++ ...atsInstrumentationMessaingReceiveTest.java | 39 ++ .../v2_21/NatsInstrumentationPublishTest.java | 35 ++ .../v2_21/NatsInstrumentationRequestTest.java | 43 ++ .../v2_21/NatsTelemetryDispatcherTest.java | 155 ----- .../nats/v2_21/NatsTelemetryPublishTest.java | 150 ----- .../nats/v2_21/NatsTelemetryRequestTest.java | 273 --------- .../v2_21/NatsTelemetrySubscriptionTest.java | 131 ----- instrumentation/nats/nats-2.21/metadata.yaml | 8 +- ...ractNatsInstrumentationDispatcherTest.java | 111 ++++ ...tsInstrumentationMessagingReceiveTest.java | 86 +++ ...bstractNatsInstrumentationPublishTest.java | 125 ++++ ...bstractNatsInstrumentationRequestTest.java | 433 ++++++++++++++ .../v2_21/NatsInstrumentationTestHelper.java | 53 ++ .../nats/v2_21/TestConnection.java | 123 ++-- .../nats/v2_21/TestDispatcher.java | 18 +- .../nats/v2_21/TestMessage.java | 7 +- .../nats/v2_21/TestSubscription.java | 4 +- 45 files changed, 1886 insertions(+), 2609 deletions(-) create mode 100644 instrumentation/nats/nats-2.21/javaagent/README.md delete mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/DispatcherInstrumentation.java rename instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/{internal/MessageConsumer.java => SpanFinisher.java} (79%) delete mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java delete mode 100644 instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/NatsData.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java create mode 100644 instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationMessagingReceiveTest.java delete mode 100644 instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscriptionTest.java create mode 100644 instrumentation/nats/nats-2.21/library/README.md create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcherFactory.java delete mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java delete mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestContextCustomizer.java create mode 100644 instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java create mode 100644 instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationMessaingReceiveTest.java create mode 100644 instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java create mode 100644 instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java delete mode 100644 instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryDispatcherTest.java delete mode 100644 instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java delete mode 100644 instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryRequestTest.java delete mode 100644 instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscriptionTest.java create mode 100644 instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationDispatcherTest.java create mode 100644 instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationMessagingReceiveTest.java create mode 100644 instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationPublishTest.java create mode 100644 instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationRequestTest.java create mode 100644 instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationTestHelper.java diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 85118cd92502..0a584bef38af 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -105,6 +105,7 @@ These are the supported libraries and frameworks: | [Micrometer](https://micrometer.io/) | 1.5+ (disabled by default) | [opentelemetry-micrometer-1.5](../instrumentation/micrometer/micrometer-1.5/library) | none | | [MongoDB Driver](https://mongodb.github.io/mongo-java-driver/) | 3.1+ | [opentelemetry-mongo-3.1](../instrumentation/mongo/mongo-3.1/library) | [Database Client Spans], [Database Client Metrics] [6] | | [MyBatis](https://mybatis.org/mybatis-3/) | 3.2+ | N/A | none | +| [NATS](https://github.com/nats-io/nats.java) | 3.8+ | [nats-2.21](../instrumentation/nats/nats-2.21/library) | [Messaging Spans] | | [Netty HTTP codec [5]](https://github.com/netty/netty) | 3.8+ | [opentelemetry-netty-4.1](../instrumentation/netty/netty-4.1/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] | | [OpenSearch Rest Client](https://github.com/opensearch-project/opensearch-java) | 1.0+ | | [Database Client Spans], [Database Client Metrics] [6] | | [OkHttp](https://github.com/square/okhttp/) | 2.2+ | [opentelemetry-okhttp-3.0](../instrumentation/okhttp/okhttp-3.0/library) | [HTTP Client Spans], [HTTP Client Metrics] | diff --git a/instrumentation/nats/nats-2.21/javaagent/README.md b/instrumentation/nats/nats-2.21/javaagent/README.md new file mode 100644 index 000000000000..c68aac0660c4 --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/README.md @@ -0,0 +1,20 @@ +# Auth-instrumentation for NATS version 2.21 + +Provides OpenTelemetry auto-instrumentation for [NATS 2.21](https://github.com/nats-io/nats.java). + +### Trace propagation + +It's recommended to provide `Message` with a writable `Header` structure +to allow propagation between publishers and subscribers. Without headers, +the tracing context will not be propagated in the headers. + +```java +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; + +// don't +Message msg = NatsMessage.builder().subject("sub").build(); + +// do +Message msg = NatsMessage.builder().subject("sub").headers(new Headers()).build(); +``` diff --git a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts index c1d88aa5675b..f34d83b3d5d0 100644 --- a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts @@ -7,6 +7,7 @@ muzzle { group.set("io.nats") module.set("jnats") versions.set("[2.21.0,)") + assertInverse.set(true) } } @@ -14,10 +15,25 @@ dependencies { library("io.nats:jnats:2.21.0") implementation(project(":instrumentation:nats:nats-2.21:library")) + testImplementation(project(":instrumentation:nats:nats-2.21:testing")) } tasks { + val testMessagingReceive by registering(Test::class) { + filter { + includeTestsMatching("NatsInstrumentationMessagingReceiveTest") + } + jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") + } + test { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + filter { + excludeTestsMatching("NatsInstrumentationMessagingReceiveTest") + } + } + + check { + dependsOn(testMessagingReceive) } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java index 79ade6cb433b..29e5d103ae5c 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java @@ -6,7 +6,7 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_21; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.CLIENT_INSTRUMENTER; +import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.PRODUCER_INSTRUMENTER; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; @@ -21,7 +21,6 @@ import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import io.opentelemetry.javaagent.instrumentation.nats.v2_21.internal.MessageConsumer; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; @@ -133,11 +132,11 @@ public static void onEnter( natsRequest = NatsRequest.create(connection, null, subject, null, body); Context parentContext = Context.current(); - if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { return; } - otelContext = CLIENT_INSTRUMENTER.start(parentContext, natsRequest); + otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); otelScope = otelContext.makeCurrent(); } @@ -155,14 +154,14 @@ public static void onExit( Throwable error = throwable; NatsRequest natsResponse = null; - if (message == null) { + if (error == null && message == null) { error = new TimeoutException("Timed out waiting for message"); } else { natsResponse = NatsRequest.create(connection, message); } otelScope.close(); - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, error); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, error); } } @@ -181,11 +180,11 @@ public static void onEnter( natsRequest = NatsRequest.create(connection, null, subject, headers, body); Context parentContext = Context.current(); - if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { return; } - otelContext = CLIENT_INSTRUMENTER.start(parentContext, natsRequest); + otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); otelScope = otelContext.makeCurrent(); } @@ -203,14 +202,14 @@ public static void onExit( Throwable error = throwable; NatsRequest natsResponse = null; - if (message == null) { + if (error == null && message == null) { error = new TimeoutException("Timed out waiting for message"); } else { natsResponse = NatsRequest.create(connection, message); } otelScope.close(); - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, error); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, error); } } @@ -227,11 +226,11 @@ public static void onEnter( natsRequest = NatsRequest.create(connection, message); Context parentContext = Context.current(); - if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { return; } - otelContext = CLIENT_INSTRUMENTER.start(parentContext, natsRequest); + otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); otelScope = otelContext.makeCurrent(); } @@ -249,14 +248,14 @@ public static void onExit( Throwable error = throwable; NatsRequest natsResponse = null; - if (message == null) { + if (error == null && message == null) { error = new TimeoutException("Timed out waiting for message"); } else { natsResponse = NatsRequest.create(connection, message); } otelScope.close(); - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, error); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, error); } } @@ -275,11 +274,11 @@ public static void onEnter( natsRequest = NatsRequest.create(connection, null, subject, null, body); otelParentContext = Context.current(); - if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { + if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { return; } - otelContext = CLIENT_INSTRUMENTER.start(otelParentContext, natsRequest); + otelContext = PRODUCER_INSTRUMENTER.start(otelParentContext, natsRequest); otelScope = otelContext.makeCurrent(); } @@ -298,10 +297,11 @@ public static void onExit( otelScope.close(); if (throwable != null) { - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); } else { - messageFuture.whenComplete( - new MessageConsumer(CLIENT_INSTRUMENTER, otelContext, connection, natsRequest)); + messageFuture = + messageFuture.whenComplete( + new SpanFinisher(PRODUCER_INSTRUMENTER, otelContext, connection, natsRequest)); messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); } } @@ -323,11 +323,11 @@ public static void onEnter( natsRequest = NatsRequest.create(connection, null, subject, headers, body); otelParentContext = Context.current(); - if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { + if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { return; } - otelContext = CLIENT_INSTRUMENTER.start(otelParentContext, natsRequest); + otelContext = PRODUCER_INSTRUMENTER.start(otelParentContext, natsRequest); otelScope = otelContext.makeCurrent(); } @@ -346,10 +346,11 @@ public static void onExit( otelScope.close(); if (throwable != null) { - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); } else { - messageFuture.whenComplete( - new MessageConsumer(CLIENT_INSTRUMENTER, otelContext, connection, natsRequest)); + messageFuture = + messageFuture.whenComplete( + new SpanFinisher(PRODUCER_INSTRUMENTER, otelContext, connection, natsRequest)); messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); } } @@ -369,11 +370,11 @@ public static void onEnter( natsRequest = NatsRequest.create(connection, message); otelParentContext = Context.current(); - if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { + if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { return; } - otelContext = CLIENT_INSTRUMENTER.start(otelParentContext, natsRequest); + otelContext = PRODUCER_INSTRUMENTER.start(otelParentContext, natsRequest); otelScope = otelContext.makeCurrent(); } @@ -392,10 +393,11 @@ public static void onExit( otelScope.close(); if (throwable != null) { - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); } else { - messageFuture.whenComplete( - new MessageConsumer(CLIENT_INSTRUMENTER, otelContext, connection, natsRequest)); + messageFuture = + messageFuture.whenComplete( + new SpanFinisher(PRODUCER_INSTRUMENTER, otelContext, connection, natsRequest)); messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); } } @@ -416,11 +418,11 @@ public static void onEnter( natsRequest = NatsRequest.create(connection, null, subject, null, body); otelParentContext = Context.current(); - if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { + if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { return; } - otelContext = CLIENT_INSTRUMENTER.start(otelParentContext, natsRequest); + otelContext = PRODUCER_INSTRUMENTER.start(otelParentContext, natsRequest); otelScope = otelContext.makeCurrent(); } @@ -439,10 +441,11 @@ public static void onExit( otelScope.close(); if (throwable != null) { - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); } else { - messageFuture.whenComplete( - new MessageConsumer(CLIENT_INSTRUMENTER, otelContext, connection, natsRequest)); + messageFuture = + messageFuture.whenComplete( + new SpanFinisher(PRODUCER_INSTRUMENTER, otelContext, connection, natsRequest)); messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); } } @@ -464,11 +467,11 @@ public static void onEnter( natsRequest = NatsRequest.create(connection, null, subject, headers, body); otelParentContext = Context.current(); - if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { + if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { return; } - otelContext = CLIENT_INSTRUMENTER.start(otelParentContext, natsRequest); + otelContext = PRODUCER_INSTRUMENTER.start(otelParentContext, natsRequest); otelScope = otelContext.makeCurrent(); } @@ -487,10 +490,11 @@ public static void onExit( otelScope.close(); if (throwable != null) { - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); } else { - messageFuture.whenComplete( - new MessageConsumer(CLIENT_INSTRUMENTER, otelContext, connection, natsRequest)); + messageFuture = + messageFuture.whenComplete( + new SpanFinisher(PRODUCER_INSTRUMENTER, otelContext, connection, natsRequest)); messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); } } @@ -510,11 +514,11 @@ public static void onEnter( natsRequest = NatsRequest.create(connection, message); otelParentContext = Context.current(); - if (!CLIENT_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { + if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { return; } - otelContext = CLIENT_INSTRUMENTER.start(otelParentContext, natsRequest); + otelContext = PRODUCER_INSTRUMENTER.start(otelParentContext, natsRequest); otelScope = otelContext.makeCurrent(); } @@ -533,10 +537,11 @@ public static void onExit( otelScope.close(); if (throwable != null) { - CLIENT_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); } else { - messageFuture.whenComplete( - new MessageConsumer(CLIENT_INSTRUMENTER, otelContext, connection, natsRequest)); + messageFuture = + messageFuture.whenComplete( + new SpanFinisher(PRODUCER_INSTRUMENTER, otelContext, connection, natsRequest)); messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java deleted file mode 100644 index a24719c94844..000000000000 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionSubscribeInstrumentation.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; - -import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; -import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.returns; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; - -import io.nats.client.Connection; -import io.nats.client.Subscription; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import io.opentelemetry.javaagent.instrumentation.nats.v2_21.internal.NatsData; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public class ConnectionSubscribeInstrumentation implements TypeInstrumentation { - - @Override - public ElementMatcher typeMatcher() { - return implementsInterface(named("io.nats.client.Connection")); - } - - @Override - public void transform(TypeTransformer transformer) { - transformer.applyAdviceToMethod( - isPublic() - .and(named("subscribe")) - .and(takesArgument(0, String.class)) - .and(returns(named("io.nats.client.Subscription"))), - ConnectionSubscribeInstrumentation.class.getName() + "$SubscribeAdvice"); - } - - @SuppressWarnings("unused") - public static class SubscribeAdvice { - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.This Connection connection, @Advice.Return Subscription subscription) { - NatsData.addSubscription(subscription, connection); - } - } -} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/DispatcherInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/DispatcherInstrumentation.java new file mode 100644 index 000000000000..0c1fd9be873e --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/DispatcherInstrumentation.java @@ -0,0 +1,57 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class DispatcherInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("io.nats.client.Dispatcher")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isPublic().and(named("start")), + DispatcherInstrumentation.class.getName() + "$DisablePropagationAdvice"); + } + + @SuppressWarnings("unused") + public static class DisablePropagationAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static Scope onEnter() { + // NatsConnection creates a long-running dispatcher on the first `request` call + // leading to a leaked context over the `publish` span creating the dispatcher. + // Dispatchers are usually background long-lived threads, we can force root, + // as we're not expecting to be anything else than entry points for network messages. + if (Java8BytecodeBridge.currentContext() != Java8BytecodeBridge.rootContext()) { + // Prevent context from leaking by running this method under root context. + // Root context is not propagated by executor instrumentation. + return Java8BytecodeBridge.rootContext().makeCurrent(); + } + return null; + } + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.Enter Scope scope) { + if (scope != null) { + scope.close(); + } + } + } +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java index d5c9b1aea019..15040828c248 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java @@ -56,25 +56,23 @@ public static void onEnter( Context parentContext = Context.current(); natsRequest = NatsRequest.create(message.getConnection(), message); - if (!CONSUMER_RECEIVE_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - return; + if (CONSUMER_RECEIVE_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + parentContext = + InstrumenterUtil.startAndEnd( + CONSUMER_RECEIVE_INSTRUMENTER, + parentContext, + natsRequest, + null, + null, + timer.startTime(), + timer.now()); } - Context receiveContext = - InstrumenterUtil.startAndEnd( - CONSUMER_RECEIVE_INSTRUMENTER, - parentContext, - natsRequest, - null, - null, - timer.startTime(), - timer.now()); - - if (!CONSUMER_PROCESS_INSTRUMENTER.shouldStart(receiveContext, natsRequest)) { + if (!CONSUMER_PROCESS_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { return; } - otelContext = CONSUMER_PROCESS_INSTRUMENTER.start(receiveContext, natsRequest); + otelContext = CONSUMER_PROCESS_INSTRUMENTER.start(parentContext, natsRequest); otelScope = otelContext.makeCurrent(); } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java index f195065add67..3c9fa26630bf 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java @@ -5,10 +5,11 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +import static java.util.Arrays.asList; + import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -import java.util.Arrays; import java.util.List; @AutoService(InstrumentationModule.class) @@ -18,15 +19,12 @@ public NatsInstrumentationModule() { super("nats", "nats-2.21"); } - // TODO classLoaderMatcher - @Override public List typeInstrumentations() { - return Arrays.asList( - new ConnectionSubscribeInstrumentation(), + return asList( new ConnectionPublishInstrumentation(), new ConnectionRequestInstrumentation(), - new SubscriptionInstrumentation(), + new DispatcherInstrumentation(), new MessageHandlerInstrumentation()); } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java index ac4a315c2772..cfe8ed2bd23e 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java @@ -5,28 +5,31 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_21; -import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createClientInstrumenter; import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createConsumerProcessInstrumenter; import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createConsumerReceiveInstrumenter; import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createProducerInstrumenter; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; public final class NatsSingletons { - public static final Instrumenter PRODUCER_INSTRUMENTER = + private static final boolean messagingReceiveInstrumentationEnabled = + ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", false); + + public static final Instrumenter PRODUCER_INSTRUMENTER = createProducerInstrumenter(GlobalOpenTelemetry.get()); public static final Instrumenter CONSUMER_RECEIVE_INSTRUMENTER = - createConsumerReceiveInstrumenter(GlobalOpenTelemetry.get()); + createConsumerReceiveInstrumenter( + GlobalOpenTelemetry.get(), messagingReceiveInstrumentationEnabled); public static final Instrumenter CONSUMER_PROCESS_INSTRUMENTER = - createConsumerProcessInstrumenter(GlobalOpenTelemetry.get()); - - public static final Instrumenter CLIENT_INSTRUMENTER = - createClientInstrumenter(GlobalOpenTelemetry.get()); + createConsumerProcessInstrumenter( + GlobalOpenTelemetry.get(), messagingReceiveInstrumentationEnabled); private NatsSingletons() {} } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SpanFinisher.java similarity index 79% rename from instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java rename to instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SpanFinisher.java index 89039bf64420..68e43fff9e38 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/MessageConsumer.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SpanFinisher.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21.internal; +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; import io.nats.client.Connection; import io.nats.client.Message; @@ -12,17 +12,13 @@ import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; import java.util.function.BiConsumer; -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public class MessageConsumer implements BiConsumer { +public class SpanFinisher implements BiConsumer { private final Instrumenter instrumenter; private final Context context; private final Connection connection; private final NatsRequest request; - public MessageConsumer( + public SpanFinisher( Instrumenter instrumenter, Context context, Connection connection, diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java deleted file mode 100644 index c745769ea1c0..000000000000 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SubscriptionInstrumentation.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; - -import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.CONSUMER_RECEIVE_INSTRUMENTER; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; -import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.returns; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import io.nats.client.Connection; -import io.nats.client.Message; -import io.nats.client.Subscription; -import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; -import io.opentelemetry.instrumentation.api.internal.Timer; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import io.opentelemetry.javaagent.instrumentation.nats.v2_21.internal.NatsData; -import java.util.concurrent.TimeoutException; -import javax.annotation.Nullable; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public class SubscriptionInstrumentation implements TypeInstrumentation { - - @Override - public ElementMatcher typeMatcher() { - return implementsInterface(named("io.nats.client.Subscription")); - } - - @Override - public void transform(TypeTransformer transformer) { - transformer.applyAdviceToMethod( - isPublic() - .and(named("nextMessage")) - .and(takesArguments(1)) - .and(returns(named("io.nats.client.Message"))), - SubscriptionInstrumentation.class.getName() + "$NextMessageAdvice"); - transformer.applyAdviceToMethod( - isPublic().and(named("unsubscribe")), - SubscriptionInstrumentation.class.getName() + "$UnsubscribeAdvice"); - } - - @SuppressWarnings("unused") - public static class NextMessageAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static Timer onEnter() { - return Timer.start(); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.Thrown Throwable throwable, - @Advice.This Subscription subscription, - @Advice.Enter Timer timer, - @Advice.Return @Nullable Message message) { - Context parentContext = Context.current(); - TimeoutException timeout = null; - - Connection connection = NatsData.getConnection(subscription); - - // connection should always be non-null at this stage - if (connection == null) { - return; - } - - NatsRequest natsRequest = - NatsRequest.create(connection, null, subscription.getSubject(), null, null); - - if (message == null) { - timeout = new TimeoutException("Timed out waiting for message"); - } else { - natsRequest = NatsRequest.create(connection, message); - } - - if (!CONSUMER_RECEIVE_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - return; - } - - InstrumenterUtil.startAndEnd( - CONSUMER_RECEIVE_INSTRUMENTER, - parentContext, - natsRequest, - null, - timeout, - timer.startTime(), - timer.now()); - } - } - - @SuppressWarnings("unused") - public static class UnsubscribeAdvice { - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit(@Advice.This Subscription subscription) { - NatsData.removeSubscription(subscription); - } - } -} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/NatsData.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/NatsData.java deleted file mode 100644 index b0ae3d3b5c04..000000000000 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/internal/NatsData.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.nats.v2_21.internal; - -import io.nats.client.Connection; -import io.nats.client.Subscription; -import io.opentelemetry.instrumentation.api.util.VirtualField; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time.", or "This class is internal and experimental. Its APIs are unstable and can change at - * any time. Its APIs (or a version of them) may be promoted to the public stable API in the future, - * but no guarantees are made. - */ -public final class NatsData { - - private static final VirtualField subscriptionConnection = - VirtualField.find(Subscription.class, Connection.class); - - public static void addSubscription(Subscription subscription, Connection connection) { - subscriptionConnection.set(subscription, connection); - } - - public static void removeSubscription(Subscription subscription) { - subscriptionConnection.set(subscription, null); - } - - public static Connection getConnection(Subscription subscription) { - return subscriptionConnection.get(subscription); - } - - private NatsData() {} -} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java new file mode 100644 index 000000000000..00e46c72d00b --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.nats.client.Nats; +import io.opentelemetry.instrumentation.nats.v2_21.AbstractNatsInstrumentationDispatcherTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +class NatsInstrumentationDispatcherTest extends AbstractNatsInstrumentationDispatcherTest { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + static final DockerImageName natsImage; + static final GenericContainer natsContainer; + static final Connection natsConnection; + + static { + natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); + + natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); + natsContainer.start(); + + try { + String host = natsContainer.getHost(); + Integer port = natsContainer.getMappedPort(4222); + natsConnection = Nats.connect("nats://" + host + ":" + port); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + @AfterAll + static void afterAll() throws InterruptedException, TimeoutException { + natsConnection.drain(Duration.ZERO); + natsContainer.close(); + } + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected Connection connection() { + return natsConnection; + } +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationMessagingReceiveTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationMessagingReceiveTest.java new file mode 100644 index 000000000000..6b3ffc89da48 --- /dev/null +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationMessagingReceiveTest.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.nats.client.Nats; +import io.opentelemetry.instrumentation.nats.v2_21.AbstractNatsInstrumentationMessagingReceiveTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +class NatsInstrumentationMessagingReceiveTest + extends AbstractNatsInstrumentationMessagingReceiveTest { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + static final DockerImageName natsImage; + static final GenericContainer natsContainer; + static final Connection natsConnection; + + static { + natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); + + natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); + natsContainer.start(); + + try { + String host = natsContainer.getHost(); + Integer port = natsContainer.getMappedPort(4222); + natsConnection = Nats.connect("nats://" + host + ":" + port); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + @AfterAll + static void afterAll() throws InterruptedException, TimeoutException { + natsConnection.drain(Duration.ZERO); + natsContainer.close(); + } + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected Connection connection() { + return natsConnection; + } +} diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java index 3dd46d25f7c3..5a19cca09b2a 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java @@ -5,166 +5,56 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_21; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; -import static org.assertj.core.api.Assertions.assertThat; - import io.nats.client.Connection; -import io.nats.client.Message; import io.nats.client.Nats; -import io.nats.client.Subscription; -import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsMessage; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.nats.v2_21.AbstractNatsInstrumentationPublishTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import java.io.IOException; import java.time.Duration; +import java.util.concurrent.TimeoutException; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; -@SuppressWarnings("deprecation") // using deprecated semconv -class NatsInstrumentationPublishTest { +class NatsInstrumentationPublishTest extends AbstractNatsInstrumentationPublishTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - static final DockerImageName natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); - - static final GenericContainer natsContainer = - new GenericContainer<>(natsImage).withExposedPorts(4222); + static final DockerImageName natsImage; + static final GenericContainer natsContainer; + static final Connection natsConnection; - static String natsUrl; - static Connection connection; - static Subscription subscription; - static int clientId; + static { + natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); - @BeforeAll - static void beforeAll() { + natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); natsContainer.start(); - natsUrl = "nats://" + natsContainer.getHost() + ":" + natsContainer.getMappedPort(4222); + + try { + String host = natsContainer.getHost(); + Integer port = natsContainer.getMappedPort(4222); + natsConnection = Nats.connect("nats://" + host + ":" + port); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } } @AfterAll - static void afterAll() { + static void afterAll() throws InterruptedException, TimeoutException { + natsConnection.drain(Duration.ZERO); natsContainer.close(); } - @BeforeEach - void beforeEach() throws IOException, InterruptedException { - connection = Nats.connect(natsUrl); - subscription = connection.subscribe("sub"); - clientId = connection.getServerInfo().getClientId(); - } - - @AfterEach - void afterEach() throws InterruptedException { - subscription.drain(Duration.ofSeconds(1)); - connection.close(); - } - - @Test - void testPublishBody() throws InterruptedException { - // when - testing.runWithSpan("parent", () -> connection.publish("sub", new byte[] {0})); - - // then - assertPublishSpan(); - assertNoHeaders(); - } - - @Test - void testPublishHeadersBody() throws InterruptedException { - // when - testing.runWithSpan("parent", () -> connection.publish("sub", new Headers(), new byte[] {0})); - - // then - assertPublishSpan(); - assertTraceparentHeader(); - } - - @Test - void testPublishReplyToBody() throws InterruptedException { - // when - testing.runWithSpan("parent", () -> connection.publish("sub", "rt", new byte[] {0})); - - // then - assertPublishSpan(); - assertNoHeaders(); - } - - @Test - void testPublishReplyToHeadersBody() throws InterruptedException { - // when - testing.runWithSpan( - "parent", () -> connection.publish("sub", "rt", new Headers(), new byte[] {0})); - - // then - assertPublishSpan(); - assertTraceparentHeader(); - } - - @Test - void testPublishMessage() throws InterruptedException { - NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); - - // when - testing.runWithSpan("parent", () -> connection.publish(message)); - - // then - assertPublishSpan(); - assertNoHeaders(); - } - - @Test - void testPublishMessageWithHeaders() throws InterruptedException { - NatsMessage message = - NatsMessage.builder().subject("sub").data("x").headers(new Headers()).build(); - - // when - testing.runWithSpan("parent", () -> connection.publish(message)); - - // then - assertPublishSpan(); - assertTraceparentHeader(); - } - - private static void assertPublishSpan() { - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub publish") - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "publish"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))))); - } - - private static void assertNoHeaders() throws InterruptedException { - Message published = subscription.nextMessage(Duration.ofSeconds(1)); - assertThat(published.getHeaders()).isNull(); + @Override + protected InstrumentationExtension testing() { + return testing; } - private static void assertTraceparentHeader() throws InterruptedException { - Message published = subscription.nextMessage(Duration.ofSeconds(1)); - assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); + @Override + protected Connection connection() { + return natsConnection; } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java index 67df04c0550d..84c1fd8cfa24 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java @@ -5,493 +5,61 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_21; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; -import static org.assertj.core.api.Assertions.assertThat; - import io.nats.client.Connection; -import io.nats.client.Dispatcher; -import io.nats.client.Message; import io.nats.client.Nats; -import io.nats.client.Subscription; -import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsMessage; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.nats.v2_21.AbstractNatsInstrumentationRequestTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import java.io.IOException; import java.time.Duration; -import java.util.concurrent.CancellationException; import java.util.concurrent.TimeoutException; -import org.assertj.core.api.Condition; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; -@SuppressWarnings("deprecation") // using deprecated semconv -class NatsInstrumentationRequestTest { +class NatsInstrumentationRequestTest extends AbstractNatsInstrumentationRequestTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - static final DockerImageName natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); - - static final GenericContainer natsContainer = - new GenericContainer<>(natsImage).withExposedPorts(4222); + static final DockerImageName natsImage; + static final GenericContainer natsContainer; + static final Connection natsConnection; - static String natsUrl; - static Connection connection; - static Subscription subscription; - static int clientId; + static { + natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); - @BeforeAll - static void beforeAll() { + natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); natsContainer.start(); - natsUrl = "nats://" + natsContainer.getHost() + ":" + natsContainer.getMappedPort(4222); + + try { + String host = natsContainer.getHost(); + Integer port = natsContainer.getMappedPort(4222); + natsConnection = Nats.connect("nats://" + host + ":" + port); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } } @AfterAll - static void afterAll() { + static void afterAll() throws InterruptedException, TimeoutException { + natsConnection.drain(Duration.ZERO); natsContainer.close(); } - @BeforeEach - void beforeEach() throws IOException, InterruptedException { - connection = Nats.connect(natsUrl); - subscription = connection.subscribe("sub"); - clientId = connection.getServerInfo().getClientId(); - } - - @AfterEach - void afterEach() throws InterruptedException, TimeoutException { - subscription.drain(Duration.ofSeconds(1)); - subscription.unsubscribe(); - connection.drain(Duration.ZERO); - connection.close(); - } - - @Test - void testRequestTimeout() throws InterruptedException { - // when - testing.runWithSpan( - "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); - - // then - assertTimeoutPublishSpan(); - assertNoHeaders(); - } - - @Test - void testRequestBody() throws InterruptedException { - // given - Dispatcher dispatcher = - connection - .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) - .subscribe("sub"); - - // when - testing.runWithSpan( - "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); - connection.closeDispatcher(dispatcher); - - // then - assertPublishSpan(); - assertNoHeaders(); - } - - @Test - void testRequestHeadersBody() throws InterruptedException { - // given - Dispatcher dispatcher = - connection - .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) - .subscribe("sub"); - - // when - testing.runWithSpan( - "parent", - () -> connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); - connection.closeDispatcher(dispatcher); - - // then - assertPublishSpanSameTrace(); - assertTraceparentHeader(); - } - - @Test - void testRequestMessage() throws InterruptedException { - // given - Dispatcher dispatcher = - connection - .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) - .subscribe("sub"); - NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); - - // when - testing.runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); - connection.closeDispatcher(dispatcher); - - // then - assertPublishSpan(); - assertNoHeaders(); - } - - @Test - void testRequestMessageHeaders() throws InterruptedException { - // given - Dispatcher dispatcher = - connection - .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) - .subscribe("sub"); - NatsMessage message = - NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); - - // when - testing.runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); - connection.closeDispatcher(dispatcher); - - // then - assertPublishSpanSameTrace(); - assertTraceparentHeader(); - } - - @Test - void testRequestFutureBody() throws InterruptedException { - // given - Dispatcher dispatcher = - connection - .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) - .subscribe("sub"); - - // when - testing - .runWithSpan("parent", () -> connection.request("sub", new byte[] {0})) - .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); - - // then - assertPublishSpan(); - assertNoHeaders(); - } - - @Test - void testRequestFutureHeadersBody() throws InterruptedException { - // given - Dispatcher dispatcher = - connection - .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) - .subscribe("sub"); - - // when - testing - .runWithSpan("parent", () -> connection.request("sub", new Headers(), new byte[] {0})) - .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); - - // then - assertPublishSpanSameTrace(); - assertTraceparentHeader(); - } - - @Test - void testRequestFutureMessage() throws InterruptedException { - // given - Dispatcher dispatcher = - connection - .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) - .subscribe("sub"); - NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); - - // when - testing - .runWithSpan("parent", () -> connection.request(message)) - .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); - - // then - assertPublishSpan(); - assertNoHeaders(); - } - - @Test - void testRequestFutureMessageHeaders() throws InterruptedException { - // given - Dispatcher dispatcher = - connection - .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) - .subscribe("sub"); - NatsMessage message = - NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); - - // when - testing - .runWithSpan("parent", () -> connection.request(message)) - .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); - - // then - assertPublishSpanSameTrace(); - assertTraceparentHeader(); - } - - @Test - void testRequestTimeoutFutureBody() throws InterruptedException { - // when - testing.runWithSpan( - "parent", - () -> connection.requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); - - // then - assertCancellationPublishSpan(); - assertNoHeaders(); - } - - @Test - void testRequestTimeoutFutureHeadersBody() throws InterruptedException { - // when - testing.runWithSpan( - "parent", - () -> - connection.requestWithTimeout( - "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); - - // then - assertCancellationPublishSpan(); - assertTraceparentHeader(); - } - - @Test - void testRequestTimeoutFutureMessage() throws InterruptedException { - // given - NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); - - // when - testing.runWithSpan( - "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); - - // then - assertCancellationPublishSpan(); - assertNoHeaders(); - } - - @Test - void testRequestTimeoutFutureMessageHeaders() throws InterruptedException { - // given - NatsMessage message = - NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); - - // when - testing.runWithSpan( - "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); - - // then - assertCancellationPublishSpan(); - assertTraceparentHeader(); - } - - private static void assertPublishSpan() { - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub publish") - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "publish"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))), - span -> - span.has( - new Condition<>( - data -> - data.getName().startsWith("_INBOX.") - && data.getName().endsWith(" receive"), - "Name condition")) - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - satisfies( - MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))), - span -> - span.has( - new Condition<>( - data -> - data.getName().startsWith("_INBOX.") - && data.getName().endsWith(" process"), - "Name condition")) - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(2)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "process"), - equalTo(MESSAGING_SYSTEM, "nats"), - satisfies( - MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId)))), - // dispatcher receive, process, publish, not retesting all properties - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("sub receive").hasKind(SpanKind.CONSUMER).hasNoParent(), - span -> - span.hasName("sub process") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)), - span -> - span.has( - new Condition<>( - data -> - data.getName().startsWith("_INBOX.") - && data.getName().endsWith(" publish"), - "Name condition")) - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(1)))); - } - - private static void assertPublishSpanSameTrace() { - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub publish") - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "publish"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))), - - // dispatcher receive, process, publish, not retesting all properties - span -> - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)), - span -> - span.hasName("sub process") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(2)), - span -> - span.has( - new Condition<>( - data -> - data.getName().startsWith("_INBOX.") - && data.getName().endsWith(" publish"), - "Name condition")) - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(3)), - // end dispatcher - - span -> - span.has( - new Condition<>( - data -> - data.getName().startsWith("_INBOX.") - && data.getName().endsWith(" receive"), - "Name condition")) - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - satisfies( - MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))), - span -> - span.has( - new Condition<>( - data -> - data.getName().startsWith("_INBOX.") - && data.getName().endsWith(" process"), - "Name condition")) - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(5)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "process"), - equalTo(MESSAGING_SYSTEM, "nats"), - satisfies( - MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))))); - } - - private static void assertTimeoutPublishSpan() { - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub publish") - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasException(new TimeoutException("Timed out waiting for message")) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "publish"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))))); - } - - private static void assertCancellationPublishSpan() { - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub publish") - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasException( - new CancellationException( - "Future cancelled, response not registered in time, check connection status.")) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "publish"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))))); + @Override + protected InstrumentationExtension testing() { + return testing; } - private static void assertNoHeaders() throws InterruptedException { - Message published = subscription.nextMessage(Duration.ofSeconds(1)); - assertThat(published.getHeaders()).isNull(); + @Override + protected Connection connection() { + return natsConnection; } - private static void assertTraceparentHeader() throws InterruptedException { - Message published = subscription.nextMessage(Duration.ofSeconds(1)); - assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); + @Override + protected boolean isInboxMonitored() { + return true; } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscriptionTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscriptionTest.java deleted file mode 100644 index 828d8dcbca73..000000000000 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationSubscriptionTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; -import static org.assertj.core.api.Assertions.assertThat; - -import io.nats.client.Connection; -import io.nats.client.Nats; -import io.nats.client.Subscription; -import io.nats.client.impl.Headers; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import java.io.IOException; -import java.time.Duration; -import java.util.concurrent.TimeoutException; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.utility.DockerImageName; - -@SuppressWarnings("deprecation") // using deprecated semconv -class NatsInstrumentationSubscriptionTest { - - @RegisterExtension - static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - - static final DockerImageName natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); - - static final GenericContainer natsContainer = - new GenericContainer<>(natsImage).withExposedPorts(4222); - - static String natsUrl; - static Connection connection; - static Subscription subscription; - static int clientId; - - @BeforeAll - static void beforeAll() { - natsContainer.start(); - natsUrl = "nats://" + natsContainer.getHost() + ":" + natsContainer.getMappedPort(4222); - } - - @AfterAll - static void afterAll() { - natsContainer.close(); - } - - @BeforeEach - void beforeEach() throws IOException, InterruptedException { - connection = Nats.connect(natsUrl); - subscription = connection.subscribe("sub"); - clientId = connection.getServerInfo().getClientId(); - } - - @AfterEach - void afterEach() throws InterruptedException { - subscription.drain(Duration.ofSeconds(1)); - connection.close(); - } - - @Test - void testSubscribeTimeout() throws InterruptedException { - // when - testing.runWithSpan("parent", () -> subscription.nextMessage(Duration.ofSeconds(1))); - - // then - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) - .hasException(new TimeoutException("Timed out waiting for message")) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 0), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))))); - } - - @Test - void testNextMessage() throws InterruptedException { - // given - Headers headers = new Headers(new Headers(), /* readOnly= */ true); - - // when - testing.runWithSpan( - "parent", - () -> { - connection.publish("sub", headers, new byte[] {0}); - subscription.nextMessage(Duration.ofSeconds(1)); - }); - - // then - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub publish") - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "publish"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))), - span -> - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))))); - } - - @SuppressWarnings("PreferJavaTimeOverload") - @Test - void testNextMessageLink() throws InterruptedException { - // given - String linkTraceId = "0af7651916cd43dd8448eb211c80319c"; - Headers headers = - new Headers( - new Headers().put("traceparent", "00-" + linkTraceId + "-b7ad6b7169203331-01"), - /* readOnly= */ true); - - // when - testing.runWithSpan( - "parent", - () -> { - connection.publish("sub", headers, new byte[] {0}); - subscription.nextMessage(Duration.ofSeconds(1)); - }); - - // then - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub publish") - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "publish"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))), - span -> - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) - .hasLinksSatisfying( - links -> - assertThat(links) - .singleElement() - .satisfies( - link -> - assertThat(link.getSpanContext().getTraceId()) - .isEqualTo(linkTraceId))) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo( - AttributeKey.stringKey("messaging.client_id"), - String.valueOf(clientId))))); - } -} diff --git a/instrumentation/nats/nats-2.21/library/README.md b/instrumentation/nats/nats-2.21/library/README.md new file mode 100644 index 000000000000..da94909ca4cf --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/README.md @@ -0,0 +1,85 @@ +# Library Instrumentation for NATS version 2.21 + +Provides OpenTelemetry instrumentation for [NATS 2.21](https://github.com/nats-io/nats.java). + +## Quickstart + +### Add these dependencies to your project + +Replace `OPENTELEMETRY_VERSION` with the [latest +release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-nats-2.21). + +For Maven, add to your `pom.xml` dependencies: + +```xml + + + io.opentelemetry.instrumentation + opentelemetry-nats-2.21 + OPENTELEMETRY_VERSION + + +``` + +For Gradle, add to your dependencies: + +```groovy +implementation("io.opentelemetry.instrumentation:opentelemetry-nats-2.21:OPENTELEMETRY_VERSION") +``` + +### Usage + +The instrumentation library provides the class `NatsTelemetry` that has a builder +method and allows the creation of an instance of the `Connection` to provide +OpenTelemetry-based spans and context propagation: + +```java +import io.nats.client.Connection; +import io.nats.client.Nats; +import io.nats.client.Options; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.nats.v2_21.NatsTelemetry; + +public class OpenTelemetryNatsConnection { + + private OpenTelemetry openTelemetry; + private NatsTelemetry telemetry; + + public OpenTelemetryNatsConnection(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + this.telemetry = NatsTelemetry.create(openTelemetry); + } + + // creates a new connection with opentelemetry instrumentation. + // This will *not* instrument the connection's main inbox + // if you're using the default NatsConnection implementation + public Connection wrap(Connection connection) { + return telemetry.wrap(connection); + } + + // prefer wrapping the Options.Builder to get the full instrumentation + // when using the default NatsConnection implementation + public Connection create(Options.Builder builder) throws IOException, InterruptedException { + Options options = telemetry.wrap(builder).build(); + Connection connection = Nats.connect(options); + return wrap(connection); + } +} +``` + +### Trace propagation + +It's recommended to provide `Message` with a writable `Header` structure +to allow propagation between publishers and subscribers. Without headers, +the tracing context will not be propagated in the headers. + +```java +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; + +// don't +Message msg = NatsMessage.builder().subject("sub").build(); + +// do +Message msg = NatsMessage.builder().subject("sub").headers(new Headers()).build(); +``` diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java index 81da357de4c5..f8b068d025c8 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java @@ -6,6 +6,8 @@ package io.opentelemetry.instrumentation.nats.v2_21; import io.nats.client.Connection; +import io.nats.client.Options; +import io.nats.client.impl.DispatcherFactory; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; @@ -20,28 +22,39 @@ public static NatsTelemetryBuilder builder(OpenTelemetry openTelemetry) { return new NatsTelemetryBuilder(openTelemetry); } - private final Instrumenter producerInstrumenter; + private final Instrumenter producerInstrumenter; private final Instrumenter consumerReceiveInstrumenter; private final Instrumenter consumerProcessInstrumenter; - private final Instrumenter clientInstrumenter; public NatsTelemetry( - Instrumenter producerInstrumenter, + Instrumenter producerInstrumenter, Instrumenter consumerReceiveInstrumenter, - Instrumenter consumerProcessInstrumenter, - Instrumenter clientInstrumenter) { + Instrumenter consumerProcessInstrumenter) { this.producerInstrumenter = producerInstrumenter; this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; this.consumerProcessInstrumenter = consumerProcessInstrumenter; - this.clientInstrumenter = clientInstrumenter; } - public OpenTelemetryConnection wrap(Connection connection) { - return new OpenTelemetryConnection( - connection, - producerInstrumenter, - consumerReceiveInstrumenter, - consumerProcessInstrumenter, - clientInstrumenter); + /** + * Returns a decorated {@link Connection} with messaging spans instrumentation. This will *not* + * instrument the connection's main inbox by default. Use {@link #wrap(Options.Builder)} + * beforehand to build an Options doing so. + */ + public Connection wrap(Connection connection) { + return OpenTelemetryConnection.wrap( + connection, producerInstrumenter, consumerReceiveInstrumenter, consumerProcessInstrumenter); + } + + /** Returns a {@link Options.Builder} with instrumented {@link DispatcherFactory}. */ + public Options.Builder wrap(Options.Builder options) { + DispatcherFactory factory = options.build().getDispatcherFactory(); + + if (factory == null) { + factory = new DispatcherFactory(); + } + + return options.dispatcherFactory( + OpenTelemetryDispatcherFactory.wrap( + factory, consumerReceiveInstrumenter, consumerProcessInstrumenter)); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java index 975f058129d7..a392b4cb69ac 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java @@ -5,22 +5,31 @@ package io.opentelemetry.instrumentation.nats.v2_21; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory; public final class NatsTelemetryBuilder { private final OpenTelemetry openTelemetry; + private boolean messagingReceiveInstrumentationEnabled = false; NatsTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; } + @CanIgnoreReturnValue + public NatsTelemetryBuilder setMessagingReceiveInstrumentationEnabled(boolean enabled) { + this.messagingReceiveInstrumentationEnabled = enabled; + return this; + } + public NatsTelemetry build() { return new NatsTelemetry( NatsInstrumenterFactory.createProducerInstrumenter(openTelemetry), - NatsInstrumenterFactory.createConsumerReceiveInstrumenter(openTelemetry), - NatsInstrumenterFactory.createConsumerProcessInstrumenter(openTelemetry), - NatsInstrumenterFactory.createClientInstrumenter(openTelemetry)); + NatsInstrumenterFactory.createConsumerReceiveInstrumenter( + openTelemetry, messagingReceiveInstrumentationEnabled), + NatsInstrumenterFactory.createConsumerProcessInstrumenter( + openTelemetry, messagingReceiveInstrumentationEnabled)); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java index 6eb0579cdaf4..d1248ab506aa 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java @@ -6,452 +6,260 @@ package io.opentelemetry.instrumentation.nats.v2_21; import io.nats.client.Connection; -import io.nats.client.ConnectionListener; -import io.nats.client.ConsumerContext; import io.nats.client.Dispatcher; -import io.nats.client.ForceReconnectOptions; -import io.nats.client.JetStream; -import io.nats.client.JetStreamApiException; -import io.nats.client.JetStreamManagement; -import io.nats.client.JetStreamOptions; -import io.nats.client.KeyValue; -import io.nats.client.KeyValueManagement; -import io.nats.client.KeyValueOptions; import io.nats.client.Message; import io.nats.client.MessageHandler; -import io.nats.client.ObjectStore; -import io.nats.client.ObjectStoreManagement; -import io.nats.client.ObjectStoreOptions; -import io.nats.client.Options; -import io.nats.client.Statistics; -import io.nats.client.StreamContext; -import io.nats.client.Subscription; -import io.nats.client.api.ServerInfo; import io.nats.client.impl.Headers; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; -import io.opentelemetry.instrumentation.nats.v2_21.internal.ThrowingSupplier; -import java.io.IOException; -import java.net.InetAddress; -import java.time.Duration; -import java.util.Collection; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; -import java.util.function.Supplier; -public class OpenTelemetryConnection implements Connection { +final class OpenTelemetryConnection implements InvocationHandler { private final Connection delegate; - private final Instrumenter producerInstrumenter; + private final Instrumenter producerInstrumenter; private final Instrumenter consumerReceiveInstrumenter; private final Instrumenter consumerProcessInstrumenter; - private final Instrumenter clientInstrumenter; public OpenTelemetryConnection( Connection connection, - Instrumenter producerInstrumenter, + Instrumenter producerInstrumenter, Instrumenter consumerReceiveInstrumenter, - Instrumenter consumerProcessInstrumenter, - Instrumenter clientInstrumenter) { + Instrumenter consumerProcessInstrumenter) { this.delegate = connection; this.producerInstrumenter = producerInstrumenter; this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; this.consumerProcessInstrumenter = consumerProcessInstrumenter; - this.clientInstrumenter = clientInstrumenter; } - @Override - public void publish(String subject, byte[] body) { - wrapPublish( - NatsRequest.create(this, null, subject, null, body), () -> delegate.publish(subject, body)); - } - - @Override - public void publish(String subject, Headers headers, byte[] body) { - wrapPublish( - NatsRequest.create(this, null, subject, headers, body), - () -> delegate.publish(subject, headers, body)); - } - - @Override - public void publish(String subject, String replyTo, byte[] body) { - wrapPublish( - NatsRequest.create(this, replyTo, subject, null, body), - () -> delegate.publish(subject, replyTo, body)); - } - - @Override - public void publish(String subject, String replyTo, Headers headers, byte[] body) { - wrapPublish( - NatsRequest.create(this, replyTo, subject, headers, body), - () -> delegate.publish(subject, replyTo, headers, body)); - } - - @Override - public void publish(Message message) { - wrapPublish(NatsRequest.create(this, message), () -> delegate.publish(message)); - } - - @Override - public Message request(String subject, byte[] body, Duration timeout) - throws InterruptedException { - return wrapRequest( - NatsRequest.create(this, null, subject, null, body), - () -> delegate.request(subject, body, timeout)); - } - - @Override - public Message request(String subject, Headers headers, byte[] body, Duration timeout) - throws InterruptedException { - return wrapRequest( - NatsRequest.create(this, null, subject, headers, body), - () -> delegate.request(subject, headers, body, timeout)); - } - - @Override - public Message request(Message message, Duration timeout) throws InterruptedException { - return wrapRequest(NatsRequest.create(this, message), () -> delegate.request(message, timeout)); - } - - @Override - public CompletableFuture request(String subject, byte[] body) { - return wrapRequest( - NatsRequest.create(this, null, subject, null, body), () -> delegate.request(subject, body)); - } - - @Override - public CompletableFuture request(String subject, Headers headers, byte[] body) { - return wrapRequest( - NatsRequest.create(this, null, subject, headers, body), - () -> delegate.request(subject, headers, body)); - } - - @Override - public CompletableFuture request(Message message) { - return wrapRequest(NatsRequest.create(this, message), () -> delegate.request(message)); - } - - @Override - public CompletableFuture requestWithTimeout( - String subject, byte[] body, Duration timeout) { - return wrapRequest( - NatsRequest.create(this, null, subject, null, body), - () -> delegate.requestWithTimeout(subject, body, timeout)); - } - - @Override - public CompletableFuture requestWithTimeout( - String subject, Headers headers, byte[] body, Duration timeout) { - return wrapRequest( - NatsRequest.create(this, null, subject, headers, body), - () -> delegate.requestWithTimeout(subject, headers, body, timeout)); - } - - @Override - public CompletableFuture requestWithTimeout(Message message, Duration timeout) { - return wrapRequest( - NatsRequest.create(this, message), () -> delegate.requestWithTimeout(message, timeout)); - } - - @Override - public Subscription subscribe(String subject) { - return new OpenTelemetrySubscription( - this, delegate.subscribe(subject), consumerReceiveInstrumenter); - } - - @Override - public Subscription subscribe(String subject, String queueName) { - return new OpenTelemetrySubscription( - this, delegate.subscribe(subject, queueName), consumerReceiveInstrumenter); - } - - @Override - public Dispatcher createDispatcher(MessageHandler messageHandler) { - OpenTelemetryMessageHandler otelHandler = - new OpenTelemetryMessageHandler( - this, messageHandler, consumerReceiveInstrumenter, consumerProcessInstrumenter); - return new OpenTelemetryDispatcher( - this, - delegate.createDispatcher(otelHandler), - consumerReceiveInstrumenter, - consumerProcessInstrumenter); - } - - @Override - public Dispatcher createDispatcher() { - return new OpenTelemetryDispatcher( - this, - delegate.createDispatcher(), - consumerReceiveInstrumenter, - consumerProcessInstrumenter); - } - - @Override - public void closeDispatcher(Dispatcher dispatcher) { - if (dispatcher instanceof OpenTelemetryDispatcher) { - delegate.closeDispatcher(((OpenTelemetryDispatcher) dispatcher).getDelegate()); - return; + public static Connection wrap( + Connection connection, + Instrumenter producerInstrumenter, + Instrumenter consumerReceiveInstrumenter, + Instrumenter consumerProcessInstrumenter) { + return (Connection) + Proxy.newProxyInstance( + OpenTelemetryConnection.class.getClassLoader(), + new Class[] {Connection.class}, + new OpenTelemetryConnection( + connection, + producerInstrumenter, + consumerReceiveInstrumenter, + consumerProcessInstrumenter)); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("publish".equals(method.getName()) && method.getReturnType().equals(Void.TYPE)) { + return publish(method, args); } - delegate.closeDispatcher(dispatcher); - } - - @Override - public void addConnectionListener(ConnectionListener connectionListener) { - delegate.addConnectionListener(connectionListener); - } - - @Override - public void removeConnectionListener(ConnectionListener connectionListener) { - delegate.removeConnectionListener(connectionListener); - } - - @Override - public void flush(Duration timeout) throws TimeoutException, InterruptedException { - delegate.flush(timeout); - } - - @Override - public CompletableFuture drain(Duration timeout) - throws TimeoutException, InterruptedException { - return delegate.drain(timeout); - } - - @Override - public void close() throws InterruptedException { - delegate.close(); - } - - @Override - public Status getStatus() { - return delegate.getStatus(); - } - - @Override - public long getMaxPayload() { - return delegate.getMaxPayload(); - } - - @Override - public Collection getServers() { - return delegate.getServers(); - } - - @Override - public Statistics getStatistics() { - return delegate.getStatistics(); - } - - @Override - public Options getOptions() { - return delegate.getOptions(); - } - - @Override - public ServerInfo getServerInfo() { - return delegate.getServerInfo(); - } - - @Override - public String getConnectedUrl() { - return delegate.getConnectedUrl(); - } - - @Override - public InetAddress getClientInetAddress() { - return delegate.getClientInetAddress(); - } - - @Override - public String getLastError() { - return delegate.getLastError(); - } - - @Override - public void clearLastError() { - delegate.clearLastError(); - } - - @Override - public String createInbox() { - return delegate.createInbox(); - } - - @Override - public void flushBuffer() throws IOException { - delegate.flushBuffer(); - } - - @Override - public void forceReconnect() throws IOException, InterruptedException { - delegate.forceReconnect(); - } - - @Override - public void forceReconnect(ForceReconnectOptions forceReconnectOptions) - throws IOException, InterruptedException { - delegate.forceReconnect(forceReconnectOptions); - } - - @Override - public Duration RTT() throws IOException { - return delegate.RTT(); - } - - @Override - public StreamContext getStreamContext(String streamName) - throws IOException, JetStreamApiException { - return delegate.getStreamContext(streamName); - } - - @Override - public StreamContext getStreamContext(String streamName, JetStreamOptions jetStreamOptions) - throws IOException, JetStreamApiException { - return delegate.getStreamContext(streamName, jetStreamOptions); - } - - @Override - public ConsumerContext getConsumerContext(String streamName, String consumerName) - throws IOException, JetStreamApiException { - return delegate.getConsumerContext(streamName, consumerName); - } - - @Override - public ConsumerContext getConsumerContext( - String streamName, String consumerName, JetStreamOptions jetStreamOptions) - throws IOException, JetStreamApiException { - return delegate.getConsumerContext(streamName, consumerName, jetStreamOptions); - } - - @Override - public JetStream jetStream() throws IOException { - return delegate.jetStream(); - } - - @Override - public JetStream jetStream(JetStreamOptions jetStreamOptions) throws IOException { - return delegate.jetStream(jetStreamOptions); - } - - @Override - public JetStreamManagement jetStreamManagement() throws IOException { - return delegate.jetStreamManagement(); - } - - @Override - public JetStreamManagement jetStreamManagement(JetStreamOptions jetStreamOptions) - throws IOException { - return delegate.jetStreamManagement(jetStreamOptions); - } - - @Override - public KeyValue keyValue(String bucketName) throws IOException { - return delegate.keyValue(bucketName); - } - - @Override - public KeyValue keyValue(String s, KeyValueOptions keyValueOptions) throws IOException { - return delegate.keyValue(s, keyValueOptions); - } + if ("request".equals(method.getName()) && method.getReturnType().equals(Message.class)) { + return request(method, args); + } - @Override - public KeyValueManagement keyValueManagement() throws IOException { - return delegate.keyValueManagement(); - } + if (("request".equals(method.getName()) || "requestWithTimeout".equals(method.getName())) + && method.getReturnType().equals(CompletableFuture.class)) { + return requestAsync(method, args); + } - @Override - public KeyValueManagement keyValueManagement(KeyValueOptions keyValueOptions) throws IOException { - return delegate.keyValueManagement(keyValueOptions); - } + if ("createDispatcher".equals(method.getName()) + && method.getReturnType().equals(Dispatcher.class)) { + return createDispatcher(method, args); + } - @Override - public ObjectStore objectStore(String bucketName) throws IOException { - return delegate.objectStore(bucketName); - } + if ("closeDispatcher".equals(method.getName())) { + return closeDispatcher(method, args); + } - @Override - public ObjectStore objectStore(String bucketName, ObjectStoreOptions objectStoreOptions) - throws IOException { - return delegate.objectStore(bucketName, objectStoreOptions); + try { + return method.invoke(delegate, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } } - @Override - public ObjectStoreManagement objectStoreManagement() throws IOException { - return delegate.objectStoreManagement(); + private static Object invokeProxyMethod(Method method, Object target, Object[] args) + throws Throwable { + try { + return method.invoke(target, args); + } catch (InvocationTargetException exception) { + throw exception.getCause(); + } } - @Override - public ObjectStoreManagement objectStoreManagement(ObjectStoreOptions objectStoreOptions) - throws IOException { - return delegate.objectStoreManagement(objectStoreOptions); - } + // void publish(String subject, byte[] body) + // void publish(String subject, Headers headers, byte[] body) + // void publish(String subject, String replyTo, byte[] body) + // void publish(String subject, String replyTo, Headers headers, byte[] body) + // void publish(Message message) + private Object publish(Method method, Object[] args) throws Throwable { + NatsRequest natsRequest = null; + + if (method.getParameterCount() == 2 + && method.getParameterTypes()[0] == String.class + && method.getParameterTypes()[1] == byte[].class) { + natsRequest = NatsRequest.create(delegate, null, (String) args[0], null, (byte[]) args[1]); + } else if (method.getParameterCount() == 3 + && method.getParameterTypes()[0] == String.class + && method.getParameterTypes()[1] == Headers.class + && method.getParameterTypes()[2] == byte[].class) { + natsRequest = + NatsRequest.create(delegate, null, (String) args[0], (Headers) args[1], (byte[]) args[2]); + } else if (method.getParameterCount() == 3 + && method.getParameterTypes()[0] == String.class + && method.getParameterTypes()[1] == String.class + && method.getParameterTypes()[2] == byte[].class) { + natsRequest = + NatsRequest.create(delegate, (String) args[1], (String) args[0], null, (byte[]) args[2]); + } else if (method.getParameterCount() == 4 + && method.getParameterTypes()[0] == String.class + && method.getParameterTypes()[1] == String.class + && method.getParameterTypes()[2] == Headers.class + && method.getParameterTypes()[3] == byte[].class) { + natsRequest = + NatsRequest.create( + delegate, (String) args[1], (String) args[0], (Headers) args[2], (byte[]) args[3]); + } else if (method.getParameterCount() == 1 && method.getParameterTypes()[0] == Message.class) { + natsRequest = NatsRequest.create(delegate, (Message) args[0]); + } - private void wrapPublish(NatsRequest natsRequest, Runnable publish) { Context parentContext = Context.current(); - if (!producerInstrumenter.shouldStart(parentContext, natsRequest)) { - publish.run(); - return; + if (natsRequest == null || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { + return invokeProxyMethod(method, delegate, args); } Context context = producerInstrumenter.start(parentContext, natsRequest); try (Scope ignored = context.makeCurrent()) { - publish.run(); + return invokeProxyMethod(method, delegate, args); } finally { producerInstrumenter.end(context, natsRequest, null, null); } } - private Message wrapRequest( - NatsRequest natsRequest, ThrowingSupplier request) - throws InterruptedException { + // Message request(String subject, byte[] body, Duration timeout) throws InterruptedException; + // Message request(String subject, Headers headers, byte[] body, Duration timeout) throws + // InterruptedException; + // Message request(Message message, Duration timeout) throws InterruptedException; + private Message request(Method method, Object[] args) throws Throwable { + NatsRequest natsRequest = null; + + if (method.getParameterCount() == 3 + && method.getParameterTypes()[0] == String.class + && method.getParameterTypes()[1] == byte[].class) { + natsRequest = NatsRequest.create(delegate, null, (String) args[0], null, (byte[]) args[1]); + } else if (method.getParameterCount() == 4 + && method.getParameterTypes()[0] == String.class + && method.getParameterTypes()[1] == Headers.class + && method.getParameterTypes()[2] == byte[].class) { + natsRequest = + NatsRequest.create(delegate, null, (String) args[0], (Headers) args[1], (byte[]) args[2]); + } else if (method.getParameterCount() == 2 && method.getParameterTypes()[0] == Message.class) { + natsRequest = NatsRequest.create(delegate, (Message) args[0]); + } + Context parentContext = Context.current(); - if (!clientInstrumenter.shouldStart(parentContext, natsRequest)) { - return request.call(); + if (natsRequest == null || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { + return (Message) invokeProxyMethod(method, delegate, args); } - Context context = clientInstrumenter.start(parentContext, natsRequest); + Context context = producerInstrumenter.start(parentContext, natsRequest); TimeoutException timeout = null; NatsRequest response = null; try (Scope ignored = context.makeCurrent()) { - Message message = request.call(); + Message message = (Message) invokeProxyMethod(method, delegate, args); if (message == null) { timeout = new TimeoutException("Timed out waiting for message"); } else { - response = NatsRequest.create(this, message); + response = NatsRequest.create(delegate, message); } return message; } finally { - clientInstrumenter.end(context, natsRequest, response, timeout); + producerInstrumenter.end(context, natsRequest, response, timeout); } } - private CompletableFuture wrapRequest( - NatsRequest natsRequest, Supplier> request) { + // CompletableFuture request(String subject, byte[] body); + // CompletableFuture requestWithTimeout(String subject, byte[] body, Duration timeout); + // CompletableFuture request(String subject, Headers headers, byte[] body); + // CompletableFuture requestWithTimeout(String subject, Headers headers, byte[] body, + // Duration timeout); + // CompletableFuture request(Message message); + // CompletableFuture requestWithTimeout(Message message, Duration timeout); + @SuppressWarnings("unchecked") + private CompletableFuture requestAsync(Method method, Object[] args) throws Throwable { + NatsRequest natsRequest = null; + + if ((method.getParameterCount() == 2 || method.getParameterCount() == 3) + && method.getParameterTypes()[0] == String.class + && method.getParameterTypes()[1] == byte[].class) { + natsRequest = NatsRequest.create(delegate, null, (String) args[0], null, (byte[]) args[1]); + } else if ((method.getParameterCount() == 3 || method.getParameterCount() == 4) + && method.getParameterTypes()[0] == String.class + && method.getParameterTypes()[1] == Headers.class + && method.getParameterTypes()[2] == byte[].class) { + natsRequest = + NatsRequest.create(delegate, null, (String) args[0], (Headers) args[1], (byte[]) args[2]); + } else if ((method.getParameterCount() == 1 || method.getParameterCount() == 2) + && method.getParameterTypes()[0] == Message.class) { + natsRequest = NatsRequest.create(delegate, (Message) args[0]); + } + Context parentContext = Context.current(); - if (!clientInstrumenter.shouldStart(parentContext, natsRequest)) { - return request.get(); + if (natsRequest == null || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { + return (CompletableFuture) invokeProxyMethod(method, delegate, args); } - Context context = clientInstrumenter.start(parentContext, natsRequest); + NatsRequest notNullNatsRequest = natsRequest; + Context context = producerInstrumenter.start(parentContext, notNullNatsRequest); - return request - .get() + return ((CompletableFuture) invokeProxyMethod(method, delegate, args)) .whenComplete( (message, exception) -> { if (message != null) { - NatsRequest response = NatsRequest.create(this, message); - clientInstrumenter.end(context, natsRequest, response, exception); + NatsRequest response = NatsRequest.create(delegate, message); + producerInstrumenter.end(context, notNullNatsRequest, response, exception); } else { - clientInstrumenter.end(context, natsRequest, null, exception); + producerInstrumenter.end(context, notNullNatsRequest, null, exception); } }); } + + // public Dispatcher createDispatcher() + // public Dispatcher createDispatcher(MessageHandler messageHandler) + private Dispatcher createDispatcher(Method method, Object[] args) throws Throwable { + if (method.getParameterCount() == 1 && method.getParameterTypes()[0] == MessageHandler.class) { + args[0] = + new OpenTelemetryMessageHandler( + (MessageHandler) args[0], consumerReceiveInstrumenter, consumerProcessInstrumenter); + } + + Dispatcher wrapped = (Dispatcher) invokeProxyMethod(method, delegate, args); + return OpenTelemetryDispatcher.wrap( + wrapped, consumerReceiveInstrumenter, consumerProcessInstrumenter); + } + + // public void closeDispatcher(Dispatcher dispatcher) + private Object closeDispatcher(Method method, Object[] args) throws Throwable { + if (method.getParameterCount() == 1 + && args[0] instanceof Proxy + && Proxy.getInvocationHandler(args[0]) instanceof OpenTelemetryDispatcher) { + args[0] = ((OpenTelemetryDispatcher) Proxy.getInvocationHandler(args[0])).getDelegate(); + } + + return invokeProxyMethod(method, delegate, args); + } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java index 82eedbacf550..2c9beb10ce35 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java @@ -5,159 +5,83 @@ package io.opentelemetry.instrumentation.nats.v2_21; -import io.nats.client.Connection; import io.nats.client.Dispatcher; import io.nats.client.MessageHandler; import io.nats.client.Subscription; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; -import java.time.Duration; -import java.util.concurrent.CompletableFuture; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; -public class OpenTelemetryDispatcher implements Dispatcher { +final class OpenTelemetryDispatcher implements InvocationHandler { - private final Connection connection; private final Dispatcher delegate; private final Instrumenter consumerReceiveInstrumenter; private final Instrumenter consumerProcessInstrumenter; public OpenTelemetryDispatcher( - Connection connection, Dispatcher delegate, Instrumenter consumerReceiveInstrumenter, Instrumenter consumerProcessInstrumenter) { - this.connection = connection; this.delegate = delegate; this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; this.consumerProcessInstrumenter = consumerProcessInstrumenter; } - @Override - public void start(String id) { - delegate.start(id); - } - - @Override - public Dispatcher subscribe(String subject) { - delegate.subscribe(subject); - // from javadoc - Returns: The Dispatcher, so calls can be chained. - return this; - } - - @Override - public Dispatcher subscribe(String subject, String queue) { - delegate.subscribe(subject, queue); - // from javadoc - Returns: The Dispatcher, so calls can be chained. - return this; - } - - @Override - public Subscription subscribe(String subject, MessageHandler handler) { - OpenTelemetryMessageHandler otelHandler = - new OpenTelemetryMessageHandler( - connection, handler, consumerReceiveInstrumenter, consumerProcessInstrumenter); - Subscription wrapped = delegate.subscribe(subject, otelHandler); - return new OpenTelemetrySubscription(connection, wrapped, consumerReceiveInstrumenter); - } - - @Override - public Subscription subscribe(String subject, String queue, MessageHandler handler) { - OpenTelemetryMessageHandler otelHandler = - new OpenTelemetryMessageHandler( - connection, handler, consumerReceiveInstrumenter, consumerProcessInstrumenter); - Subscription wrapped = delegate.subscribe(subject, queue, otelHandler); - return new OpenTelemetrySubscription(connection, wrapped, consumerReceiveInstrumenter); - } - - @Override - public Dispatcher unsubscribe(String subject) { - delegate.unsubscribe(subject); - // from javadoc - Returns: The Dispatcher, so calls can be chained. - return this; + public static Dispatcher wrap( + Dispatcher delegate, + Instrumenter consumerReceiveInstrumenter, + Instrumenter consumerProcessInstrumenter) { + return (Dispatcher) + Proxy.newProxyInstance( + OpenTelemetryDispatcher.class.getClassLoader(), + new Class[] {Dispatcher.class}, + new OpenTelemetryDispatcher( + delegate, consumerReceiveInstrumenter, consumerProcessInstrumenter)); } @Override - public Dispatcher unsubscribe(Subscription subscription) { - if (subscription instanceof OpenTelemetrySubscription) { - delegate.unsubscribe(((OpenTelemetrySubscription) subscription).getDelegate()); - } else { - delegate.unsubscribe(subscription); + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("subscribe".equals(method.getName()) && method.getReturnType().equals(Subscription.class)) { + return subscribe(method, args); } - // from javadoc - Returns: The Dispatcher, so calls can be chained. - return this; - } - - @Override - public Dispatcher unsubscribe(String subject, int after) { - delegate.unsubscribe(subject, after); - // from javadoc - Returns: The Dispatcher, so calls can be chained. - return this; - } - - @Override - public Dispatcher unsubscribe(Subscription subscription, int after) { - if (subscription instanceof OpenTelemetrySubscription) { - delegate.unsubscribe(((OpenTelemetrySubscription) subscription).getDelegate(), after); - } else { - delegate.unsubscribe(subscription, after); + try { + return method.invoke(delegate, args); + } catch (InvocationTargetException e) { + throw e.getCause(); } - - // from javadoc - Returns: The Dispatcher, so calls can be chained. - return this; } - @Override - public void setPendingLimits(long maxMessages, long maxBytes) { - delegate.setPendingLimits(maxMessages, maxBytes); - } - - @Override - public long getPendingMessageLimit() { - return delegate.getPendingMessageLimit(); - } - - @Override - public long getPendingByteLimit() { - return delegate.getPendingByteLimit(); - } - - @Override - public long getPendingMessageCount() { - return delegate.getPendingMessageCount(); - } - - @Override - public long getPendingByteCount() { - return delegate.getPendingByteCount(); - } - - @Override - public long getDeliveredCount() { - return delegate.getDeliveredCount(); - } - - @Override - public long getDroppedCount() { - return delegate.getDroppedCount(); - } - - @Override - public void clearDroppedCount() { - delegate.clearDroppedCount(); + private static Object invokeProxyMethod(Method method, Object target, Object[] args) + throws Throwable { + try { + return method.invoke(target, args); + } catch (InvocationTargetException exception) { + throw exception.getCause(); + } } - @Override - public boolean isActive() { - return delegate.isActive(); - } + // Subscription subscribe(String subject, MessageHandler handler); + // Subscription subscribe(String subject, String queue, MessageHandler handler); + private Subscription subscribe(Method method, Object[] args) throws Throwable { + if (method.getParameterCount() == 2 && method.getParameterTypes()[1] == MessageHandler.class) { + args[1] = + new OpenTelemetryMessageHandler( + (MessageHandler) args[1], consumerReceiveInstrumenter, consumerProcessInstrumenter); + } else if (method.getParameterCount() == 3 + && method.getParameterTypes()[2] == MessageHandler.class) { + args[2] = + new OpenTelemetryMessageHandler( + (MessageHandler) args[2], consumerReceiveInstrumenter, consumerProcessInstrumenter); + } - @Override - public CompletableFuture drain(Duration timeout) throws InterruptedException { - return delegate.drain(timeout); + return (Subscription) invokeProxyMethod(method, delegate, args); } - protected Dispatcher getDelegate() { + Dispatcher getDelegate() { return delegate; } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcherFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcherFactory.java new file mode 100644 index 000000000000..fc131f9eda8f --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcherFactory.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.MessageHandler; +import io.nats.client.impl.DispatcherFactory; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +final class OpenTelemetryDispatcherFactory implements InvocationHandler { + + private final DispatcherFactory delegate; + private final Instrumenter consumerReceiveInstrumenter; + private final Instrumenter consumerProcessInstrumenter; + + public OpenTelemetryDispatcherFactory( + DispatcherFactory delegate, + Instrumenter consumerReceiveInstrumenter, + Instrumenter consumerProcessInstrumenter) { + this.delegate = delegate; + this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; + this.consumerProcessInstrumenter = consumerProcessInstrumenter; + } + + public static DispatcherFactory wrap( + DispatcherFactory delegate, + Instrumenter consumerReceiveInstrumenter, + Instrumenter consumerProcessInstrumenter) { + return (DispatcherFactory) + Proxy.newProxyInstance( + OpenTelemetryDispatcherFactory.class.getClassLoader(), + new Class[] {DispatcherFactory.class}, + new OpenTelemetryDispatcherFactory( + delegate, consumerReceiveInstrumenter, consumerProcessInstrumenter)); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("createDispatcher".equals(method.getName())) { + return createDispatcher(method, args); + } + + try { + return method.invoke(delegate, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + + private static Object invokeProxyMethod(Method method, Object target, Object[] args) + throws Throwable { + try { + return method.invoke(target, args); + } catch (InvocationTargetException exception) { + throw exception.getCause(); + } + } + + // NatsDispatcher createDispatcher(NatsConnection conn, MessageHandler handler) + private Object createDispatcher(Method method, Object[] args) throws Throwable { + if (method.getParameterCount() == 2 && method.getParameterTypes()[1] == MessageHandler.class) { + args[1] = + new OpenTelemetryMessageHandler( + (MessageHandler) args[1], consumerReceiveInstrumenter, consumerProcessInstrumenter); + } + + return invokeProxyMethod(method, delegate, args); + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java index ae1811da8df0..f7c8bbb781b5 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java @@ -5,7 +5,6 @@ package io.opentelemetry.instrumentation.nats.v2_21; -import io.nats.client.Connection; import io.nats.client.Message; import io.nats.client.MessageHandler; import io.opentelemetry.context.Context; @@ -15,19 +14,16 @@ import io.opentelemetry.instrumentation.api.internal.Timer; import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; -public class OpenTelemetryMessageHandler implements MessageHandler { +final class OpenTelemetryMessageHandler implements MessageHandler { - private final Connection connection; private final MessageHandler delegate; private final Instrumenter consumerReceiveInstrumenter; private final Instrumenter consumerProcessInstrumenter; public OpenTelemetryMessageHandler( - Connection connection, MessageHandler delegate, Instrumenter consumerReceiveInstrumenter, Instrumenter consumerProcessInstrumenter) { - this.connection = connection; this.delegate = delegate; this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; this.consumerProcessInstrumenter = consumerProcessInstrumenter; @@ -38,29 +34,26 @@ public void onMessage(Message message) throws InterruptedException { Timer timer = Timer.start(); Context parentContext = Context.current(); - NatsRequest natsRequest = NatsRequest.create(connection, message); + NatsRequest natsRequest = NatsRequest.create(message.getConnection(), message); - if (!consumerReceiveInstrumenter.shouldStart(parentContext, natsRequest)) { - delegate.onMessage(message); - return; + if (consumerReceiveInstrumenter.shouldStart(parentContext, natsRequest)) { + parentContext = + InstrumenterUtil.startAndEnd( + consumerReceiveInstrumenter, + parentContext, + natsRequest, + null, + null, + timer.startTime(), + timer.now()); } - Context receiveContext = - InstrumenterUtil.startAndEnd( - consumerReceiveInstrumenter, - parentContext, - natsRequest, - null, - null, - timer.startTime(), - timer.now()); - - if (!consumerProcessInstrumenter.shouldStart(receiveContext, natsRequest)) { + if (!consumerProcessInstrumenter.shouldStart(parentContext, natsRequest)) { delegate.onMessage(message); return; } - Context processContext = consumerProcessInstrumenter.start(receiveContext, natsRequest); + Context processContext = consumerProcessInstrumenter.start(parentContext, natsRequest); InterruptedException exception = null; try (Scope ignored = processContext.makeCurrent()) { diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java deleted file mode 100644 index 7270f4e81e70..000000000000 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetrySubscription.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_21; - -import io.nats.client.Connection; -import io.nats.client.Dispatcher; -import io.nats.client.Message; -import io.nats.client.Subscription; -import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; -import io.opentelemetry.instrumentation.api.internal.Timer; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; -import io.opentelemetry.instrumentation.nats.v2_21.internal.ThrowingSupplier2; -import java.time.Duration; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; - -public class OpenTelemetrySubscription implements Subscription { - - private final Connection connection; - private final Subscription delegate; - private final Instrumenter consumerReceiveInstrumenter; - - public OpenTelemetrySubscription( - Connection connection, - Subscription subscription, - Instrumenter consumerReceiveInstrumenter) { - this.connection = connection; - this.delegate = subscription; - this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; - } - - @Override - public String getSubject() { - return delegate.getSubject(); - } - - @Override - public String getQueueName() { - return delegate.getQueueName(); - } - - @Override - public Dispatcher getDispatcher() { - return delegate.getDispatcher(); - } - - @SuppressWarnings("ThrowsUncheckedException") - @Override - public Message nextMessage(Duration timeout) throws InterruptedException, IllegalStateException { - return wrapNextMessage(() -> delegate.nextMessage(timeout)); - } - - @SuppressWarnings({"PreferJavaTimeOverload", "ThrowsUncheckedException"}) - @Override - public Message nextMessage(long timeoutMillis) - throws InterruptedException, IllegalStateException { - return wrapNextMessage(() -> delegate.nextMessage(timeoutMillis)); - } - - @Override - public void unsubscribe() { - delegate.unsubscribe(); - } - - @Override - public Subscription unsubscribe(int after) { - return delegate.unsubscribe(after); - } - - @Override - public void setPendingLimits(long maxMessages, long maxBytes) { - delegate.setPendingLimits(maxMessages, maxBytes); - } - - @Override - public long getPendingMessageLimit() { - return delegate.getPendingMessageLimit(); - } - - @Override - public long getPendingByteLimit() { - return delegate.getPendingByteLimit(); - } - - @Override - public long getPendingMessageCount() { - return delegate.getPendingMessageCount(); - } - - @Override - public long getPendingByteCount() { - return delegate.getPendingByteCount(); - } - - @Override - public long getDeliveredCount() { - return delegate.getDeliveredCount(); - } - - @Override - public long getDroppedCount() { - return delegate.getDroppedCount(); - } - - @Override - public void clearDroppedCount() { - delegate.clearDroppedCount(); - } - - @Override - public boolean isActive() { - return delegate.isActive(); - } - - @Override - public CompletableFuture drain(Duration timeout) throws InterruptedException { - return delegate.drain(timeout); - } - - protected Subscription getDelegate() { - return delegate; - } - - private Message wrapNextMessage( - ThrowingSupplier2 nextMessage) - throws InterruptedException { - Timer timer = Timer.start(); - Message message = nextMessage.call(); - - Context parentContext = Context.current(); - TimeoutException timeout = null; - NatsRequest natsRequest = NatsRequest.create(connection, null, getSubject(), null, null); - - if (message == null) { - timeout = new TimeoutException("Timed out waiting for message"); - } else { - natsRequest = NatsRequest.create(connection, message); - } - - if (!consumerReceiveInstrumenter.shouldStart(parentContext, natsRequest)) { - return message; - } - - InstrumenterUtil.startAndEnd( - consumerReceiveInstrumenter, - parentContext, - natsRequest, - null, - timeout, - timer.startTime(), - timer.now()); - - return message; - } -} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java index e9d226343aa5..632d693105f0 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java @@ -9,10 +9,9 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; /** @@ -24,70 +23,56 @@ public final class NatsInstrumenterFactory { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.nats-2.21"; - public static final SpanNameExtractor PRODUCER_SPAN_NAME_EXTRACTOR = - MessagingSpanNameExtractor.create( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PUBLISH); - - public static final AttributesExtractor PRODUCER_ATTRIBUTES_EXTRACTOR = - MessagingAttributesExtractor.create( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PUBLISH); - - public static final SpanNameExtractor CONSUMER_RECEIVE_SPAN_NAME_EXTRACTOR = - MessagingSpanNameExtractor.create( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE); - - public static final AttributesExtractor CONSUMER_RECEIVE_ATTRIBUTES_EXTRACTOR = - MessagingAttributesExtractor.create( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE); - - public static final SpanNameExtractor CONSUMER_PROCESS_SPAN_NAME_EXTRACTOR = - MessagingSpanNameExtractor.create( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PROCESS); - - public static final AttributesExtractor CONSUMER_PROCESS_ATTRIBUTES_EXTRACTOR = - MessagingAttributesExtractor.create( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PROCESS); - - public static final AttributesExtractor CLIENT_ATTRIBUTES_EXTRACTOR = - MessagingAttributesExtractor.create( - NatsRequestMessagingAttributesGetter.NATS_REQUEST_INSTANCE, MessageOperation.PUBLISH); - - public static Instrumenter createProducerInstrumenter( + public static Instrumenter createProducerInstrumenter( OpenTelemetry openTelemetry) { - return Instrumenter.builder( - openTelemetry, INSTRUMENTATION_NAME, PRODUCER_SPAN_NAME_EXTRACTOR) - .addAttributesExtractor(PRODUCER_ATTRIBUTES_EXTRACTOR) + return Instrumenter.builder( + openTelemetry, + INSTRUMENTATION_NAME, + MessagingSpanNameExtractor.create( + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PUBLISH)) + .addAttributesExtractor( // TODO capture headers + MessagingAttributesExtractor.create( + NatsRequestMessagingAttributesGetter.NATS_REQUEST_INSTANCE, + MessageOperation.PUBLISH)) .buildProducerInstrumenter(NatsRequestTextMapSetter.INSTANCE); } public static Instrumenter createConsumerReceiveInstrumenter( - OpenTelemetry openTelemetry) { + OpenTelemetry openTelemetry, boolean enabled) { return Instrumenter.builder( - openTelemetry, INSTRUMENTATION_NAME, CONSUMER_RECEIVE_SPAN_NAME_EXTRACTOR) - .addAttributesExtractor(CONSUMER_RECEIVE_ATTRIBUTES_EXTRACTOR) - .addSpanLinksExtractor( - new PropagatorBasedSpanLinksExtractor<>( - openTelemetry.getPropagators().getTextMapPropagator(), - NatsRequestTextMapGetter.INSTANCE)) - .addContextCustomizer( - new NatsRequestContextCustomizer(openTelemetry.getPropagators().getTextMapPropagator())) + openTelemetry, + INSTRUMENTATION_NAME, + MessagingSpanNameExtractor.create( + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE)) + .addAttributesExtractor( // TODO capture headers + MessagingAttributesExtractor.create( + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE)) + .setEnabled(enabled) .buildConsumerInstrumenter(NatsRequestTextMapGetter.INSTANCE); } public static Instrumenter createConsumerProcessInstrumenter( - OpenTelemetry openTelemetry) { - return Instrumenter.builder( - openTelemetry, INSTRUMENTATION_NAME, CONSUMER_PROCESS_SPAN_NAME_EXTRACTOR) - .addAttributesExtractor(CONSUMER_PROCESS_ATTRIBUTES_EXTRACTOR) - .buildInstrumenter(SpanKindExtractor.alwaysInternal()); - } + OpenTelemetry openTelemetry, boolean messagingReceiveInstrumentationEnabled) { + InstrumenterBuilder builder = + Instrumenter.builder( + openTelemetry, + INSTRUMENTATION_NAME, + MessagingSpanNameExtractor.create( + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PROCESS)) + .addAttributesExtractor( + // TODO capture headers + MessagingAttributesExtractor.create( + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PROCESS)); - public static Instrumenter createClientInstrumenter( - OpenTelemetry openTelemetry) { - return Instrumenter.builder( - openTelemetry, INSTRUMENTATION_NAME, PRODUCER_SPAN_NAME_EXTRACTOR) - .addAttributesExtractor(CLIENT_ATTRIBUTES_EXTRACTOR) - .buildClientInstrumenter(NatsRequestTextMapSetter.INSTANCE); + if (messagingReceiveInstrumentationEnabled) { + builder.addSpanLinksExtractor( + new PropagatorBasedSpanLinksExtractor<>( + openTelemetry.getPropagators().getTextMapPropagator(), + NatsRequestTextMapGetter.INSTANCE)); + return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); + } else { + return builder.buildConsumerInstrumenter(NatsRequestTextMapGetter.INSTANCE); + } } private NatsInstrumenterFactory() {} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestContextCustomizer.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestContextCustomizer.java deleted file mode 100644 index dad1bf6a8b7a..000000000000 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestContextCustomizer.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_21.internal; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.TextMapPropagator; -import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; - -/** - * This * class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time.", or "This class is internal and experimental. Its APIs are unstable and can change at - * any time. Its APIs (or a version of them) may be promoted to the public stable API in the future, - * but no guarantees are made. - */ -public class NatsRequestContextCustomizer implements ContextCustomizer { - private final TextMapPropagator propagator; - - public NatsRequestContextCustomizer(TextMapPropagator propagator) { - this.propagator = propagator; - } - - @Override - /* In case of a `connection.request`, the replyTo will be set to an internal buffer prefixed - * _INBOX.*. In this case we can consider a request-response pattern and a synchronous/short timed - * window. This Customize will set the span as CHILD_OF and inside the same trace - */ - public Context onStart( - Context parentContext, NatsRequest natsRequest, Attributes startAttributes) { - if (!Span.fromContext(parentContext).getSpanContext().isValid() - && natsRequest.getReplyTo() != null - && natsRequest.getReplyTo().startsWith("_INBOX.")) { - return propagator.extract(parentContext, natsRequest, NatsRequestTextMapGetter.INSTANCE); - } - - return parentContext; - } -} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java new file mode 100644 index 000000000000..fef6d1d15995 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class NatsInstrumentationDispatcherTest extends AbstractNatsInstrumentationDispatcherTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + static final NatsTelemetry telemetry; + static final Connection natsConnection; + + static { + telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); + natsConnection = telemetry.wrap(new TestConnection()); + } + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected Connection connection() { + return natsConnection; + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationMessaingReceiveTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationMessaingReceiveTest.java new file mode 100644 index 000000000000..95c01c62aa3d --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationMessaingReceiveTest.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class NatsInstrumentationMessaingReceiveTest + extends AbstractNatsInstrumentationMessagingReceiveTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + static final NatsTelemetry telemetry; + static final Connection natsConnection; + + static { + telemetry = + NatsTelemetry.builder(testing.getOpenTelemetry()) + .setMessagingReceiveInstrumentationEnabled(true) + .build(); + natsConnection = telemetry.wrap(new TestConnection()); + } + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected Connection connection() { + return natsConnection; + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java new file mode 100644 index 000000000000..449905b9f227 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class NatsInstrumentationPublishTest extends AbstractNatsInstrumentationPublishTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + static final NatsTelemetry telemetry; + static final Connection natsConnection; + + static { + telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); + natsConnection = telemetry.wrap(new TestConnection()); + } + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected Connection connection() { + return natsConnection; + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java new file mode 100644 index 000000000000..cf68da2b8abe --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import io.nats.client.Connection; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class NatsInstrumentationRequestTest extends AbstractNatsInstrumentationRequestTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + static final NatsTelemetry telemetry; + static final Connection natsConnection; + + static { + telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); + natsConnection = telemetry.wrap(new TestConnection()); + } + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected Connection connection() { + return natsConnection; + } + + @Override + protected boolean isInboxMonitored() { + // in the library instrumentation, as we're proxying Connection, + // we can not properly instrument the Dispatcher and the MessageHandler + // created for every `request` on the _INBOX.* subjects + return false; + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryDispatcherTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryDispatcherTest.java deleted file mode 100644 index c696d860df64..000000000000 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryDispatcherTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_21; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatNoException; - -import io.nats.client.Dispatcher; -import io.nats.client.MessageHandler; -import io.nats.client.Subscription; -import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsMessage; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import io.opentelemetry.sdk.testing.assertj.TraceAssert; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -@SuppressWarnings("deprecation") // using deprecated semconv -class NatsTelemetryDispatcherTest { - - @RegisterExtension - static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - - static final NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); - - private final MessageHandler handler = msg -> {}; - - @Test - void testSubscribeDefaultHandler() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - Dispatcher d1 = connection.createDispatcher(handler); - d1.subscribe("sub1"); - d1.subscribe("sub2", "queue"); - connection.createDispatcher(handler).subscribe("sub3"); - - // when - testConnection.deliver(NatsMessage.builder().subject("sub1").data("x").build()); - testConnection.deliver(NatsMessage.builder().subject("sub2").data("x").build()); - testConnection.deliver(NatsMessage.builder().subject("sub3").data("x").build()); - - // then - testing.waitAndAssertTraces( - trace -> assertReceiveProcessSpans(trace, "sub1"), - trace -> assertReceiveProcessSpans(trace, "sub2"), - trace -> assertReceiveProcessSpans(trace, "sub3")); - - assertThatNoException() - .isThrownBy( - () -> { - d1.unsubscribe("sub1"); - d1.unsubscribe("sub2", 1); - }); - } - - @Test - void testSubscribeCustomHandler() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - Dispatcher dispatcher = connection.createDispatcher(); - Subscription s1 = dispatcher.subscribe("sub1", handler); - Subscription s2 = dispatcher.subscribe("sub2", "queue", handler); - Subscription s3 = connection.createDispatcher(handler).subscribe("sub3", handler); - - // when - testConnection.deliver(NatsMessage.builder().subject("sub1").data("x").build()); - testConnection.deliver(NatsMessage.builder().subject("sub2").data("x").build()); - testConnection.deliver(NatsMessage.builder().subject("sub3").data("x").build()); - - // then - testing.waitAndAssertTraces( - trace -> assertReceiveProcessSpans(trace, "sub1"), - trace -> assertReceiveProcessSpans(trace, "sub2"), - trace -> assertReceiveProcessSpans(trace, "sub3")); - - // and - assertThatNoException() - .isThrownBy( - () -> { - s1.unsubscribe(); - dispatcher.unsubscribe(s2); - s3.unsubscribe(1); - }); - } - - @Test - void testSpanLink() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - connection.createDispatcher(handler).subscribe("sub"); - - String linkTraceId = "0af7651916cd43dd8448eb211c80319c"; - Headers headers = - new Headers( - new Headers().put("traceparent", "00-" + linkTraceId + "-b7ad6b7169203331-01"), true); - - // when - testConnection.deliver(NatsMessage.builder().subject("sub").data("x").headers(headers).build()); - - // then - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("sub receive") - .hasNoParent() - .hasLinksSatisfying( - links -> - assertThat(links) - .singleElement() - .satisfies( - link -> - assertThat(link.getSpanContext().getTraceId()) - .isEqualTo(linkTraceId))), - span -> span.hasName("sub process").hasParent(trace.getSpan(0)))); - } - - private static void assertReceiveProcessSpans(TraceAssert trace, String subject) { - trace.hasSpansSatisfyingExactly( - span -> - span.hasName(subject + " receive") - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, subject), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")), - span -> - span.hasName(subject + " process") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "process"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, subject), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1"))); - } -} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java deleted file mode 100644 index 86374ed466f0..000000000000 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryPublishTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_21; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import io.nats.client.Message; -import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsMessage; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -@SuppressWarnings("deprecation") // using deprecated semconv -class NatsTelemetryPublishTest { - - @RegisterExtension - static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - - static final NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); - - @Test - void testPublishBody() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - - // when - testing.runWithSpan("parent", () -> connection.publish("sub", new byte[] {0})); - - // then - assertPublishSpan(); - assertNoHeaders(testConnection); - } - - @Test - void testPublishHeadersBody() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - - // when - testing.runWithSpan("parent", () -> connection.publish("sub", new Headers(), new byte[] {0})); - - // then - assertPublishSpan(); - assertTraceparentHeader(testConnection); - } - - @Test - void testPublishReplyToBody() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - - // when - testing.runWithSpan("parent", () -> connection.publish("sub", "rt", new byte[] {0})); - - // then - assertPublishSpan(); - assertNoHeaders(testConnection); - } - - @Test - void testPublishReplyToHeadersBody() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - - // when - testing.runWithSpan( - "parent", () -> connection.publish("sub", "rt", new Headers(), new byte[] {0})); - - // then - assertPublishSpan(); - assertTraceparentHeader(testConnection); - } - - @Test - void testPublishMessage() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); - - // when - testing.runWithSpan("parent", () -> connection.publish(message)); - - // then - assertPublishSpan(); - assertNoHeaders(testConnection); - } - - @Test - void testPublishMessageHeaders() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - NatsMessage message = - NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); - - // when - testing.runWithSpan("parent", () -> connection.publish(message)); - - // then - assertPublishSpan(); - assertTraceparentHeader(testConnection); - } - - private static void assertPublishSpan() { - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub publish") - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "publish"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); - } - - private static void assertNoHeaders(TestConnection connection) { - Message published = connection.publishedMessages.remove(); - assertNotNull(published); - assertThat(published.getHeaders()).isNull(); - } - - private static void assertTraceparentHeader(TestConnection connection) { - Message published = connection.publishedMessages.remove(); - assertNotNull(published); - assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); - } -} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryRequestTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryRequestTest.java deleted file mode 100644 index e04691ecc774..000000000000 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryRequestTest.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_21; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import io.nats.client.Message; -import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsMessage; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import java.time.Duration; -import java.util.concurrent.TimeoutException; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -@SuppressWarnings("deprecation") // using deprecated semconv -class NatsTelemetryRequestTest { - - @RegisterExtension - static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - - static final NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); - - @Test - void testRequestTimeout() throws InterruptedException { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - - // when - testing.runWithSpan( - "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); - - // then - assertPublishSpan(); - assertTimeoutPublishSpan(); - assertNoHeaders(testConnection); - } - - @Test - void testRequestBody() throws InterruptedException { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - - // when - testing.runWithSpan( - "parent", - () -> { - testConnection.requestResponseMessages.offer( - NatsMessage.builder().subject("sub").build()); - connection.request("sub", new byte[] {0}, Duration.ofSeconds(1)); - }); - - // then - assertPublishSpan(); - assertNoHeaders(testConnection); - } - - @Test - void testRequestHeadersBody() throws InterruptedException { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - - // when - testing.runWithSpan( - "parent", - () -> { - testConnection.requestResponseMessages.offer( - NatsMessage.builder().subject("sub").build()); - connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1)); - }); - - // then - assertPublishSpan(); - assertTraceparentHeader(testConnection); - } - - @Test - void testRequestMessage() throws InterruptedException { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); - - // when - testing.runWithSpan( - "parent", - () -> { - testConnection.requestResponseMessages.offer( - NatsMessage.builder().subject("sub").build()); - connection.request(message, Duration.ofSeconds(1)); - }); - - // then - assertPublishSpan(); - assertNoHeaders(testConnection); - } - - @Test - void testRequestMessageHeaders() throws InterruptedException { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - NatsMessage message = - NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); - - // when - testing.runWithSpan( - "parent", - () -> { - testConnection.requestResponseMessages.offer( - NatsMessage.builder().subject("sub").build()); - connection.request(message, Duration.ofSeconds(1)); - }); - - // then - assertPublishSpan(); - assertTraceparentHeader(testConnection); - } - - @Test - void testRequestFutureTimeout() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - - // when - testing.runWithSpan( - "parent", - () -> connection.requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); - - // then - assertPublishSpan(); - assertTimeoutPublishSpan(); - assertNoHeaders(testConnection); - } - - @Test - void testRequestFutureBody() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - - // when - testing.runWithSpan( - "parent", - () -> { - testConnection.requestResponseMessages.offer( - NatsMessage.builder().subject("sub").build()); - connection.request("sub", new byte[] {0}); - }); - - // then - assertPublishSpan(); - assertNoHeaders(testConnection); - } - - @Test - void testRequestFutureHeadersBody() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - - // when - testing.runWithSpan( - "parent", - () -> { - testConnection.requestResponseMessages.offer( - NatsMessage.builder().subject("sub").build()); - connection.request("sub", new Headers(), new byte[] {0}); - }); - - // then - assertPublishSpan(); - assertTraceparentHeader(testConnection); - } - - @Test - void testRequestFutureMessage() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); - - // when - testing.runWithSpan( - "parent", - () -> { - testConnection.requestResponseMessages.offer( - NatsMessage.builder().subject("sub").build()); - connection.request(message); - }); - - // then - assertPublishSpan(); - assertNoHeaders(testConnection); - } - - @Test - void testRequestFutureMessageHeaders() { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - NatsMessage message = - NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); - - // when - testing.runWithSpan( - "parent", - () -> { - testConnection.requestResponseMessages.offer( - NatsMessage.builder().subject("sub").build()); - connection.request(message); - }); - - // then - assertPublishSpan(); - assertTraceparentHeader(testConnection); - } - - private static void assertPublishSpan() { - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub publish") - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "publish"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); - } - - private static void assertTimeoutPublishSpan() { - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent"), - span -> - span.hasName("sub publish") - .hasException(new TimeoutException("Timed out waiting for message")))); - } - - private static void assertNoHeaders(TestConnection connection) { - Message published = connection.requestedMessages.remove(); - assertNotNull(published); - assertThat(published.getHeaders()).isNull(); - } - - private static void assertTraceparentHeader(TestConnection connection) { - Message published = connection.requestedMessages.remove(); - assertNotNull(published); - assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); - } -} diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscriptionTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscriptionTest.java deleted file mode 100644 index 8a28240e6861..000000000000 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetrySubscriptionTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_21; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; -import static org.assertj.core.api.Assertions.assertThat; - -import io.nats.client.Subscription; -import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsMessage; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import java.time.Duration; -import java.util.concurrent.TimeoutException; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -@SuppressWarnings("deprecation") // using deprecated semconv -class NatsTelemetrySubscriptionTest { - - @RegisterExtension - static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - - static final NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); - - @Test - void testNextMessageTimeout() throws InterruptedException { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - Subscription subscription = connection.subscribe("sub"); - - // when - testing.runWithSpan("parent", () -> subscription.nextMessage(Duration.ofSeconds(1))); - - // then - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) - .hasException(new TimeoutException("Timed out waiting for message")) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 0), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); - } - - @Test - void testNextMessage() throws InterruptedException { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - Subscription subscription = connection.subscribe("sub"); - - // when - testConnection.deliver(NatsMessage.builder().subject("sub").data("x").build()); - testing.runWithSpan("parent", () -> subscription.nextMessage(Duration.ofSeconds(1))); - - // then - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); - } - - @SuppressWarnings("PreferJavaTimeOverload") - @Test - void testNextMessageLink() throws InterruptedException { - // given - TestConnection testConnection = new TestConnection(); - OpenTelemetryConnection connection = telemetry.wrap(testConnection); - Subscription subscription = connection.subscribe("sub"); - String linkTraceId = "0af7651916cd43dd8448eb211c80319c"; - Headers headers = - new Headers( - new Headers().put("traceparent", "00-" + linkTraceId + "-b7ad6b7169203331-01"), true); - - // when - testConnection.deliver(NatsMessage.builder().subject("sub").data("x").headers(headers).build()); - testing.runWithSpan("parent", () -> subscription.nextMessage(1000)); - - // then - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) - .hasLinksSatisfying( - links -> - assertThat(links) - .singleElement() - .satisfies( - link -> - assertThat(link.getSpanContext().getTraceId()) - .isEqualTo(linkTraceId))) - .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_OPERATION, "receive"), - equalTo(MESSAGING_SYSTEM, "nats"), - equalTo(MESSAGING_DESTINATION_NAME, "sub"), - equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), "1")))); - } -} diff --git a/instrumentation/nats/nats-2.21/metadata.yaml b/instrumentation/nats/nats-2.21/metadata.yaml index c386bbe8692a..a53895dcc409 100644 --- a/instrumentation/nats/nats-2.21/metadata.yaml +++ b/instrumentation/nats/nats-2.21/metadata.yaml @@ -1,7 +1,7 @@ disabled_by_default: true -description: > - TODO +description: This instrumentation provides messaging spans for NATS configurations: - - name: TODO - description: TODO + - name: otel.instrumentation.messaging.experimental.receive-telemetry.enabled + description: Enables experimental receive telemetry for NATS instrumentation. + type: boolean default: false diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationDispatcherTest.java new file mode 100644 index 000000000000..c6188f13aa33 --- /dev/null +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationDispatcherTest.java @@ -0,0 +1,111 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.messagingAttributes; +import static org.assertj.core.api.Assertions.assertThatNoException; + +import io.nats.client.Connection; +import io.nats.client.Dispatcher; +import io.nats.client.MessageHandler; +import io.nats.client.Subscription; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("deprecation") // using deprecated semconv +public abstract class AbstractNatsInstrumentationDispatcherTest { + + protected abstract InstrumentationExtension testing(); + + protected abstract Connection connection(); + + private int clientId; + + @BeforeEach + void beforeEach() { + clientId = connection().getServerInfo().getClientId(); + } + + @Test + void testSubscribe() { + // given + MessageHandler handler = msg -> {}; + // global message handler + Dispatcher d1 = connection().createDispatcher(handler).subscribe("sub"); + // per-subscription message handler + Dispatcher d2 = connection().createDispatcher(); + Subscription s1 = d2.subscribe("sub", handler); + Subscription s2 = d2.subscribe("sub", "queue", handler); + + // when + testing() + .runWithSpan( + "parent", + () -> { + NatsMessage.Builder builder = NatsMessage.builder().subject("sub").data("x"); + connection().publish(builder.build()); // no propagation + connection().publish(builder.headers(new Headers()).build()); // propagation + }); + + // then 4 traces + // - parent + 2 publish + 3 process (propagation) + // - process (no propagation) (*3) + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)), + span -> + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(2)), + span -> + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(2)), + span -> + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(2))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("sub process").hasKind(SpanKind.CONSUMER).hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("sub process").hasKind(SpanKind.CONSUMER).hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasAttributesSatisfyingExactly( + messagingAttributes("process", "sub", clientId)))); + + // finally, to make sure we're unwrapping properly the + // OpenTelemetryDispatcher in the library + assertThatNoException() + .isThrownBy( + () -> { + d2.unsubscribe(s1); + d2.unsubscribe(s2); + connection().closeDispatcher(d1); + connection().closeDispatcher(d2); + }); + } +} diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationMessagingReceiveTest.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationMessagingReceiveTest.java new file mode 100644 index 000000000000..739589f2a9d8 --- /dev/null +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationMessagingReceiveTest.java @@ -0,0 +1,86 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.messagingAttributes; +import static org.assertj.core.api.Assertions.assertThat; + +import io.nats.client.Connection; +import io.nats.client.Dispatcher; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("deprecation") // using deprecated semconv +public abstract class AbstractNatsInstrumentationMessagingReceiveTest { + + protected abstract InstrumentationExtension testing(); + + protected abstract Connection connection(); + + private int clientId; + + @BeforeEach + void beforeEach() { + clientId = connection().getServerInfo().getClientId(); + } + + @Test + void testSubscribe() { + // given + Dispatcher dispatcher = connection().createDispatcher(msg -> {}).subscribe("sub"); + + // when + String traceId = + testing() + .runWithSpan( + "parent", + () -> { + connection() + .publish( + NatsMessage.builder() + .subject("sub") + .headers(new Headers()) + .data("x") + .build()); + return Span.fromContext(Context.current()).getSpanContext().getTraceId(); + }); + connection().closeDispatcher(dispatcher); + + // then + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> span.hasName("sub publish").hasParent(trace.getSpan(0))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasAttributesSatisfyingExactly( + messagingAttributes("receive", "sub", clientId)), + span -> + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasLinksSatisfying( + links -> + assertThat(links) + .singleElement() + .satisfies( + link -> + assertThat(link.getSpanContext().getTraceId()) + .isEqualTo(traceId))))); + } +} diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationPublishTest.java new file mode 100644 index 000000000000..add744124062 --- /dev/null +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationPublishTest.java @@ -0,0 +1,125 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.assertNoHeaders; +import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.assertTraceparentHeader; +import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.messagingAttributes; + +import io.nats.client.Connection; +import io.nats.client.Subscription; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.time.Duration; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("deprecation") // using deprecated semconv +public abstract class AbstractNatsInstrumentationPublishTest { + + protected abstract InstrumentationExtension testing(); + + protected abstract Connection connection(); + + private int clientId; + private Subscription subscription; + + @BeforeEach + void beforeEach() { + clientId = connection().getServerInfo().getClientId(); + subscription = connection().subscribe("sub"); + } + + @AfterEach + void afterEach() throws InterruptedException { + subscription.drain(Duration.ofSeconds(1)); + } + + @Test + void testPublishBody() throws InterruptedException { + // when + testing().runWithSpan("parent", () -> connection().publish("sub", new byte[] {0})); + + // then + assertPublishSpan(); + assertNoHeaders(subscription); + } + + @Test + void testPublishHeadersBody() throws InterruptedException { + // when + testing() + .runWithSpan("parent", () -> connection().publish("sub", new Headers(), new byte[] {0})); + + // then + assertPublishSpan(); + assertTraceparentHeader(subscription); + } + + @Test + void testPublishReplyToBody() throws InterruptedException { + // when + testing().runWithSpan("parent", () -> connection().publish("sub", "rt", new byte[] {0})); + + // then + assertPublishSpan(); + assertNoHeaders(subscription); + } + + @Test + void testPublishReplyToHeadersBody() throws InterruptedException { + // when + testing() + .runWithSpan( + "parent", () -> connection().publish("sub", "rt", new Headers(), new byte[] {0})); + + // then + assertPublishSpan(); + assertTraceparentHeader(subscription); + } + + @Test + void testPublishMessage() throws InterruptedException { + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + + // when + testing().runWithSpan("parent", () -> connection().publish(message)); + + // then + assertPublishSpan(); + assertNoHeaders(subscription); + } + + @Test + void testPublishMessageWithHeaders() throws InterruptedException { + NatsMessage message = + NatsMessage.builder().subject("sub").data("x").headers(new Headers()).build(); + + // when + testing().runWithSpan("parent", () -> connection().publish(message)); + + // then + assertPublishSpan(); + assertTraceparentHeader(subscription); + } + + private void assertPublishSpan() { + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + messagingAttributes("publish", "sub", clientId)))); + } +} diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationRequestTest.java new file mode 100644 index 000000000000..1a0e022b5e3c --- /dev/null +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationRequestTest.java @@ -0,0 +1,433 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.assertNoHeaders; +import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.assertTraceparentHeader; +import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.messagingAttributes; +import static java.util.Arrays.asList; + +import io.nats.client.Connection; +import io.nats.client.Dispatcher; +import io.nats.client.Subscription; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.testing.assertj.TraceAssert; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("deprecation") // using deprecated semconv +public abstract class AbstractNatsInstrumentationRequestTest { + + protected abstract InstrumentationExtension testing(); + + protected abstract Connection connection(); + + protected abstract boolean isInboxMonitored(); + + private int clientId; + private Subscription subscription; + + @BeforeEach + void beforeEach() { + clientId = connection().getServerInfo().getClientId(); + subscription = connection().subscribe("sub"); + } + + @AfterEach + void afterEach() throws InterruptedException { + subscription.drain(Duration.ofSeconds(1)); + } + + @Test + void testRequestTimeout() throws InterruptedException { + // when + testing() + .runWithSpan( + "parent", () -> connection().request("sub", new byte[] {0}, Duration.ofSeconds(1))); + + // then + assertTimeoutPublishSpan(); + assertNoHeaders(subscription); + } + + @Test + void testRequestBody() throws InterruptedException { + // given + Dispatcher dispatcher = + connection() + .createDispatcher(m -> connection().publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); + + // when + testing() + .runWithSpan( + "parent", () -> connection().request("sub", new byte[] {0}, Duration.ofSeconds(1))); + connection().closeDispatcher(dispatcher); + + // then + assertPublishReceiveSpans(); + assertNoHeaders(subscription); + } + + @Test + void testRequestHeadersBody() throws InterruptedException { + // given + Dispatcher dispatcher = + connection() + .createDispatcher(m -> connection().publish(m.getReplyTo(), new Headers(), m.getData())) + .subscribe("sub"); + + // when + testing() + .runWithSpan( + "parent", + () -> + connection().request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + connection().closeDispatcher(dispatcher); + + // then + assertPublishReceiveSpansSameTrace(); + assertTraceparentHeader(subscription); + } + + @Test + void testRequestMessage() throws InterruptedException { + // given + Dispatcher dispatcher = + connection() + .createDispatcher(m -> connection().publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + + // when + testing().runWithSpan("parent", () -> connection().request(message, Duration.ofSeconds(1))); + connection().closeDispatcher(dispatcher); + + // then + assertPublishReceiveSpans(); + assertNoHeaders(subscription); + } + + @Test + void testRequestMessageHeaders() throws InterruptedException { + // given + Dispatcher dispatcher = + connection() + .createDispatcher(m -> connection().publish(m.getReplyTo(), new Headers(), m.getData())) + .subscribe("sub"); + NatsMessage message = + NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + + // when + testing().runWithSpan("parent", () -> connection().request(message, Duration.ofSeconds(1))); + connection().closeDispatcher(dispatcher); + + // then + assertPublishReceiveSpansSameTrace(); + assertTraceparentHeader(subscription); + } + + @Test + void testRequestFutureBody() throws InterruptedException { + // given + Dispatcher dispatcher = + connection() + .createDispatcher(m -> connection().publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); + + // when + testing() + .runWithSpan("parent", () -> connection().request("sub", new byte[] {0})) + .whenComplete((m, e) -> connection().closeDispatcher(dispatcher)); + + // then + assertPublishReceiveSpans(); + assertNoHeaders(subscription); + } + + @Test + void testRequestFutureHeadersBody() throws InterruptedException { + // given + Dispatcher dispatcher = + connection() + .createDispatcher(m -> connection().publish(m.getReplyTo(), new Headers(), m.getData())) + .subscribe("sub"); + + // when + testing() + .runWithSpan("parent", () -> connection().request("sub", new Headers(), new byte[] {0})) + .whenComplete((m, e) -> connection().closeDispatcher(dispatcher)); + + // then + assertPublishReceiveSpansSameTrace(); + assertTraceparentHeader(subscription); + } + + @Test + void testRequestFutureMessage() throws InterruptedException { + // given + Dispatcher dispatcher = + connection() + .createDispatcher(m -> connection().publish(m.getReplyTo(), m.getData())) + .subscribe("sub"); + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + + // when + testing() + .runWithSpan("parent", () -> connection().request(message)) + .whenComplete((m, e) -> connection().closeDispatcher(dispatcher)); + + // then + assertPublishReceiveSpans(); + assertNoHeaders(subscription); + } + + @Test + void testRequestFutureMessageHeaders() throws InterruptedException { + // given + Dispatcher dispatcher = + connection() + .createDispatcher(m -> connection().publish(m.getReplyTo(), new Headers(), m.getData())) + .subscribe("sub"); + NatsMessage message = + NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + + // when + testing() + .runWithSpan("parent", () -> connection().request(message)) + .whenComplete((m, e) -> connection().closeDispatcher(dispatcher)); + + // then + assertPublishReceiveSpansSameTrace(); + assertTraceparentHeader(subscription); + } + + @Test + void testRequestTimeoutFutureBody() throws InterruptedException { + // when + testing() + .runWithSpan( + "parent", + () -> connection().requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); + + // then + assertCancellationPublishSpan(); + assertNoHeaders(subscription); + } + + @Test + void testRequestTimeoutFutureHeadersBody() throws InterruptedException { + // when + testing() + .runWithSpan( + "parent", + () -> + connection() + .requestWithTimeout( + "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + + // then + assertCancellationPublishSpan(); + assertTraceparentHeader(subscription); + } + + @Test + void testRequestTimeoutFutureMessage() throws InterruptedException { + // given + NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); + + // when + testing() + .runWithSpan( + "parent", () -> connection().requestWithTimeout(message, Duration.ofSeconds(1))); + + // then + assertCancellationPublishSpan(); + assertNoHeaders(subscription); + } + + @Test + void testRequestTimeoutFutureMessageHeaders() throws InterruptedException { + // given + NatsMessage message = + NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); + + // when + testing() + .runWithSpan( + "parent", () -> connection().requestWithTimeout(message, Duration.ofSeconds(1))); + + // then + assertCancellationPublishSpan(); + assertTraceparentHeader(subscription); + } + + private void assertPublishReceiveSpans() { + List> asserts = + new ArrayList<>( + asList( + // publisher: parent + publish + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + messagingAttributes("publish", "sub", clientId))), + // subscriber: receive + process + publish(response) (another trace because no + // propagation) + trace -> + trace.hasSpansSatisfyingExactly( + // span -> span.hasName("sub + // receive").hasKind(SpanKind.CONSUMER).hasNoParent(), + span -> + span.hasName("sub process").hasKind(SpanKind.CONSUMER).hasNoParent(), + span -> + span.has( + new Condition<>( + data -> + data.getName().startsWith("_INBOX.") + && data.getName().endsWith(" publish"), + "Name condition")) + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0))), + // publisher: receive + process (another trace because no propagation) + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.has( + new Condition<>( + data -> + data.getName().startsWith("_INBOX.") + && data.getName().endsWith(" process"), + "Name condition")) + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasAttributesSatisfyingExactly( + messagingAttributes("process", "_INBOX.", clientId))))); + if (!isInboxMonitored()) { + /*asserts.add(span -> + span.has( + new Condition<>( + data -> + data.getName().startsWith("_INBOX.") + && data.getName().endsWith(" receive"), + "Name condition")) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + messagingAttributes("receive", "_INBOX.", clientId)));*/ + asserts.remove(asserts.size() - 1); + } + testing().waitAndAssertTraces(asserts); + } + + private void assertPublishReceiveSpansSameTrace() { + testing() + .waitAndAssertTraces( + trace -> { + List> asserts = + new ArrayList<>( + asList( + // publisher: parent + publish + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + messagingAttributes("publish", "sub", clientId)), + // dispatcher receive, process, publish, not retesting all properties + span -> + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)), + span -> + span.has( + new Condition<>( + data -> + data.getName().startsWith("_INBOX.") + && data.getName().endsWith(" publish"), + "Name condition")) + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(2)), + // publisher: receive + process + span -> + span.has( + new Condition<>( + data -> + data.getName().startsWith("_INBOX.") + && data.getName().endsWith(" process"), + "Name condition")) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(3)) + .hasAttributesSatisfyingExactly( + messagingAttributes("process", "_INBOX.", clientId)))); + + /*asserts.add(span -> + span.hasName("sub receive") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)));*/ + // end dispatcher + + if (!isInboxMonitored()) { + /*asserts.add(span -> + span.has( + new Condition<>( + data -> + data.getName().startsWith("_INBOX.") + && data.getName().endsWith(" receive"), + "Name condition")) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + messagingAttributes("receive", "_INBOX.", clientId)));*/ + asserts.remove(asserts.size() - 1); + } + + trace.hasSpansSatisfyingExactly(asserts); + }); + } + + private void assertTimeoutPublishSpan() { + assertExceptionPublishSpan(new TimeoutException("Timed out waiting for message")); + } + + private void assertCancellationPublishSpan() { + assertExceptionPublishSpan( + new CancellationException( + "Future cancelled, response not registered in time, check connection status.")); + } + + private void assertExceptionPublishSpan(Throwable exception) { + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasException(exception) + .hasAttributesSatisfyingExactly( + messagingAttributes("publish", "sub", clientId)))); + } +} diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationTestHelper.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationTestHelper.java new file mode 100644 index 000000000000..edf50d840e24 --- /dev/null +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationTestHelper.java @@ -0,0 +1,53 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_21; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; +import static org.assertj.core.api.Assertions.assertThat; + +import io.nats.client.Message; +import io.nats.client.Subscription; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import java.time.Duration; + +@SuppressWarnings("deprecation") // using deprecated semconv +public class NatsInstrumentationTestHelper { + + public static AttributeAssertion[] messagingAttributes( + String operation, String subject, int clientId) { + AttributeAssertion destinationName = equalTo(MESSAGING_DESTINATION_NAME, subject); + if (subject.startsWith("_INBOX.")) { + destinationName = satisfies(MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")); + } + + return new AttributeAssertion[] { + equalTo(MESSAGING_OPERATION, operation), + equalTo(MESSAGING_SYSTEM, "nats"), + destinationName, + equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), + equalTo(AttributeKey.stringKey("messaging.client_id"), String.valueOf(clientId)) + }; + } + + public static void assertNoHeaders(Subscription subscription) throws InterruptedException { + Message published = subscription.nextMessage(Duration.ofSeconds(1)); + assertThat(published.getHeaders()).isNull(); + } + + public static void assertTraceparentHeader(Subscription subscription) + throws InterruptedException { + Message published = subscription.nextMessage(Duration.ofSeconds(1)); + assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); + } + + private NatsInstrumentationTestHelper() {} +} diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java index 4359794887b7..6590ff3bc534 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java @@ -19,6 +19,7 @@ import io.nats.client.KeyValueOptions; import io.nats.client.Message; import io.nats.client.MessageHandler; +import io.nats.client.NUID; import io.nats.client.ObjectStore; import io.nats.client.ObjectStoreManagement; import io.nats.client.ObjectStoreOptions; @@ -29,6 +30,7 @@ import io.nats.client.api.ServerInfo; import io.nats.client.impl.Headers; import io.nats.client.impl.NatsMessage; +import io.nats.client.support.NatsRequestCompletableFuture; import java.io.IOException; import java.net.InetAddress; import java.time.Duration; @@ -36,45 +38,60 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.Queue; +import java.util.Map; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class TestConnection implements Connection { + public static final String INBOX_PREFIX = "_INBOX."; + + private final NUID nuid = new NUID(); + private final ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1); + private final List subscriptions = Collections.synchronizedList(new LinkedList<>()); private final List dispatchers = Collections.synchronizedList(new LinkedList<>()); - public final Queue publishedMessages = new ConcurrentLinkedQueue<>(); - public final Queue requestedMessages = new ConcurrentLinkedQueue<>(); - public final Queue requestResponseMessages = new ConcurrentLinkedQueue<>(); + private final Map pending = new ConcurrentHashMap<>(); - public void deliver(Message message) { - subscriptions.forEach(subscription -> subscription.deliver(message, null)); - dispatchers.forEach(dispatcher -> dispatcher.deliver(message)); + public TestConnection() { + TestDispatcher inboxDispatcher = + new TestDispatcher( + msg -> { + NatsRequestCompletableFuture res = pending.remove(msg.getSubject()); + if (res != null) { + res.complete(msg); + } + }); + inboxDispatcher.subscribe(INBOX_PREFIX); + dispatchers.add(inboxDispatcher); } @Override public void publish(String subject, byte[] body) { - this.publish(NatsMessage.builder().subject(subject).data(body).build()); + publish(NatsMessage.builder().subject(subject).data(body).build()); } @Override public void publish(String subject, Headers headers, byte[] body) { - this.publish(NatsMessage.builder().subject(subject).headers(headers).data(body).build()); + publish(NatsMessage.builder().subject(subject).headers(headers).data(body).build()); } @Override public void publish(String subject, String replyTo, byte[] body) { - this.publish(NatsMessage.builder().subject(subject).replyTo(replyTo).data(body).build()); + publish(NatsMessage.builder().subject(subject).replyTo(replyTo).data(body).build()); } @Override public void publish(String subject, String replyTo, Headers headers, byte[] body) { - this.publish( + publish( NatsMessage.builder() .subject(subject) .replyTo(replyTo) @@ -85,77 +102,98 @@ public void publish(String subject, String replyTo, Headers headers, byte[] body @Override public void publish(Message message) { - publishedMessages.add(message); + internalPublish(message); + } + + private void internalPublish(Message message) { + // simulate async boundary, avoids passing Context between publish/dispatcher in testing + new Thread( + () -> { + TestMessage msg = new TestMessage(this, null, message); + subscriptions.forEach(sub -> sub.deliver(msg.setSubscription(sub), null)); + dispatchers.forEach(dispatcher -> dispatcher.deliver(msg)); + }) + .start(); } @Override public Message request(String subject, byte[] body, Duration timeout) throws InterruptedException { - return this.request(NatsMessage.builder().subject(subject).data(body).build(), timeout); + return request(NatsMessage.builder().subject(subject).data(body).build(), timeout); } @Override public Message request(String subject, Headers headers, byte[] body, Duration timeout) throws InterruptedException { - return this.request( + return request( NatsMessage.builder().subject(subject).headers(headers).data(body).build(), timeout); } @Override public Message request(Message message, Duration timeout) throws InterruptedException { - this.requestedMessages.add(message); - return requestResponseMessages.peek(); + try { + return request(message).get(timeout.toMillis(), TimeUnit.MILLISECONDS); + } catch (ExecutionException | TimeoutException | CancellationException e) { + return null; + } } @Override public CompletableFuture request(String subject, byte[] body) { - return this.request(NatsMessage.builder().subject(subject).data(body).build()); + return request(NatsMessage.builder().subject(subject).data(body).build()); } @Override public CompletableFuture request(String subject, Headers headers, byte[] body) { - return this.request(NatsMessage.builder().subject(subject).headers(headers).data(body).build()); + return request(NatsMessage.builder().subject(subject).headers(headers).data(body).build()); } @Override public CompletableFuture request(Message message) { - this.requestedMessages.add(message); - Message response = requestResponseMessages.peek(); - - if (response != null) { - return CompletableFuture.completedFuture(message); - } - - CompletableFuture future = new CompletableFuture<>(); - future.completeExceptionally(new TimeoutException("Timed out waiting for message")); - return future; + return requestWithTimeout(message, null); } @Override public CompletableFuture requestWithTimeout( String subject, byte[] body, Duration timeout) { - return this.requestWithTimeout( - NatsMessage.builder().subject(subject).data(body).build(), timeout); + return requestWithTimeout(NatsMessage.builder().subject(subject).data(body).build(), timeout); } @Override public CompletableFuture requestWithTimeout( String subject, Headers headers, byte[] body, Duration timeout) { - return this.requestWithTimeout( + return requestWithTimeout( NatsMessage.builder().subject(subject).headers(headers).data(body).build(), timeout); } @Override public CompletableFuture requestWithTimeout(Message message, Duration timeout) { - this.requestedMessages.add(message); - Message response = requestResponseMessages.peek(); + NatsRequestCompletableFuture future = + new NatsRequestCompletableFuture( + NatsRequestCompletableFuture.CancelAction.CANCEL, timeout, false); + + String inbox = createInbox(); + pending.put(inbox, future); + + internalPublish( + NatsMessage.builder() + .subject(message.getSubject()) + .replyTo(inbox) + .headers(message.getHeaders()) + .data(message.getData()) + .build()); - if (response != null) { - return CompletableFuture.completedFuture(message); + if (timeout != null) { + scheduler.schedule( + () -> { + if (!future.isDone()) { + pending.remove(inbox).cancelTimedOut(); + } + }, + timeout.toMillis(), + TimeUnit.MILLISECONDS); } - CompletableFuture future = new CompletableFuture<>(); - future.completeExceptionally(new TimeoutException("Timed out waiting for message")); return future; } @@ -189,7 +227,12 @@ public Dispatcher createDispatcher() { @Override public void closeDispatcher(Dispatcher dispatcher) { - dispatchers.remove(dispatcher); + if (dispatcher instanceof TestDispatcher && dispatchers.contains((TestDispatcher) dispatcher)) { + dispatchers.remove(dispatcher); + return; + } + + throw new IllegalArgumentException("Unexpected dispatcher: " + dispatcher); } @Override @@ -281,7 +324,7 @@ public void clearLastError() {} @Override public String createInbox() { - return ""; + return INBOX_PREFIX + nuid.next(); } @Override diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java index fb30f4d832d1..5ec6ad692432 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java @@ -5,8 +5,9 @@ package io.opentelemetry.instrumentation.nats.v2_21; +import static io.opentelemetry.instrumentation.nats.v2_21.TestConnection.INBOX_PREFIX; + import io.nats.client.Dispatcher; -import io.nats.client.Message; import io.nats.client.MessageHandler; import io.nats.client.Subscription; import java.time.Duration; @@ -25,7 +26,7 @@ public class TestDispatcher implements Dispatcher { Collections.synchronizedMap(new HashMap<>()); public TestDispatcher() { - this.defaultHandler = msg -> {}; + defaultHandler = msg -> {}; } public TestDispatcher(MessageHandler defaultHandler) { @@ -33,17 +34,18 @@ public TestDispatcher(MessageHandler defaultHandler) { } @SuppressWarnings("EmptyCatch") - public void deliver(Message message) { + public void deliver(TestMessage message) { subjects.forEach( subject -> { - if (message.getSubject().equalsIgnoreCase(subject)) { + if (message.getSubject().equalsIgnoreCase(subject) + || (subject.equals(INBOX_PREFIX) && message.getSubject().startsWith(INBOX_PREFIX))) { try { defaultHandler.onMessage(message); } catch (InterruptedException ignored) { } } }); - subscriptions.forEach((subscription, handler) -> subscription.deliver(message, handler)); + subscriptions.forEach((sub, handler) -> sub.deliver(message.setSubscription(sub), handler)); } @Override @@ -86,7 +88,8 @@ public Dispatcher unsubscribe(String subject) { @Override public Dispatcher unsubscribe(Subscription subscription) { - if (subscription instanceof TestSubscription) { + if (subscription instanceof TestSubscription + && subscriptions.containsKey((TestSubscription) subscription)) { subscriptions.remove(subscription); return this; } @@ -104,7 +107,8 @@ public Dispatcher unsubscribe(String subject, int after) { @Override public Dispatcher unsubscribe(Subscription subscription, int after) { - if (subscription instanceof TestSubscription) { + if (subscription instanceof TestSubscription + && subscriptions.containsKey((TestSubscription) subscription)) { subscriptions.remove(subscription); return this; } diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestMessage.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestMessage.java index 1c28bb17a119..c4562380d0be 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestMessage.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestMessage.java @@ -18,7 +18,7 @@ public class TestMessage implements Message { private final Connection connection; - private final Subscription subscription; + private Subscription subscription; private final Message message; public TestMessage(Connection connection, Subscription subscription, Message message) { @@ -132,4 +132,9 @@ public void inProgress() { public boolean isJetStream() { return message.isJetStream(); } + + public TestMessage setSubscription(Subscription subscription) { + this.subscription = subscription; + return this; + } } diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java index b3efcd9ee99b..79638a925c23 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java @@ -39,8 +39,8 @@ public TestSubscription(String subject, String queueName, Dispatcher dispatcher) this.dispatcher = dispatcher; } - @SuppressWarnings("EmptyCatch") - public void deliver(Message message, MessageHandler handler) { + @SuppressWarnings({"EmptyCatchBlock", "EmptyCatch"}) + public void deliver(TestMessage message, MessageHandler handler) { if (message.getSubject().equalsIgnoreCase(getSubject())) { messages.add(message); if (handler != null) { From 5c4d6da9d3d3882cbe2e3144b0c8ae0b1db5fd60 Mon Sep 17 00:00:00 2001 From: Alix Date: Sat, 21 Jun 2025 17:00:07 +0200 Subject: [PATCH 12/34] fix muzzle --- instrumentation/nats/nats-2.21/javaagent/build.gradle.kts | 2 +- .../nats/v2_21/internal/NatsInstrumenterFactory.java | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts index f34d83b3d5d0..d06bf441734e 100644 --- a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts @@ -6,7 +6,7 @@ muzzle { pass { group.set("io.nats") module.set("jnats") - versions.set("[2.21.0,)") + versions.set("[2.17.2,)") assertInverse.set(true) } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java index 632d693105f0..28f9c24adc65 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java @@ -30,7 +30,7 @@ public static Instrumenter createProducerInstrumenter( INSTRUMENTATION_NAME, MessagingSpanNameExtractor.create( NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PUBLISH)) - .addAttributesExtractor( // TODO capture headers + .addAttributesExtractor( MessagingAttributesExtractor.create( NatsRequestMessagingAttributesGetter.NATS_REQUEST_INSTANCE, MessageOperation.PUBLISH)) @@ -44,7 +44,7 @@ public static Instrumenter createConsumerReceiveInstrumenter( INSTRUMENTATION_NAME, MessagingSpanNameExtractor.create( NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE)) - .addAttributesExtractor( // TODO capture headers + .addAttributesExtractor( MessagingAttributesExtractor.create( NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE)) .setEnabled(enabled) @@ -60,7 +60,6 @@ public static Instrumenter createConsumerProcessInstrumenter( MessagingSpanNameExtractor.create( NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PROCESS)) .addAttributesExtractor( - // TODO capture headers MessagingAttributesExtractor.create( NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PROCESS)); From 7d1b355fae1b5d51ea6da5b9677eed80adc95feb Mon Sep 17 00:00:00 2001 From: Alix Date: Sun, 22 Jun 2025 08:48:42 +0200 Subject: [PATCH 13/34] add captured-headers --- docs/supported-libraries.md | 2 +- .../nats/nats-2.21/javaagent/build.gradle.kts | 9 +++-- .../nats/v2_21/NatsSingletons.java | 15 ++++--- ... NatsInstrumentationExperimentalTest.java} | 5 +-- .../nats/v2_21/NatsTelemetryBuilder.java | 18 +++++++-- .../internal/NatsInstrumenterFactory.java | 30 +++++++++----- ...questMessagingAttributesGetterFactory.java | 4 +- ... NatsInstrumentationExperimentalTest.java} | 6 ++- ...tNatsInstrumentationExperimentalTest.java} | 36 ++++++++++++----- ...bstractNatsInstrumentationRequestTest.java | 40 +++---------------- .../v2_21/NatsInstrumentationTestHelper.java | 9 +++++ 11 files changed, 98 insertions(+), 76 deletions(-) rename instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/{NatsInstrumentationMessagingReceiveTest.java => NatsInstrumentationExperimentalTest.java} (92%) rename instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/{NatsInstrumentationMessaingReceiveTest.java => NatsInstrumentationExperimentalTest.java} (82%) rename instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/{AbstractNatsInstrumentationMessagingReceiveTest.java => AbstractNatsInstrumentationExperimentalTest.java} (68%) diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 0a584bef38af..0aa37f062d33 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -105,7 +105,7 @@ These are the supported libraries and frameworks: | [Micrometer](https://micrometer.io/) | 1.5+ (disabled by default) | [opentelemetry-micrometer-1.5](../instrumentation/micrometer/micrometer-1.5/library) | none | | [MongoDB Driver](https://mongodb.github.io/mongo-java-driver/) | 3.1+ | [opentelemetry-mongo-3.1](../instrumentation/mongo/mongo-3.1/library) | [Database Client Spans], [Database Client Metrics] [6] | | [MyBatis](https://mybatis.org/mybatis-3/) | 3.2+ | N/A | none | -| [NATS](https://github.com/nats-io/nats.java) | 3.8+ | [nats-2.21](../instrumentation/nats/nats-2.21/library) | [Messaging Spans] | +| [NATS](https://github.com/nats-io/nats.java) | 2.17.2+ | [nats-2.21](../instrumentation/nats/nats-2.21/library) | [Messaging Spans] | | [Netty HTTP codec [5]](https://github.com/netty/netty) | 3.8+ | [opentelemetry-netty-4.1](../instrumentation/netty/netty-4.1/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] | | [OpenSearch Rest Client](https://github.com/opensearch-project/opensearch-java) | 1.0+ | | [Database Client Spans], [Database Client Metrics] [6] | | [OkHttp](https://github.com/square/okhttp/) | 2.2+ | [opentelemetry-okhttp-3.0](../instrumentation/okhttp/okhttp-3.0/library) | [HTTP Client Spans], [HTTP Client Metrics] | diff --git a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts index d06bf441734e..4b759a78d06c 100644 --- a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts @@ -19,21 +19,22 @@ dependencies { } tasks { - val testMessagingReceive by registering(Test::class) { + val testExperimental by registering(Test::class) { filter { - includeTestsMatching("NatsInstrumentationMessagingReceiveTest") + includeTestsMatching("NatsInstrumentationExperimentalTest") } jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") + jvmArgs("-Dotel.instrumentation.messaging.experimental.capture-headers=captured-header") } test { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) filter { - excludeTestsMatching("NatsInstrumentationMessagingReceiveTest") + excludeTestsMatching("NatsInstrumentationExperimentalTest") } } check { - dependsOn(testMessagingReceive) + dependsOn(testExperimental) } } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java index cfe8ed2bd23e..178806563a13 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java @@ -11,25 +11,28 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig; +import java.util.List; public final class NatsSingletons { private static final boolean messagingReceiveInstrumentationEnabled = - ConfigPropertiesUtil.getBoolean( - "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", false); + ExperimentalConfig.get().messagingReceiveInstrumentationEnabled(); + + private static final List capturedHeaders = + ExperimentalConfig.get().getMessagingHeaders(); public static final Instrumenter PRODUCER_INSTRUMENTER = - createProducerInstrumenter(GlobalOpenTelemetry.get()); + createProducerInstrumenter(GlobalOpenTelemetry.get(), capturedHeaders); public static final Instrumenter CONSUMER_RECEIVE_INSTRUMENTER = createConsumerReceiveInstrumenter( - GlobalOpenTelemetry.get(), messagingReceiveInstrumentationEnabled); + GlobalOpenTelemetry.get(), messagingReceiveInstrumentationEnabled, capturedHeaders); public static final Instrumenter CONSUMER_PROCESS_INSTRUMENTER = createConsumerProcessInstrumenter( - GlobalOpenTelemetry.get(), messagingReceiveInstrumentationEnabled); + GlobalOpenTelemetry.get(), messagingReceiveInstrumentationEnabled, capturedHeaders); private NatsSingletons() {} } diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationMessagingReceiveTest.java b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationExperimentalTest.java similarity index 92% rename from instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationMessagingReceiveTest.java rename to instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationExperimentalTest.java index 6b3ffc89da48..180a8df9d191 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationMessagingReceiveTest.java +++ b/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationExperimentalTest.java @@ -7,7 +7,7 @@ import io.nats.client.Connection; import io.nats.client.Nats; -import io.opentelemetry.instrumentation.nats.v2_21.AbstractNatsInstrumentationMessagingReceiveTest; +import io.opentelemetry.instrumentation.nats.v2_21.AbstractNatsInstrumentationExperimentalTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import java.io.IOException; @@ -18,8 +18,7 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; -class NatsInstrumentationMessagingReceiveTest - extends AbstractNatsInstrumentationMessagingReceiveTest { +class NatsInstrumentationExperimentalTest extends AbstractNatsInstrumentationExperimentalTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java index a392b4cb69ac..c416f52a168f 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java @@ -5,14 +5,20 @@ package io.opentelemetry.instrumentation.nats.v2_21; +import static java.util.Collections.emptyList; + import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; public final class NatsTelemetryBuilder { private final OpenTelemetry openTelemetry; private boolean messagingReceiveInstrumentationEnabled = false; + private List capturedHeaders = emptyList(); NatsTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -24,12 +30,18 @@ public NatsTelemetryBuilder setMessagingReceiveInstrumentationEnabled(boolean en return this; } + @CanIgnoreReturnValue + public NatsTelemetryBuilder setCapturedHeaders(Collection capturedHeaders) { + this.capturedHeaders = new ArrayList<>(capturedHeaders); + return this; + } + public NatsTelemetry build() { return new NatsTelemetry( - NatsInstrumenterFactory.createProducerInstrumenter(openTelemetry), + NatsInstrumenterFactory.createProducerInstrumenter(openTelemetry, capturedHeaders), NatsInstrumenterFactory.createConsumerReceiveInstrumenter( - openTelemetry, messagingReceiveInstrumentationEnabled), + openTelemetry, messagingReceiveInstrumentationEnabled, capturedHeaders), NatsInstrumenterFactory.createConsumerProcessInstrumenter( - openTelemetry, messagingReceiveInstrumentationEnabled)); + openTelemetry, messagingReceiveInstrumentationEnabled, capturedHeaders)); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java index 28f9c24adc65..cce703399a3b 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java @@ -13,6 +13,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; +import java.util.List; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -24,35 +25,41 @@ public final class NatsInstrumenterFactory { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.nats-2.21"; public static Instrumenter createProducerInstrumenter( - OpenTelemetry openTelemetry) { + OpenTelemetry openTelemetry, List capturedHeaders) { return Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, MessagingSpanNameExtractor.create( NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PUBLISH)) .addAttributesExtractor( - MessagingAttributesExtractor.create( - NatsRequestMessagingAttributesGetter.NATS_REQUEST_INSTANCE, - MessageOperation.PUBLISH)) + MessagingAttributesExtractor.builder( + NatsRequestMessagingAttributesGetter.NATS_REQUEST_INSTANCE, + MessageOperation.PUBLISH) + .setCapturedHeaders(capturedHeaders) + .build()) .buildProducerInstrumenter(NatsRequestTextMapSetter.INSTANCE); } public static Instrumenter createConsumerReceiveInstrumenter( - OpenTelemetry openTelemetry, boolean enabled) { + OpenTelemetry openTelemetry, boolean enabled, List capturedHeaders) { return Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, MessagingSpanNameExtractor.create( NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE)) .addAttributesExtractor( - MessagingAttributesExtractor.create( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE)) + MessagingAttributesExtractor.builder( + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE) + .setCapturedHeaders(capturedHeaders) + .build()) .setEnabled(enabled) .buildConsumerInstrumenter(NatsRequestTextMapGetter.INSTANCE); } public static Instrumenter createConsumerProcessInstrumenter( - OpenTelemetry openTelemetry, boolean messagingReceiveInstrumentationEnabled) { + OpenTelemetry openTelemetry, + boolean messagingReceiveInstrumentationEnabled, + List capturedHeaders) { InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, @@ -60,8 +67,11 @@ public static Instrumenter createConsumerProcessInstrumenter( MessagingSpanNameExtractor.create( NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PROCESS)) .addAttributesExtractor( - MessagingAttributesExtractor.create( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PROCESS)); + MessagingAttributesExtractor.builder( + NatsRequestMessagingAttributesGetter.VOID_INSTANCE, + MessageOperation.PROCESS) + .setCapturedHeaders(capturedHeaders) + .build()); if (messagingReceiveInstrumentationEnabled) { builder.addSpanLinksExtractor( diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetterFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetterFactory.java index 0ed9530989f4..2c6a96d1f3c9 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetterFactory.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetterFactory.java @@ -82,7 +82,9 @@ public Long getBatchMessageCount(NatsRequest request, @Nullable RESPONSE unused) @Override public List getMessageHeader(NatsRequest request, String name) { Headers headers = request.getHeaders(); - return headers == null ? Collections.emptyList() : headers.get(name); + return headers == null || headers.get(name) == null + ? Collections.emptyList() + : headers.get(name); } }; } diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationMessaingReceiveTest.java b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationExperimentalTest.java similarity index 82% rename from instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationMessaingReceiveTest.java rename to instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationExperimentalTest.java index 95c01c62aa3d..c5a515a90986 100644 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationMessaingReceiveTest.java +++ b/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationExperimentalTest.java @@ -5,13 +5,14 @@ package io.opentelemetry.instrumentation.nats.v2_21; +import static java.util.Collections.singletonList; + import io.nats.client.Connection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import org.junit.jupiter.api.extension.RegisterExtension; -class NatsInstrumentationMessaingReceiveTest - extends AbstractNatsInstrumentationMessagingReceiveTest { +class NatsInstrumentationExperimentalTest extends AbstractNatsInstrumentationExperimentalTest { @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); @@ -23,6 +24,7 @@ class NatsInstrumentationMessaingReceiveTest telemetry = NatsTelemetry.builder(testing.getOpenTelemetry()) .setMessagingReceiveInstrumentationEnabled(true) + .setCapturedHeaders(singletonList("captured-header")) .build(); natsConnection = telemetry.wrap(new TestConnection()); } diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationMessagingReceiveTest.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationExperimentalTest.java similarity index 68% rename from instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationMessagingReceiveTest.java rename to instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationExperimentalTest.java index 739589f2a9d8..3ce1ae388e09 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationMessagingReceiveTest.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationExperimentalTest.java @@ -6,21 +6,26 @@ package io.opentelemetry.instrumentation.nats.v2_21; import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.messagingAttributes; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import io.nats.client.Connection; import io.nats.client.Dispatcher; +import io.nats.client.Message; import io.nats.client.impl.Headers; import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationMessagingReceiveTest { +public abstract class AbstractNatsInstrumentationExperimentalTest { protected abstract InstrumentationExtension testing(); @@ -34,34 +39,43 @@ void beforeEach() { } @Test - void testSubscribe() { + void testMessagingReceiveAndCapturedHeaders() { // given Dispatcher dispatcher = connection().createDispatcher(msg -> {}).subscribe("sub"); // when + Headers headers = new Headers(); + headers.put("captured-header", "value"); String traceId = testing() .runWithSpan( "parent", () -> { - connection() - .publish( - NatsMessage.builder() - .subject("sub") - .headers(new Headers()) - .data("x") - .build()); + Message message = + NatsMessage.builder().subject("sub").headers(headers).data("x").build(); + connection().publish(message); return Span.fromContext(Context.current()).getSpanContext().getTraceId(); }); connection().closeDispatcher(dispatcher); // then + AttributeAssertion[] headerAssert = + new AttributeAssertion[] { + equalTo( + AttributeKey.stringArrayKey("messaging.header.captured_header"), + singletonList("value")) + }; + testing() .waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasNoParent(), - span -> span.hasName("sub publish").hasParent(trace.getSpan(0))), + span -> + span.hasName("sub publish") + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + messagingAttributes("publish", "sub", clientId, headerAssert))), trace -> trace.hasSpansSatisfyingExactly( span -> @@ -69,7 +83,7 @@ void testSubscribe() { .hasKind(SpanKind.CONSUMER) .hasNoParent() .hasAttributesSatisfyingExactly( - messagingAttributes("receive", "sub", clientId)), + messagingAttributes("receive", "sub", clientId, headerAssert)), span -> span.hasName("sub process") .hasKind(SpanKind.CONSUMER) diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationRequestTest.java index 1a0e022b5e3c..ab39138fd1a7 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationRequestTest.java @@ -291,12 +291,9 @@ private void assertPublishReceiveSpans() { .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( messagingAttributes("publish", "sub", clientId))), - // subscriber: receive + process + publish(response) (another trace because no - // propagation) + // subscriber: process + publish(response) (another trace because no propagation) trace -> trace.hasSpansSatisfyingExactly( - // span -> span.hasName("sub - // receive").hasKind(SpanKind.CONSUMER).hasNoParent(), span -> span.hasName("sub process").hasKind(SpanKind.CONSUMER).hasNoParent(), span -> @@ -308,7 +305,7 @@ private void assertPublishReceiveSpans() { "Name condition")) .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0))), - // publisher: receive + process (another trace because no propagation) + // publisher: process (another trace because no propagation) trace -> trace.hasSpansSatisfyingExactly( span -> @@ -322,18 +319,8 @@ private void assertPublishReceiveSpans() { .hasNoParent() .hasAttributesSatisfyingExactly( messagingAttributes("process", "_INBOX.", clientId))))); + if (!isInboxMonitored()) { - /*asserts.add(span -> - span.has( - new Condition<>( - data -> - data.getName().startsWith("_INBOX.") - && data.getName().endsWith(" receive"), - "Name condition")) - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)) - .hasAttributesSatisfyingExactly( - messagingAttributes("receive", "_INBOX.", clientId)));*/ asserts.remove(asserts.size() - 1); } testing().waitAndAssertTraces(asserts); @@ -354,7 +341,7 @@ private void assertPublishReceiveSpansSameTrace() { .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( messagingAttributes("publish", "sub", clientId)), - // dispatcher receive, process, publish, not retesting all properties + // subscriber: process + publish(response) span -> span.hasName("sub process") .hasKind(SpanKind.CONSUMER) @@ -368,7 +355,7 @@ private void assertPublishReceiveSpansSameTrace() { "Name condition")) .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(2)), - // publisher: receive + process + // publisher: process span -> span.has( new Condition<>( @@ -381,24 +368,7 @@ private void assertPublishReceiveSpansSameTrace() { .hasAttributesSatisfyingExactly( messagingAttributes("process", "_INBOX.", clientId)))); - /*asserts.add(span -> - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)));*/ - // end dispatcher - if (!isInboxMonitored()) { - /*asserts.add(span -> - span.has( - new Condition<>( - data -> - data.getName().startsWith("_INBOX.") - && data.getName().endsWith(" receive"), - "Name condition")) - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)) - .hasAttributesSatisfyingExactly( - messagingAttributes("receive", "_INBOX.", clientId)));*/ asserts.remove(asserts.size() - 1); } diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationTestHelper.java b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationTestHelper.java index edf50d840e24..dd0b8b54c6c3 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationTestHelper.java +++ b/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationTestHelper.java @@ -22,6 +22,15 @@ @SuppressWarnings("deprecation") // using deprecated semconv public class NatsInstrumentationTestHelper { + public static AttributeAssertion[] messagingAttributes( + String operation, String subject, int clientId, AttributeAssertion[] other) { + AttributeAssertion[] standard = messagingAttributes(operation, subject, clientId); + AttributeAssertion[] result = new AttributeAssertion[standard.length + other.length]; + System.arraycopy(standard, 0, result, 0, standard.length); + System.arraycopy(other, 0, result, standard.length, other.length); + return result; + } + public static AttributeAssertion[] messagingAttributes( String operation, String subject, int clientId) { AttributeAssertion destinationName = equalTo(MESSAGING_DESTINATION_NAME, subject); From 360a21f5975adf9ce142bced3030f2315d48bbc1 Mon Sep 17 00:00:00 2001 From: Alix Date: Sun, 22 Jun 2025 12:39:02 +0200 Subject: [PATCH 14/34] fix dispatcher in library instrumentation --- .github/scripts/check-package-names.sh | 3 + .../impl/OpenTelemetryDispatcherFactory.java | 39 ++++++++++ .../nats/v2_21/NatsTelemetry.java | 3 +- .../v2_21/OpenTelemetryDispatcherFactory.java | 76 ------------------- .../v2_21/OpenTelemetryMessageHandler.java | 6 +- .../internal/NatsInstrumenterFactory.java | 4 +- .../nats/v2_21/internal/NatsRequest.java | 4 +- 7 files changed, 51 insertions(+), 84 deletions(-) create mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java delete mode 100644 instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcherFactory.java diff --git a/.github/scripts/check-package-names.sh b/.github/scripts/check-package-names.sh index ce53c483fb44..286dc1c3db5a 100755 --- a/.github/scripts/check-package-names.sh +++ b/.github/scripts/check-package-names.sh @@ -29,6 +29,9 @@ for dir in $(find instrumentation -name "*.java" | grep library/src/main/java | if [[ "$dir" == "instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/lettuce/core/protocol" ]]; then continue fi + if [[ "$dir" == "instrumentation/nats/nats-2.21/library/src/main/java/io/nats/client/impl" ]]; then + continue + fi # some common modules don't have any base version # - lettuce-common diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java new file mode 100644 index 000000000000..29230a9f7921 --- /dev/null +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.nats.client.impl; + +import io.nats.client.MessageHandler; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.nats.v2_21.OpenTelemetryMessageHandler; +import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class OpenTelemetryDispatcherFactory extends DispatcherFactory { + + private final DispatcherFactory delegate; + private final Instrumenter consumerReceiveInstrumenter; + private final Instrumenter consumerProcessInstrumenter; + + public OpenTelemetryDispatcherFactory( + DispatcherFactory delegate, + Instrumenter consumerReceiveInstrumenter, + Instrumenter consumerProcessInstrumenter) { + this.delegate = delegate; + this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; + this.consumerProcessInstrumenter = consumerProcessInstrumenter; + } + + @Override + NatsDispatcher createDispatcher(NatsConnection natsConnection, MessageHandler messageHandler) { + return delegate.createDispatcher( + natsConnection, + new OpenTelemetryMessageHandler( + messageHandler, consumerReceiveInstrumenter, consumerProcessInstrumenter)); + } +} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java index f8b068d025c8..b67a3872a772 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java @@ -8,6 +8,7 @@ import io.nats.client.Connection; import io.nats.client.Options; import io.nats.client.impl.DispatcherFactory; +import io.nats.client.impl.OpenTelemetryDispatcherFactory; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; @@ -54,7 +55,7 @@ public Options.Builder wrap(Options.Builder options) { } return options.dispatcherFactory( - OpenTelemetryDispatcherFactory.wrap( + new OpenTelemetryDispatcherFactory( factory, consumerReceiveInstrumenter, consumerProcessInstrumenter)); } } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcherFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcherFactory.java deleted file mode 100644 index fc131f9eda8f..000000000000 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcherFactory.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_21; - -import io.nats.client.MessageHandler; -import io.nats.client.impl.DispatcherFactory; -import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; - -final class OpenTelemetryDispatcherFactory implements InvocationHandler { - - private final DispatcherFactory delegate; - private final Instrumenter consumerReceiveInstrumenter; - private final Instrumenter consumerProcessInstrumenter; - - public OpenTelemetryDispatcherFactory( - DispatcherFactory delegate, - Instrumenter consumerReceiveInstrumenter, - Instrumenter consumerProcessInstrumenter) { - this.delegate = delegate; - this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; - this.consumerProcessInstrumenter = consumerProcessInstrumenter; - } - - public static DispatcherFactory wrap( - DispatcherFactory delegate, - Instrumenter consumerReceiveInstrumenter, - Instrumenter consumerProcessInstrumenter) { - return (DispatcherFactory) - Proxy.newProxyInstance( - OpenTelemetryDispatcherFactory.class.getClassLoader(), - new Class[] {DispatcherFactory.class}, - new OpenTelemetryDispatcherFactory( - delegate, consumerReceiveInstrumenter, consumerProcessInstrumenter)); - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if ("createDispatcher".equals(method.getName())) { - return createDispatcher(method, args); - } - - try { - return method.invoke(delegate, args); - } catch (InvocationTargetException e) { - throw e.getCause(); - } - } - - private static Object invokeProxyMethod(Method method, Object target, Object[] args) - throws Throwable { - try { - return method.invoke(target, args); - } catch (InvocationTargetException exception) { - throw exception.getCause(); - } - } - - // NatsDispatcher createDispatcher(NatsConnection conn, MessageHandler handler) - private Object createDispatcher(Method method, Object[] args) throws Throwable { - if (method.getParameterCount() == 2 && method.getParameterTypes()[1] == MessageHandler.class) { - args[1] = - new OpenTelemetryMessageHandler( - (MessageHandler) args[1], consumerReceiveInstrumenter, consumerProcessInstrumenter); - } - - return invokeProxyMethod(method, delegate, args); - } -} diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java index f7c8bbb781b5..e7bb21938cfe 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java @@ -14,7 +14,11 @@ import io.opentelemetry.instrumentation.api.internal.Timer; import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; -final class OpenTelemetryMessageHandler implements MessageHandler { +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. Exposed for {@link io.nats.client.impl.OpenTelemetryDispatcherFactory}. + */ +public final class OpenTelemetryMessageHandler implements MessageHandler { private final MessageHandler delegate; private final Instrumenter consumerReceiveInstrumenter; diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java index cce703399a3b..3cd5ae171791 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java @@ -17,9 +17,7 @@ /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time.", or "This class is internal and experimental. Its APIs are unstable and can change at - * any time. Its APIs (or a version of them) may be promoted to the public stable API in the future, - * but no guarantees are made. + * any time. */ public final class NatsInstrumenterFactory { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.nats-2.21"; diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java index dc8bcac8f358..1f62d7a7d1e5 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java +++ b/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java @@ -13,9 +13,7 @@ /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time.", or "This class is internal and experimental. Its APIs are unstable and can change at - * any time. Its APIs (or a version of them) may be promoted to the public stable API in the future, - * but no guarantees are made. + * any time. */ @AutoValue public abstract class NatsRequest { From dd00a7fc80912022a399b6a40e3d8413b3482c5a Mon Sep 17 00:00:00 2001 From: Alix Date: Wed, 27 Aug 2025 14:58:31 +0200 Subject: [PATCH 15/34] generate metadata --- docs/instrumentation-list.yaml | 53 +++++++++++++++++++ instrumentation-docs/instrumentations.sh | 2 + .../nats/nats-2.21/javaagent/build.gradle.kts | 6 +++ instrumentation/nats/nats-2.21/metadata.yaml | 8 ++- 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index 8978ac5e1557..af0384af0c93 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -4102,6 +4102,59 @@ libraries: target_versions: javaagent: - org.mybatis:mybatis:[3.2.0,) + nats: + - name: nats-2.21 + description: This instrumentation provides messaging spans for NATS + disabled_by_default: true + source_path: instrumentation/nats/nats-2.21 + scope: + name: io.opentelemetry.nats-2.21 + target_versions: + javaagent: + - io.nats:jnats:[2.17.2,) + library: + - io.nats:jnats:2.21.0 + configurations: + - name: otel.instrumentation.messaging.experimental.receive-telemetry.enabled + description: | + Enables experimental receive telemetry, which will cause consumers to start a new trace, with only a span link connecting it to the producer trace. + type: boolean + default: false + - name: otel.instrumentation.messaging.experimental.capture-headers + description: Allows configuring headers to capture as span attributes. + type: list + default: '' + telemetry: + - when: default + spans: + - span_kind: CONSUMER + attributes: + - name: messaging.client_id + type: STRING + - name: messaging.destination.name + type: STRING + - name: messaging.header.captured_header + type: STRING_ARRAY + - name: messaging.message.body.size + type: LONG + - name: messaging.operation + type: STRING + - name: messaging.system + type: STRING + - span_kind: PRODUCER + attributes: + - name: messaging.client_id + type: STRING + - name: messaging.destination.name + type: STRING + - name: messaging.header.captured_header + type: STRING_ARRAY + - name: messaging.message.body.size + type: LONG + - name: messaging.operation + type: STRING + - name: messaging.system + type: STRING netty: - name: netty-3.8 source_path: instrumentation/netty/netty-3.8 diff --git a/instrumentation-docs/instrumentations.sh b/instrumentation-docs/instrumentations.sh index 34d537a503fd..5f351844f035 100755 --- a/instrumentation-docs/instrumentations.sh +++ b/instrumentation-docs/instrumentations.sh @@ -137,6 +137,8 @@ readonly INSTRUMENTATIONS=( "grails-3.0:javaagent:test" "grizzly-2.3:javaagent:test" "gwt-2.0:javaagent:test" + "nats:nats-2.21:javaagent:test" + "nats:nats-2.21:javaagent:testExperimental" ) # Some instrumentation test suites don't run ARM, so we use colima to run them in an x86_64 diff --git a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts index 4b759a78d06c..4d17f721bf9a 100644 --- a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts @@ -18,6 +18,8 @@ dependencies { testImplementation(project(":instrumentation:nats:nats-2.21:testing")) } +val collectMetadata = findProperty("collectMetadata")?.toString() ?: "false" + tasks { val testExperimental by registering(Test::class) { filter { @@ -25,6 +27,8 @@ tasks { } jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") jvmArgs("-Dotel.instrumentation.messaging.experimental.capture-headers=captured-header") + + systemProperty("collectMetadata", collectMetadata) } test { @@ -32,6 +36,8 @@ tasks { filter { excludeTestsMatching("NatsInstrumentationExperimentalTest") } + + systemProperty("collectMetadata", collectMetadata) } check { diff --git a/instrumentation/nats/nats-2.21/metadata.yaml b/instrumentation/nats/nats-2.21/metadata.yaml index a53895dcc409..455ef35e9957 100644 --- a/instrumentation/nats/nats-2.21/metadata.yaml +++ b/instrumentation/nats/nats-2.21/metadata.yaml @@ -2,6 +2,12 @@ disabled_by_default: true description: This instrumentation provides messaging spans for NATS configurations: - name: otel.instrumentation.messaging.experimental.receive-telemetry.enabled - description: Enables experimental receive telemetry for NATS instrumentation. + description: > + Enables experimental receive telemetry, which will cause consumers to start a new trace, with + only a span link connecting it to the producer trace. type: boolean default: false + - name: otel.instrumentation.messaging.experimental.capture-headers + description: Allows configuring headers to capture as span attributes. + type: list + default: '' From 1c88b1c88cb387f6e40a43f44774809b692c5166 Mon Sep 17 00:00:00 2001 From: Alix Date: Wed, 27 Aug 2025 16:39:34 +0200 Subject: [PATCH 16/34] remove muzzle check for version without dependency --- instrumentation/nats/nats-2.21/javaagent/build.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts index 4d17f721bf9a..bb42f690bad5 100644 --- a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts @@ -7,6 +7,10 @@ muzzle { group.set("io.nats") module.set("jnats") versions.set("[2.17.2,)") + + // Could not find io.nats:nats-parent:1.0-SNAPSHOT + skip("0.5.0", "0.5.1") + assertInverse.set(true) } } From f289e05750059f93a3bf6b1b0e3d0fb7ef51f04e Mon Sep 17 00:00:00 2001 From: Alix Date: Thu, 28 Aug 2025 11:41:59 +0200 Subject: [PATCH 17/34] rename nats-2.21 to nats-2.17 --- .fossa.yml | 4 ++-- .github/scripts/check-package-names.sh | 2 +- docs/instrumentation-list.yaml | 10 +++++----- docs/supported-libraries.md | 2 +- instrumentation-docs/instrumentations.sh | 4 ++-- .../{nats-2.21 => nats-2.17}/javaagent/README.md | 4 ++-- .../javaagent/build.gradle.kts | 6 +++--- .../nats/v2_17}/CompletableFutureWrapper.java | 2 +- .../v2_17}/ConnectionPublishInstrumentation.java | 6 +++--- .../v2_17}/ConnectionRequestInstrumentation.java | 6 +++--- .../nats/v2_17}/DispatcherInstrumentation.java | 2 +- .../nats/v2_17}/MessageHandlerInstrumentation.java | 8 ++++---- .../nats/v2_17}/NatsInstrumentationModule.java | 4 ++-- .../instrumentation/nats/v2_17}/NatsSingletons.java | 10 +++++----- .../instrumentation/nats/v2_17}/SpanFinisher.java | 4 ++-- .../v2_17}/NatsInstrumentationDispatcherTest.java | 4 ++-- .../v2_17}/NatsInstrumentationExperimentalTest.java | 4 ++-- .../nats/v2_17}/NatsInstrumentationPublishTest.java | 4 ++-- .../nats/v2_17}/NatsInstrumentationRequestTest.java | 4 ++-- .../nats/{nats-2.21 => nats-2.17}/library/README.md | 12 ++++++------ .../library/build.gradle.kts | 4 ++-- .../client/impl/OpenTelemetryDispatcherFactory.java | 4 ++-- .../instrumentation/nats/v2_17}/NatsTelemetry.java | 4 ++-- .../nats/v2_17}/NatsTelemetryBuilder.java | 4 ++-- .../nats/v2_17}/OpenTelemetryConnection.java | 4 ++-- .../nats/v2_17}/OpenTelemetryDispatcher.java | 4 ++-- .../nats/v2_17}/OpenTelemetryMessageHandler.java | 4 ++-- .../v2_17}/internal/NatsInstrumenterFactory.java | 4 ++-- .../nats/v2_17}/internal/NatsRequest.java | 2 +- .../NatsRequestMessagingAttributesGetter.java | 2 +- .../NatsRequestMessagingAttributesGetterFactory.java | 2 +- .../v2_17}/internal/NatsRequestTextMapGetter.java | 2 +- .../v2_17}/internal/NatsRequestTextMapSetter.java | 2 +- .../nats/v2_17}/internal/ThrowingSupplier.java | 2 +- .../nats/v2_17}/internal/ThrowingSupplier2.java | 2 +- .../v2_17}/NatsInstrumentationDispatcherTest.java | 2 +- .../v2_17}/NatsInstrumentationExperimentalTest.java | 2 +- .../nats/v2_17}/NatsInstrumentationPublishTest.java | 2 +- .../nats/v2_17}/NatsInstrumentationRequestTest.java | 2 +- .../nats/{nats-2.21 => nats-2.17}/metadata.yaml | 0 .../testing/build.gradle.kts | 2 +- .../AbstractNatsInstrumentationDispatcherTest.java | 4 ++-- .../AbstractNatsInstrumentationExperimentalTest.java | 4 ++-- .../AbstractNatsInstrumentationPublishTest.java | 8 ++++---- .../AbstractNatsInstrumentationRequestTest.java | 8 ++++---- .../nats/v2_17}/NatsInstrumentationTestHelper.java | 2 +- .../instrumentation/nats/v2_17}/TestConnection.java | 2 +- .../instrumentation/nats/v2_17}/TestDispatcher.java | 4 ++-- .../instrumentation/nats/v2_17}/TestMessage.java | 2 +- .../nats/v2_17}/TestSubscription.java | 2 +- settings.gradle.kts | 6 +++--- 51 files changed, 100 insertions(+), 100 deletions(-) rename instrumentation/nats/{nats-2.21 => nats-2.17}/javaagent/README.md (82%) rename instrumentation/nats/{nats-2.21 => nats-2.17}/javaagent/build.gradle.kts (86%) rename instrumentation/nats/{nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21 => nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17}/CompletableFutureWrapper.java (92%) rename instrumentation/nats/{nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21 => nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17}/ConnectionPublishInstrumentation.java (98%) rename instrumentation/nats/{nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21 => nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17}/ConnectionRequestInstrumentation.java (99%) rename instrumentation/nats/{nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21 => nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17}/DispatcherInstrumentation.java (97%) rename instrumentation/nats/{nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21 => nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17}/MessageHandlerInstrumentation.java (93%) rename instrumentation/nats/{nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21 => nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17}/NatsInstrumentationModule.java (89%) rename instrumentation/nats/{nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21 => nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17}/NatsSingletons.java (81%) rename instrumentation/nats/{nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21 => nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17}/SpanFinisher.java (90%) rename instrumentation/nats/{nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21 => nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17}/NatsInstrumentationDispatcherTest.java (93%) rename instrumentation/nats/{nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21 => nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17}/NatsInstrumentationExperimentalTest.java (93%) rename instrumentation/nats/{nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21 => nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17}/NatsInstrumentationPublishTest.java (93%) rename instrumentation/nats/{nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21 => nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17}/NatsInstrumentationRequestTest.java (93%) rename instrumentation/nats/{nats-2.21 => nats-2.17}/library/README.md (88%) rename instrumentation/nats/{nats-2.21 => nats-2.17}/library/build.gradle.kts (66%) rename instrumentation/nats/{nats-2.21 => nats-2.17}/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java (91%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/NatsTelemetry.java (95%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/NatsTelemetryBuilder.java (92%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/OpenTelemetryConnection.java (98%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/OpenTelemetryDispatcher.java (96%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/OpenTelemetryMessageHandler.java (95%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/internal/NatsInstrumenterFactory.java (97%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/internal/NatsRequest.java (95%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/internal/NatsRequestMessagingAttributesGetter.java (90%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/internal/NatsRequestMessagingAttributesGetterFactory.java (97%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/internal/NatsRequestTextMapGetter.java (92%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/internal/NatsRequestTextMapSetter.java (90%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/internal/ThrowingSupplier.java (83%) rename instrumentation/nats/{nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/internal/ThrowingSupplier2.java (84%) rename instrumentation/nats/{nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17}/NatsInstrumentationDispatcherTest.java (94%) rename instrumentation/nats/{nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17}/NatsInstrumentationExperimentalTest.java (95%) rename instrumentation/nats/{nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17}/NatsInstrumentationPublishTest.java (94%) rename instrumentation/nats/{nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17}/NatsInstrumentationRequestTest.java (95%) rename instrumentation/nats/{nats-2.21 => nats-2.17}/metadata.yaml (100%) rename instrumentation/nats/{nats-2.21 => nats-2.17}/testing/build.gradle.kts (71%) rename instrumentation/nats/{nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/AbstractNatsInstrumentationDispatcherTest.java (97%) rename instrumentation/nats/{nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/AbstractNatsInstrumentationExperimentalTest.java (96%) rename instrumentation/nats/{nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/AbstractNatsInstrumentationPublishTest.java (93%) rename instrumentation/nats/{nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/AbstractNatsInstrumentationRequestTest.java (98%) rename instrumentation/nats/{nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/NatsInstrumentationTestHelper.java (98%) rename instrumentation/nats/{nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/TestConnection.java (99%) rename instrumentation/nats/{nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/TestDispatcher.java (97%) rename instrumentation/nats/{nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/TestMessage.java (98%) rename instrumentation/nats/{nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21 => nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17}/TestSubscription.java (98%) diff --git a/.fossa.yml b/.fossa.yml index 5f6f12777c6b..ea9f2391a25b 100644 --- a/.fossa.yml +++ b/.fossa.yml @@ -699,10 +699,10 @@ targets: target: ':instrumentation:mongo:mongo-async-3.3:javaagent' - type: gradle path: ./ - target: ':instrumentation:nats:nats-2.21:javaagent' + target: ':instrumentation:nats:nats-2.17:javaagent' - type: gradle path: ./ - target: ':instrumentation:nats:nats-2.21:library' + target: ':instrumentation:nats:nats-2.17:library' - type: gradle path: ./ target: ':instrumentation:netty:netty-3.8:javaagent' diff --git a/.github/scripts/check-package-names.sh b/.github/scripts/check-package-names.sh index 286dc1c3db5a..6db819de2342 100755 --- a/.github/scripts/check-package-names.sh +++ b/.github/scripts/check-package-names.sh @@ -29,7 +29,7 @@ for dir in $(find instrumentation -name "*.java" | grep library/src/main/java | if [[ "$dir" == "instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/lettuce/core/protocol" ]]; then continue fi - if [[ "$dir" == "instrumentation/nats/nats-2.21/library/src/main/java/io/nats/client/impl" ]]; then + if [[ "$dir" == "instrumentation/nats/nats-2.17/library/src/main/java/io/nats/client/impl" ]]; then continue fi diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index bd4aa359c91b..84d0fdff1eb4 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -4172,17 +4172,17 @@ libraries: javaagent: - org.mybatis:mybatis:[3.2.0,) nats: - - name: nats-2.21 + - name: nats-2.17 description: This instrumentation provides messaging spans for NATS - disabled_by_default: true - source_path: instrumentation/nats/nats-2.21 + disabled_by_default: false + source_path: instrumentation/nats/nats-2.17 scope: - name: io.opentelemetry.nats-2.21 + name: io.opentelemetry.nats-2.17 target_versions: javaagent: - io.nats:jnats:[2.17.2,) library: - - io.nats:jnats:2.21.0 + - io.nats:jnats:2.21.5 configurations: - name: otel.instrumentation.messaging.experimental.receive-telemetry.enabled description: | diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 3214c51e2834..9fcfc15eb556 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -105,7 +105,7 @@ These are the supported libraries and frameworks: | [Micrometer](https://micrometer.io/) | 1.5+ (disabled by default) | [opentelemetry-micrometer-1.5](../instrumentation/micrometer/micrometer-1.5/library) | none | | [MongoDB Driver](https://mongodb.github.io/mongo-java-driver/) | 3.1+ | [opentelemetry-mongo-3.1](../instrumentation/mongo/mongo-3.1/library) | [Database Client Spans], [Database Client Metrics] [6] | | [MyBatis](https://mybatis.org/mybatis-3/) | 3.2+ | N/A | none | -| [NATS](https://github.com/nats-io/nats.java) | 2.17.2+ | [nats-2.21](../instrumentation/nats/nats-2.21/library) | [Messaging Spans] | +| [NATS](https://github.com/nats-io/nats.java) | 2.17.2+ | [nats-2.17](../instrumentation/nats/nats-2.17/library) | [Messaging Spans] | | [Netty HTTP codec [5]](https://github.com/netty/netty) | 3.8+ | [opentelemetry-netty-4.1](../instrumentation/netty/netty-4.1/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] | | [OpenAI Java SDK](https://github.com/openai/openai-java) | 1.1+ | [openai-java-1.1](../instrumentation/openai/openai-java-1.1/library) | [GenAI Client Spans], [GenAI Client Metrics] | | [OpenSearch Rest Client](https://github.com/opensearch-project/opensearch-java) | 1.0+ | | [Database Client Spans], [Database Client Metrics] [6] | diff --git a/instrumentation-docs/instrumentations.sh b/instrumentation-docs/instrumentations.sh index 8f9f401e11da..77934bf1ed7a 100755 --- a/instrumentation-docs/instrumentations.sh +++ b/instrumentation-docs/instrumentations.sh @@ -140,8 +140,8 @@ readonly INSTRUMENTATIONS=( "graphql-java:graphql-java-12.0:javaagent:test" "graphql-java:graphql-java-20.0:javaagent:test" "graphql-java:graphql-java-20.0:javaagent:testDataFetcher" - "nats:nats-2.21:javaagent:test" - "nats:nats-2.21:javaagent:testExperimental" + "nats:nats-2.17:javaagent:test" + "nats:nats-2.17:javaagent:testExperimental" ) # Some instrumentation test suites don't run ARM, so we use colima to run them in an x86_64 diff --git a/instrumentation/nats/nats-2.21/javaagent/README.md b/instrumentation/nats/nats-2.17/javaagent/README.md similarity index 82% rename from instrumentation/nats/nats-2.21/javaagent/README.md rename to instrumentation/nats/nats-2.17/javaagent/README.md index c68aac0660c4..7b7f4dea62e8 100644 --- a/instrumentation/nats/nats-2.21/javaagent/README.md +++ b/instrumentation/nats/nats-2.17/javaagent/README.md @@ -1,6 +1,6 @@ -# Auth-instrumentation for NATS version 2.21 +# Auth-instrumentation for NATS version 2.17 -Provides OpenTelemetry auto-instrumentation for [NATS 2.21](https://github.com/nats-io/nats.java). +Provides OpenTelemetry auto-instrumentation for [NATS 2.17](https://github.com/nats-io/nats.java). ### Trace propagation diff --git a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts similarity index 86% rename from instrumentation/nats/nats-2.21/javaagent/build.gradle.kts rename to instrumentation/nats/nats-2.17/javaagent/build.gradle.kts index bb42f690bad5..79d49fb8cca9 100644 --- a/instrumentation/nats/nats-2.21/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts @@ -16,10 +16,10 @@ muzzle { } dependencies { - library("io.nats:jnats:2.21.0") + library("io.nats:jnats:2.21.5") - implementation(project(":instrumentation:nats:nats-2.21:library")) - testImplementation(project(":instrumentation:nats:nats-2.21:testing")) + implementation(project(":instrumentation:nats:nats-2.17:library")) + testImplementation(project(":instrumentation:nats:nats-2.17:testing")) } val collectMetadata = findProperty("collectMetadata")?.toString() ?: "false" diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/CompletableFutureWrapper.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/CompletableFutureWrapper.java similarity index 92% rename from instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/CompletableFutureWrapper.java rename to instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/CompletableFutureWrapper.java index fe42c0576fed..6fb6b176e301 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/CompletableFutureWrapper.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/CompletableFutureWrapper.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java similarity index 98% rename from instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java rename to instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java index d9b5ac58d5e8..48cd8f4de826 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionPublishInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.PRODUCER_INSTRUMENTER; +import static io.opentelemetry.javaagent.instrumentation.nats.v2_17.NatsSingletons.PRODUCER_INSTRUMENTER; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; @@ -17,7 +17,7 @@ import io.nats.client.impl.Headers; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import net.bytebuddy.asm.Advice; diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java similarity index 99% rename from instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java rename to instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java index 29e5d103ae5c..64ebe90d5036 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.PRODUCER_INSTRUMENTER; +import static io.opentelemetry.javaagent.instrumentation.nats.v2_17.NatsSingletons.PRODUCER_INSTRUMENTER; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; @@ -18,7 +18,7 @@ import io.nats.client.impl.Headers; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import java.time.Duration; diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/DispatcherInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/DispatcherInstrumentation.java similarity index 97% rename from instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/DispatcherInstrumentation.java rename to instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/DispatcherInstrumentation.java index 0c1fd9be873e..b32d5af19247 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/DispatcherInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/DispatcherInstrumentation.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; import static net.bytebuddy.matcher.ElementMatchers.isPublic; diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java similarity index 93% rename from instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java rename to instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java index 15040828c248..5432a051986c 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/MessageHandlerInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.CONSUMER_PROCESS_INSTRUMENTER; -import static io.opentelemetry.javaagent.instrumentation.nats.v2_21.NatsSingletons.CONSUMER_RECEIVE_INSTRUMENTER; +import static io.opentelemetry.javaagent.instrumentation.nats.v2_17.NatsSingletons.CONSUMER_PROCESS_INSTRUMENTER; +import static io.opentelemetry.javaagent.instrumentation.nats.v2_17.NatsSingletons.CONSUMER_RECEIVE_INSTRUMENTER; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; @@ -18,7 +18,7 @@ import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; import io.opentelemetry.instrumentation.api.internal.Timer; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import net.bytebuddy.asm.Advice; diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationModule.java similarity index 89% rename from instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java rename to instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationModule.java index 3c9fa26630bf..1a0c4fa7c35f 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationModule.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationModule.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; import static java.util.Arrays.asList; @@ -16,7 +16,7 @@ public class NatsInstrumentationModule extends InstrumentationModule { public NatsInstrumentationModule() { - super("nats", "nats-2.21"); + super("nats", "nats-2.17"); } @Override diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsSingletons.java similarity index 81% rename from instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java rename to instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsSingletons.java index 178806563a13..ad0a6cceff0a 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsSingletons.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsSingletons.java @@ -3,15 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; -import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createConsumerProcessInstrumenter; -import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createConsumerReceiveInstrumenter; -import static io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory.createProducerInstrumenter; +import static io.opentelemetry.instrumentation.nats.v2_17.internal.NatsInstrumenterFactory.createConsumerProcessInstrumenter; +import static io.opentelemetry.instrumentation.nats.v2_17.internal.NatsInstrumenterFactory.createConsumerReceiveInstrumenter; +import static io.opentelemetry.instrumentation.nats.v2_17.internal.NatsInstrumenterFactory.createProducerInstrumenter; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig; import java.util.List; diff --git a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SpanFinisher.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/SpanFinisher.java similarity index 90% rename from instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SpanFinisher.java rename to instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/SpanFinisher.java index 68e43fff9e38..8659542b46c5 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/SpanFinisher.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/SpanFinisher.java @@ -3,13 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; import io.nats.client.Connection; import io.nats.client.Message; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import java.util.function.BiConsumer; public class SpanFinisher implements BiConsumer { diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java similarity index 93% rename from instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java rename to instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java index 00e46c72d00b..ec5ce42b3a0b 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; import io.nats.client.Connection; import io.nats.client.Nats; -import io.opentelemetry.instrumentation.nats.v2_21.AbstractNatsInstrumentationDispatcherTest; +import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsInstrumentationDispatcherTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import java.io.IOException; diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java similarity index 93% rename from instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationExperimentalTest.java rename to instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java index 180a8df9d191..05b8c90b3517 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; import io.nats.client.Connection; import io.nats.client.Nats; -import io.opentelemetry.instrumentation.nats.v2_21.AbstractNatsInstrumentationExperimentalTest; +import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsInstrumentationExperimentalTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import java.io.IOException; diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java similarity index 93% rename from instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java rename to instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java index 5a19cca09b2a..6de245775cb3 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; import io.nats.client.Connection; import io.nats.client.Nats; -import io.opentelemetry.instrumentation.nats.v2_21.AbstractNatsInstrumentationPublishTest; +import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsInstrumentationPublishTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import java.io.IOException; diff --git a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java similarity index 93% rename from instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java rename to instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java index 84c1fd8cfa24..29f9ba4dc23a 100644 --- a/instrumentation/nats/nats-2.21/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.nats.v2_21; +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; import io.nats.client.Connection; import io.nats.client.Nats; -import io.opentelemetry.instrumentation.nats.v2_21.AbstractNatsInstrumentationRequestTest; +import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsInstrumentationRequestTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import java.io.IOException; diff --git a/instrumentation/nats/nats-2.21/library/README.md b/instrumentation/nats/nats-2.17/library/README.md similarity index 88% rename from instrumentation/nats/nats-2.21/library/README.md rename to instrumentation/nats/nats-2.17/library/README.md index da94909ca4cf..1ad359013ffe 100644 --- a/instrumentation/nats/nats-2.21/library/README.md +++ b/instrumentation/nats/nats-2.17/library/README.md @@ -1,13 +1,13 @@ -# Library Instrumentation for NATS version 2.21 +# Library Instrumentation for NATS version 2.17 -Provides OpenTelemetry instrumentation for [NATS 2.21](https://github.com/nats-io/nats.java). +Provides OpenTelemetry instrumentation for [NATS 2.17](https://github.com/nats-io/nats.java). ## Quickstart ### Add these dependencies to your project Replace `OPENTELEMETRY_VERSION` with the [latest -release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-nats-2.21). +release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-nats-2.17). For Maven, add to your `pom.xml` dependencies: @@ -15,7 +15,7 @@ For Maven, add to your `pom.xml` dependencies: io.opentelemetry.instrumentation - opentelemetry-nats-2.21 + opentelemetry-nats-2.17 OPENTELEMETRY_VERSION @@ -24,7 +24,7 @@ For Maven, add to your `pom.xml` dependencies: For Gradle, add to your dependencies: ```groovy -implementation("io.opentelemetry.instrumentation:opentelemetry-nats-2.21:OPENTELEMETRY_VERSION") +implementation("io.opentelemetry.instrumentation:opentelemetry-nats-2.17:OPENTELEMETRY_VERSION") ``` ### Usage @@ -38,7 +38,7 @@ import io.nats.client.Connection; import io.nats.client.Nats; import io.nats.client.Options; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.nats.v2_21.NatsTelemetry; +import io.opentelemetry.instrumentation.nats.v2_17.NatsTelemetry; public class OpenTelemetryNatsConnection { diff --git a/instrumentation/nats/nats-2.21/library/build.gradle.kts b/instrumentation/nats/nats-2.17/library/build.gradle.kts similarity index 66% rename from instrumentation/nats/nats-2.21/library/build.gradle.kts rename to instrumentation/nats/nats-2.17/library/build.gradle.kts index 14496c845003..81a03b6e8e17 100644 --- a/instrumentation/nats/nats-2.21/library/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/library/build.gradle.kts @@ -3,10 +3,10 @@ plugins { } dependencies { - library("io.nats:jnats:2.21.0") + library("io.nats:jnats:2.21.5") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") - testImplementation(project(":instrumentation:nats:nats-2.21:testing")) + testImplementation(project(":instrumentation:nats:nats-2.17:testing")) } diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java similarity index 91% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java index 29230a9f7921..c086f847f321 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java @@ -7,8 +7,8 @@ import io.nats.client.MessageHandler; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.nats.v2_21.OpenTelemetryMessageHandler; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.OpenTelemetryMessageHandler; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java similarity index 95% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java index b67a3872a772..dfe081fbb99a 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetry.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import io.nats.client.Connection; import io.nats.client.Options; @@ -11,7 +11,7 @@ import io.nats.client.impl.OpenTelemetryDispatcherFactory; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; public final class NatsTelemetry { diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java similarity index 92% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java index c416f52a168f..e3e6f56f8b75 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsTelemetryBuilder.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java @@ -3,13 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import static java.util.Collections.emptyList; import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsInstrumenterFactory; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsInstrumenterFactory; import java.util.ArrayList; import java.util.Collection; import java.util.List; diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java similarity index 98% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java index d1248ab506aa..4cd421637466 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import io.nats.client.Connection; import io.nats.client.Dispatcher; @@ -13,7 +13,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java similarity index 96% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java index 2c9beb10ce35..1e4089a5e596 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryDispatcher.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java @@ -3,13 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import io.nats.client.Dispatcher; import io.nats.client.MessageHandler; import io.nats.client.Subscription; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java similarity index 95% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java index e7bb21938cfe..70798dcaae75 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/OpenTelemetryMessageHandler.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import io.nats.client.Message; import io.nats.client.MessageHandler; @@ -12,7 +12,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; import io.opentelemetry.instrumentation.api.internal.Timer; -import io.opentelemetry.instrumentation.nats.v2_21.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java similarity index 97% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java index 3cd5ae171791..45340bcf0707 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21.internal; +package io.opentelemetry.instrumentation.nats.v2_17.internal; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; @@ -20,7 +20,7 @@ * any time. */ public final class NatsInstrumenterFactory { - private static final String INSTRUMENTATION_NAME = "io.opentelemetry.nats-2.21"; + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.nats-2.17"; public static Instrumenter createProducerInstrumenter( OpenTelemetry openTelemetry, List capturedHeaders) { diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java similarity index 95% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java index 1f62d7a7d1e5..cfb202d9f194 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequest.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21.internal; +package io.opentelemetry.instrumentation.nats.v2_17.internal; import com.google.auto.value.AutoValue; import io.nats.client.Connection; diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetter.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java similarity index 90% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetter.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java index 51344dcc06fb..5179cf15705c 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetter.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21.internal; +package io.opentelemetry.instrumentation.nats.v2_17.internal; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetterFactory.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetterFactory.java similarity index 97% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetterFactory.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetterFactory.java index 2c6a96d1f3c9..a34c6c2e71ba 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestMessagingAttributesGetterFactory.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetterFactory.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21.internal; +package io.opentelemetry.instrumentation.nats.v2_17.internal; import io.nats.client.impl.Headers; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapGetter.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestTextMapGetter.java similarity index 92% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapGetter.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestTextMapGetter.java index ada95ecf0a38..85b78c9fc65a 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapGetter.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestTextMapGetter.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21.internal; +package io.opentelemetry.instrumentation.nats.v2_17.internal; import io.nats.client.impl.Headers; import io.opentelemetry.context.propagation.TextMapGetter; diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapSetter.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestTextMapSetter.java similarity index 90% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapSetter.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestTextMapSetter.java index 7d7ab7dad183..62d44d832899 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/NatsRequestTextMapSetter.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestTextMapSetter.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21.internal; +package io.opentelemetry.instrumentation.nats.v2_17.internal; import io.opentelemetry.context.propagation.TextMapSetter; import javax.annotation.Nullable; diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier.java similarity index 83% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier.java index 3cfdd0e1ef71..287611ca4d2f 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21.internal; +package io.opentelemetry.instrumentation.nats.v2_17.internal; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at diff --git a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier2.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier2.java similarity index 84% rename from instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier2.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier2.java index b07185904d65..1e3e7dec3af6 100644 --- a/instrumentation/nats/nats-2.21/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/internal/ThrowingSupplier2.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier2.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21.internal; +package io.opentelemetry.instrumentation.nats.v2_17.internal; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java similarity index 94% rename from instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java rename to instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java index fef6d1d15995..7c47ab560250 100644 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import io.nats.client.Connection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java similarity index 95% rename from instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationExperimentalTest.java rename to instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java index c5a515a90986..13735394f06c 100644 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import static java.util.Collections.singletonList; diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java similarity index 94% rename from instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java rename to instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java index 449905b9f227..df23619bd2e2 100644 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import io.nats.client.Connection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; diff --git a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java similarity index 95% rename from instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java rename to instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java index cf68da2b8abe..a570f851a3d7 100644 --- a/instrumentation/nats/nats-2.21/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import io.nats.client.Connection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; diff --git a/instrumentation/nats/nats-2.21/metadata.yaml b/instrumentation/nats/nats-2.17/metadata.yaml similarity index 100% rename from instrumentation/nats/nats-2.21/metadata.yaml rename to instrumentation/nats/nats-2.17/metadata.yaml diff --git a/instrumentation/nats/nats-2.21/testing/build.gradle.kts b/instrumentation/nats/nats-2.17/testing/build.gradle.kts similarity index 71% rename from instrumentation/nats/nats-2.21/testing/build.gradle.kts rename to instrumentation/nats/nats-2.17/testing/build.gradle.kts index 10338c07fb9d..c8f7c07d1eb6 100644 --- a/instrumentation/nats/nats-2.21/testing/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/testing/build.gradle.kts @@ -5,5 +5,5 @@ plugins { dependencies { api(project(":testing-common")) - compileOnly("io.nats:jnats:2.21.0") + compileOnly("io.nats:jnats:2.21.5") } diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java similarity index 97% rename from instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationDispatcherTest.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java index c6188f13aa33..792b019877ca 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; -import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.messagingAttributes; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; import static org.assertj.core.api.Assertions.assertThatNoException; import io.nats.client.Connection; diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java similarity index 96% rename from instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationExperimentalTest.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java index 3ce1ae388e09..0be1d07a8ef5 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; -import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.messagingAttributes; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java similarity index 93% rename from instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationPublishTest.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java index add744124062..b1d8c9a46238 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; -import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.assertNoHeaders; -import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.assertTraceparentHeader; -import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.messagingAttributes; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.assertNoHeaders; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.assertTraceparentHeader; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; import io.nats.client.Connection; import io.nats.client.Subscription; diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java similarity index 98% rename from instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationRequestTest.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java index ab39138fd1a7..6bc3202ca17f 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/AbstractNatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; -import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.assertNoHeaders; -import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.assertTraceparentHeader; -import static io.opentelemetry.instrumentation.nats.v2_21.NatsInstrumentationTestHelper.messagingAttributes; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.assertNoHeaders; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.assertTraceparentHeader; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; import static java.util.Arrays.asList; import io.nats.client.Connection; diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationTestHelper.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java similarity index 98% rename from instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationTestHelper.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java index dd0b8b54c6c3..dbb85c4b9b5f 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/NatsInstrumentationTestHelper.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestConnection.java similarity index 99% rename from instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestConnection.java index 6590ff3bc534..d053aa4d9672 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestConnection.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestConnection.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import io.nats.client.Connection; import io.nats.client.ConnectionListener; diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestDispatcher.java similarity index 97% rename from instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestDispatcher.java index 5ec6ad692432..a492108877ee 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestDispatcher.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestDispatcher.java @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; -import static io.opentelemetry.instrumentation.nats.v2_21.TestConnection.INBOX_PREFIX; +import static io.opentelemetry.instrumentation.nats.v2_17.TestConnection.INBOX_PREFIX; import io.nats.client.Dispatcher; import io.nats.client.MessageHandler; diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestMessage.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestMessage.java similarity index 98% rename from instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestMessage.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestMessage.java index c4562380d0be..4d742c06f621 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestMessage.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestMessage.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import io.nats.client.Connection; import io.nats.client.Message; diff --git a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestSubscription.java similarity index 98% rename from instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestSubscription.java index 79638a925c23..2b4172c9b956 100644 --- a/instrumentation/nats/nats-2.21/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_21/TestSubscription.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestSubscription.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_21; +package io.opentelemetry.instrumentation.nats.v2_17; import io.nats.client.Dispatcher; import io.nats.client.Message; diff --git a/settings.gradle.kts b/settings.gradle.kts index 48235d49d680..b1eacafd5caa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -412,9 +412,9 @@ include(":instrumentation:mongo:mongo-4.0:javaagent") include(":instrumentation:mongo:mongo-async-3.3:javaagent") include(":instrumentation:mongo:mongo-common:testing") include(":instrumentation:mybatis-3.2:javaagent") -include(":instrumentation:nats:nats-2.21:javaagent") -include(":instrumentation:nats:nats-2.21:library") -include(":instrumentation:nats:nats-2.21:testing") +include(":instrumentation:nats:nats-2.17:javaagent") +include(":instrumentation:nats:nats-2.17:library") +include(":instrumentation:nats:nats-2.17:testing") include(":instrumentation:netty:netty-3.8:javaagent") include(":instrumentation:netty:netty-4.0:javaagent") include(":instrumentation:netty:netty-4.1:javaagent") From 3aae5606b4ca1cd0a66849139400339e6740a95d Mon Sep 17 00:00:00 2001 From: Alix Date: Thu, 28 Aug 2025 11:50:56 +0200 Subject: [PATCH 18/34] review fixes --- .../nats/nats-2.17/javaagent/README.md | 2 +- .../v2_17/MessageHandlerInstrumentation.java | 3 +-- .../nats/v2_17/internal/ThrowingSupplier.java | 16 ---------------- .../nats/v2_17/internal/ThrowingSupplier2.java | 16 ---------------- 4 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier.java delete mode 100644 instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier2.java diff --git a/instrumentation/nats/nats-2.17/javaagent/README.md b/instrumentation/nats/nats-2.17/javaagent/README.md index 7b7f4dea62e8..9a6f94285b40 100644 --- a/instrumentation/nats/nats-2.17/javaagent/README.md +++ b/instrumentation/nats/nats-2.17/javaagent/README.md @@ -1,4 +1,4 @@ -# Auth-instrumentation for NATS version 2.17 +# Auto-instrumentation for NATS version 2.17 Provides OpenTelemetry auto-instrumentation for [NATS 2.17](https://github.com/nats-io/nats.java). diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java index 5432a051986c..f26bd6c349c7 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java @@ -51,12 +51,11 @@ public static void onEnter( @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - Timer timer = Timer.start(); - Context parentContext = Context.current(); natsRequest = NatsRequest.create(message.getConnection(), message); if (CONSUMER_RECEIVE_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + Timer timer = Timer.start(); parentContext = InstrumenterUtil.startAndEnd( CONSUMER_RECEIVE_INSTRUMENTER, diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier.java deleted file mode 100644 index 287611ca4d2f..000000000000 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_17.internal; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -@FunctionalInterface -public interface ThrowingSupplier { - - T call() throws E; -} diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier2.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier2.java deleted file mode 100644 index 1e3e7dec3af6..000000000000 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/ThrowingSupplier2.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_17.internal; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -@FunctionalInterface -public interface ThrowingSupplier2 { - - T call() throws E, E2; -} From 6c092ad9a2d3e0237159f03d53bf7cec8fd81627 Mon Sep 17 00:00:00 2001 From: Alix Date: Thu, 28 Aug 2025 11:53:23 +0200 Subject: [PATCH 19/34] disabled_by_default: false --- instrumentation/nats/nats-2.17/metadata.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/nats/nats-2.17/metadata.yaml b/instrumentation/nats/nats-2.17/metadata.yaml index 455ef35e9957..dbbd9f50496a 100644 --- a/instrumentation/nats/nats-2.17/metadata.yaml +++ b/instrumentation/nats/nats-2.17/metadata.yaml @@ -1,4 +1,4 @@ -disabled_by_default: true +disabled_by_default: false description: This instrumentation provides messaging spans for NATS configurations: - name: otel.instrumentation.messaging.experimental.receive-telemetry.enabled From fefeebd8cb294b3168265e366b3938754ccd272f Mon Sep 17 00:00:00 2001 From: Alix Date: Thu, 28 Aug 2025 19:47:51 +0200 Subject: [PATCH 20/34] use @BeforeAll over static init --- .../nats/v2_17/DispatcherInstrumentation.java | 2 -- .../nats/v2_17/SpanFinisher.java | 2 +- .../NatsInstrumentationDispatcherTest.java | 20 +++++++--------- .../NatsInstrumentationExperimentalTest.java | 20 +++++++--------- .../v2_17/NatsInstrumentationPublishTest.java | 20 +++++++--------- .../v2_17/NatsInstrumentationRequestTest.java | 20 +++++++--------- .../nats/v2_17/OpenTelemetryConnection.java | 24 ++++++++----------- .../NatsInstrumentationDispatcherTest.java | 8 ++++--- .../NatsInstrumentationExperimentalTest.java | 8 ++++--- .../v2_17/NatsInstrumentationPublishTest.java | 8 ++++--- .../v2_17/NatsInstrumentationRequestTest.java | 8 ++++--- 11 files changed, 67 insertions(+), 73 deletions(-) diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/DispatcherInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/DispatcherInstrumentation.java index b32d5af19247..8f33bb3c57f8 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/DispatcherInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/DispatcherInstrumentation.java @@ -40,8 +40,6 @@ public static Scope onEnter() { // Dispatchers are usually background long-lived threads, we can force root, // as we're not expecting to be anything else than entry points for network messages. if (Java8BytecodeBridge.currentContext() != Java8BytecodeBridge.rootContext()) { - // Prevent context from leaking by running this method under root context. - // Root context is not propagated by executor instrumentation. return Java8BytecodeBridge.rootContext().makeCurrent(); } return null; diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/SpanFinisher.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/SpanFinisher.java index 8659542b46c5..a02b9a618f79 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/SpanFinisher.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/SpanFinisher.java @@ -12,7 +12,7 @@ import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import java.util.function.BiConsumer; -public class SpanFinisher implements BiConsumer { +public final class SpanFinisher implements BiConsumer { private final Instrumenter instrumenter; private final Context context; private final Connection connection; diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java index ec5ce42b3a0b..4e52387bf51f 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java @@ -14,6 +14,7 @@ import java.time.Duration; import java.util.concurrent.TimeoutException; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; @@ -23,23 +24,20 @@ class NatsInstrumentationDispatcherTest extends AbstractNatsInstrumentationDispa @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - static final DockerImageName natsImage; - static final GenericContainer natsContainer; - static final Connection natsConnection; + static DockerImageName natsImage; + static GenericContainer natsContainer; + static Connection natsConnection; - static { + @BeforeAll + static void beforeAll() throws IOException, InterruptedException { natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); natsContainer.start(); - try { - String host = natsContainer.getHost(); - Integer port = natsContainer.getMappedPort(4222); - natsConnection = Nats.connect("nats://" + host + ":" + port); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } + String host = natsContainer.getHost(); + Integer port = natsContainer.getMappedPort(4222); + natsConnection = Nats.connect("nats://" + host + ":" + port); } @AfterAll diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java index 05b8c90b3517..bbbba814e45d 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java @@ -14,6 +14,7 @@ import java.time.Duration; import java.util.concurrent.TimeoutException; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; @@ -23,23 +24,20 @@ class NatsInstrumentationExperimentalTest extends AbstractNatsInstrumentationExp @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - static final DockerImageName natsImage; - static final GenericContainer natsContainer; - static final Connection natsConnection; + static DockerImageName natsImage; + static GenericContainer natsContainer; + static Connection natsConnection; - static { + @BeforeAll + static void beforeAll() throws IOException, InterruptedException { natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); natsContainer.start(); - try { - String host = natsContainer.getHost(); - Integer port = natsContainer.getMappedPort(4222); - natsConnection = Nats.connect("nats://" + host + ":" + port); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } + String host = natsContainer.getHost(); + Integer port = natsContainer.getMappedPort(4222); + natsConnection = Nats.connect("nats://" + host + ":" + port); } @AfterAll diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java index 6de245775cb3..e98063b6578c 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java @@ -14,6 +14,7 @@ import java.time.Duration; import java.util.concurrent.TimeoutException; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; @@ -23,23 +24,20 @@ class NatsInstrumentationPublishTest extends AbstractNatsInstrumentationPublishT @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - static final DockerImageName natsImage; - static final GenericContainer natsContainer; - static final Connection natsConnection; + static DockerImageName natsImage; + static GenericContainer natsContainer; + static Connection natsConnection; - static { + @BeforeAll + static void beforeAll() throws IOException, InterruptedException { natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); natsContainer.start(); - try { - String host = natsContainer.getHost(); - Integer port = natsContainer.getMappedPort(4222); - natsConnection = Nats.connect("nats://" + host + ":" + port); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } + String host = natsContainer.getHost(); + Integer port = natsContainer.getMappedPort(4222); + natsConnection = Nats.connect("nats://" + host + ":" + port); } @AfterAll diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java index 29f9ba4dc23a..0bf164f722fe 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java @@ -14,6 +14,7 @@ import java.time.Duration; import java.util.concurrent.TimeoutException; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; @@ -23,23 +24,20 @@ class NatsInstrumentationRequestTest extends AbstractNatsInstrumentationRequestT @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - static final DockerImageName natsImage; - static final GenericContainer natsContainer; - static final Connection natsConnection; + static DockerImageName natsImage; + static GenericContainer natsContainer; + static Connection natsConnection; - static { + @BeforeAll + static void beforeAll() throws IOException, InterruptedException { natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); natsContainer.start(); - try { - String host = natsContainer.getHost(); - Integer port = natsContainer.getMappedPort(4222); - natsConnection = Nats.connect("nats://" + host + ":" + port); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } + String host = natsContainer.getHost(); + Integer port = natsContainer.getMappedPort(4222); + natsConnection = Nats.connect("nats://" + host + ":" + port); } @AfterAll diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java index 4cd421637466..3c6884812451 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java @@ -79,14 +79,10 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl return closeDispatcher(method, args); } - try { - return method.invoke(delegate, args); - } catch (InvocationTargetException e) { - throw e.getCause(); - } + return invokeMethod(method, delegate, args); } - private static Object invokeProxyMethod(Method method, Object target, Object[] args) + private static Object invokeMethod(Method method, Object target, Object[] args) throws Throwable { try { return method.invoke(target, args); @@ -134,12 +130,12 @@ private Object publish(Method method, Object[] args) throws Throwable { Context parentContext = Context.current(); if (natsRequest == null || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { - return invokeProxyMethod(method, delegate, args); + return invokeMethod(method, delegate, args); } Context context = producerInstrumenter.start(parentContext, natsRequest); try (Scope ignored = context.makeCurrent()) { - return invokeProxyMethod(method, delegate, args); + return invokeMethod(method, delegate, args); } finally { producerInstrumenter.end(context, natsRequest, null, null); } @@ -169,7 +165,7 @@ private Message request(Method method, Object[] args) throws Throwable { Context parentContext = Context.current(); if (natsRequest == null || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { - return (Message) invokeProxyMethod(method, delegate, args); + return (Message) invokeMethod(method, delegate, args); } Context context = producerInstrumenter.start(parentContext, natsRequest); @@ -177,7 +173,7 @@ private Message request(Method method, Object[] args) throws Throwable { NatsRequest response = null; try (Scope ignored = context.makeCurrent()) { - Message message = (Message) invokeProxyMethod(method, delegate, args); + Message message = (Message) invokeMethod(method, delegate, args); if (message == null) { timeout = new TimeoutException("Timed out waiting for message"); @@ -220,13 +216,13 @@ private CompletableFuture requestAsync(Method method, Object[] args) th Context parentContext = Context.current(); if (natsRequest == null || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { - return (CompletableFuture) invokeProxyMethod(method, delegate, args); + return (CompletableFuture) invokeMethod(method, delegate, args); } NatsRequest notNullNatsRequest = natsRequest; Context context = producerInstrumenter.start(parentContext, notNullNatsRequest); - return ((CompletableFuture) invokeProxyMethod(method, delegate, args)) + return ((CompletableFuture) invokeMethod(method, delegate, args)) .whenComplete( (message, exception) -> { if (message != null) { @@ -247,7 +243,7 @@ private Dispatcher createDispatcher(Method method, Object[] args) throws Throwab (MessageHandler) args[0], consumerReceiveInstrumenter, consumerProcessInstrumenter); } - Dispatcher wrapped = (Dispatcher) invokeProxyMethod(method, delegate, args); + Dispatcher wrapped = (Dispatcher) invokeMethod(method, delegate, args); return OpenTelemetryDispatcher.wrap( wrapped, consumerReceiveInstrumenter, consumerProcessInstrumenter); } @@ -260,6 +256,6 @@ private Object closeDispatcher(Method method, Object[] args) throws Throwable { args[0] = ((OpenTelemetryDispatcher) Proxy.getInvocationHandler(args[0])).getDelegate(); } - return invokeProxyMethod(method, delegate, args); + return invokeMethod(method, delegate, args); } } diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java index 7c47ab560250..83bfa114a197 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java @@ -8,6 +8,7 @@ import io.nats.client.Connection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; class NatsInstrumentationDispatcherTest extends AbstractNatsInstrumentationDispatcherTest { @@ -15,10 +16,11 @@ class NatsInstrumentationDispatcherTest extends AbstractNatsInstrumentationDispa @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - static final NatsTelemetry telemetry; - static final Connection natsConnection; + static NatsTelemetry telemetry; + static Connection natsConnection; - static { + @BeforeAll + static void beforeAll() { telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); natsConnection = telemetry.wrap(new TestConnection()); } diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java index 13735394f06c..cba06fcc48b5 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java @@ -10,6 +10,7 @@ import io.nats.client.Connection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; class NatsInstrumentationExperimentalTest extends AbstractNatsInstrumentationExperimentalTest { @@ -17,10 +18,11 @@ class NatsInstrumentationExperimentalTest extends AbstractNatsInstrumentationExp @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - static final NatsTelemetry telemetry; - static final Connection natsConnection; + static NatsTelemetry telemetry; + static Connection natsConnection; - static { + @BeforeAll + static void beforeAll() { telemetry = NatsTelemetry.builder(testing.getOpenTelemetry()) .setMessagingReceiveInstrumentationEnabled(true) diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java index df23619bd2e2..95c876549760 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java @@ -8,6 +8,7 @@ import io.nats.client.Connection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; class NatsInstrumentationPublishTest extends AbstractNatsInstrumentationPublishTest { @@ -15,10 +16,11 @@ class NatsInstrumentationPublishTest extends AbstractNatsInstrumentationPublishT @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - static final NatsTelemetry telemetry; - static final Connection natsConnection; + static NatsTelemetry telemetry; + static Connection natsConnection; - static { + @BeforeAll + static void beforeAll() { telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); natsConnection = telemetry.wrap(new TestConnection()); } diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java index a570f851a3d7..36fe979f4c3c 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java @@ -8,6 +8,7 @@ import io.nats.client.Connection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; class NatsInstrumentationRequestTest extends AbstractNatsInstrumentationRequestTest { @@ -15,10 +16,11 @@ class NatsInstrumentationRequestTest extends AbstractNatsInstrumentationRequestT @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - static final NatsTelemetry telemetry; - static final Connection natsConnection; + static NatsTelemetry telemetry; + static Connection natsConnection; - static { + @BeforeAll + static void beforeAll() { telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); natsConnection = telemetry.wrap(new TestConnection()); } From 01a48a815b2f45fbb4de3997d36066adf1a74c99 Mon Sep 17 00:00:00 2001 From: Alix Date: Fri, 29 Aug 2025 13:53:49 +0200 Subject: [PATCH 21/34] (args) to (Message) use Docker for library testing --- .../nats/nats-2.17/javaagent/README.md | 16 - .../nats/nats-2.17/javaagent/build.gradle.kts | 5 +- .../ConnectionPublishInstrumentation.java | 149 ++---- .../ConnectionRequestInstrumentation.java | 298 ++++-------- .../NatsInstrumentationDispatcherTest.java | 35 -- .../NatsInstrumentationExperimentalTest.java | 35 -- .../v2_17/NatsInstrumentationPublishTest.java | 35 -- .../v2_17/NatsInstrumentationRequestTest.java | 37 +- .../nats/nats-2.17/library/build.gradle.kts | 7 + .../nats/v2_17/OpenTelemetryConnection.java | 119 +++-- .../v2_17/OpenTelemetryMessageHandler.java | 3 +- .../internal/NatsMessageWritableHeaders.java | 48 ++ .../nats/v2_17/internal/NatsRequest.java | 13 +- .../NatsInstrumentationDispatcherTest.java | 17 +- .../NatsInstrumentationExperimentalTest.java | 23 +- .../v2_17/NatsInstrumentationPublishTest.java | 16 +- .../v2_17/NatsInstrumentationRequestTest.java | 16 +- .../nats/nats-2.17/testing/build.gradle.kts | 2 + ...ractNatsInstrumentationDispatcherTest.java | 75 ++- ...ctNatsInstrumentationExperimentalTest.java | 16 +- ...bstractNatsInstrumentationPublishTest.java | 31 +- ...bstractNatsInstrumentationRequestTest.java | 166 +++---- .../AbstractNatsInstrumentationTest.java | 44 ++ .../v2_17/NatsInstrumentationTestHelper.java | 5 - .../nats/v2_17/TestConnection.java | 430 ------------------ .../nats/v2_17/TestDispatcher.java | 163 ------- .../nats/v2_17/TestMessage.java | 140 ------ .../nats/v2_17/TestSubscription.java | 158 ------- 28 files changed, 416 insertions(+), 1686 deletions(-) create mode 100644 instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java create mode 100644 instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationTest.java delete mode 100644 instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestConnection.java delete mode 100644 instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestDispatcher.java delete mode 100644 instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestMessage.java delete mode 100644 instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestSubscription.java diff --git a/instrumentation/nats/nats-2.17/javaagent/README.md b/instrumentation/nats/nats-2.17/javaagent/README.md index 9a6f94285b40..cbe7240f3438 100644 --- a/instrumentation/nats/nats-2.17/javaagent/README.md +++ b/instrumentation/nats/nats-2.17/javaagent/README.md @@ -2,19 +2,3 @@ Provides OpenTelemetry auto-instrumentation for [NATS 2.17](https://github.com/nats-io/nats.java). -### Trace propagation - -It's recommended to provide `Message` with a writable `Header` structure -to allow propagation between publishers and subscribers. Without headers, -the tracing context will not be propagated in the headers. - -```java -import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsMessage; - -// don't -Message msg = NatsMessage.builder().subject("sub").build(); - -// do -Message msg = NatsMessage.builder().subject("sub").headers(new Headers()).build(); -``` diff --git a/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts index 79d49fb8cca9..ca8a013d1f12 100644 --- a/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts @@ -25,6 +25,10 @@ dependencies { val collectMetadata = findProperty("collectMetadata")?.toString() ?: "false" tasks { + withType().configureEach { + usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + } + val testExperimental by registering(Test::class) { filter { includeTestsMatching("NatsInstrumentationExperimentalTest") @@ -36,7 +40,6 @@ tasks { } test { - usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) filter { excludeTestsMatching("NatsInstrumentationExperimentalTest") } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java index 48cd8f4de826..b10f5dd3146c 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java @@ -17,6 +17,7 @@ import io.nats.client.impl.Headers; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsMessageWritableHeaders; import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; @@ -75,153 +76,53 @@ public void transform(TypeTransformer transformer) { @SuppressWarnings("unused") public static class PublishBodyAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(1) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - Context parentContext = Context.current(); - natsRequest = NatsRequest.create(connection, null, subject, null, body); - - if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); - otelScope = otelContext.makeCurrent(); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.Thrown Throwable throwable, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - otelScope.close(); - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + @Advice.Argument(1) byte[] body) { + connection.publish( NatsMessageWritableHeaders.create(subject, body)); + return true; } } @SuppressWarnings("unused") public static class PublishHeadersBodyAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(1) Headers headers, - @Advice.Argument(2) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - Context parentContext = Context.current(); - natsRequest = NatsRequest.create(connection, null, subject, headers, body); - - if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); - otelScope = otelContext.makeCurrent(); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.Thrown Throwable throwable, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - otelScope.close(); - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + @Advice.Argument(value = 1, readOnly = false) Headers headers, + @Advice.Argument(2) byte[] body) { + connection.publish( NatsMessageWritableHeaders.create(subject, headers, body)); + return true; } } @SuppressWarnings("unused") public static class PublishReplyToBodyAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, @Advice.Argument(1) String replyTo, - @Advice.Argument(2) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - Context parentContext = Context.current(); - natsRequest = NatsRequest.create(connection, replyTo, subject, null, body); - - if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); - otelScope = otelContext.makeCurrent(); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.Thrown Throwable throwable, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - otelScope.close(); - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + @Advice.Argument(2) byte[] body) { + connection.publish(NatsMessageWritableHeaders.create(subject, replyTo, body)); + return true; } } @SuppressWarnings("unused") public static class PublishReplyToHeadersBodyAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, @Advice.Argument(1) String replyTo, - @Advice.Argument(2) Headers headers, - @Advice.Argument(3) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - Context parentContext = Context.current(); - natsRequest = NatsRequest.create(connection, replyTo, subject, headers, body); - - if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); - otelScope = otelContext.makeCurrent(); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.Thrown Throwable throwable, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - otelScope.close(); - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); + @Advice.Argument(value = 2, readOnly = false) Headers headers, + @Advice.Argument(3) byte[] body) { + connection.publish(NatsMessageWritableHeaders.create(subject, replyTo, headers, body)); + return true; } } @@ -231,10 +132,12 @@ public static class PublishMessageAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @Advice.This Connection connection, - @Advice.Argument(0) Message message, + @Advice.Argument(value = 0, readOnly = false) Message message, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { + message = NatsMessageWritableHeaders.create(message); + Context parentContext = Context.current(); natsRequest = NatsRequest.create(connection, message); diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java index 64ebe90d5036..0e9550347026 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java @@ -18,12 +18,12 @@ import io.nats.client.impl.Headers; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsMessageWritableHeaders; import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import java.time.Duration; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -121,95 +121,48 @@ public void transform(TypeTransformer transformer) { @SuppressWarnings("unused") public static class RequestBodyAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static Message onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, @Advice.Argument(1) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - natsRequest = NatsRequest.create(connection, null, subject, null, body); - Context parentContext = Context.current(); - - if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); - otelScope = otelContext.makeCurrent(); + @Advice.Argument(2) Duration timeout, + @Advice.Local("message") Message message + ) throws InterruptedException { + message = connection.request(NatsMessageWritableHeaders.create(subject,body), timeout); + return message; } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.This Connection connection, - @Advice.Thrown Throwable throwable, - @Advice.Return Message message, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - Throwable error = throwable; - NatsRequest natsResponse = null; - if (error == null && message == null) { - error = new TimeoutException("Timed out waiting for message"); - } else { - natsResponse = NatsRequest.create(connection, message); - } - - otelScope.close(); - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, error); + public static Message onExit( + @Advice.Return(readOnly = false) Message returned, + @Advice.Local("message") Message message) { + returned = message; + return returned; } } @SuppressWarnings("unused") public static class RequestHeadersBodyAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static Message onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(1) Headers headers, + @Advice.Argument(value = 1, readOnly = false) Headers headers, @Advice.Argument(2) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - natsRequest = NatsRequest.create(connection, null, subject, headers, body); - Context parentContext = Context.current(); - - if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); - otelScope = otelContext.makeCurrent(); + @Advice.Argument(3) Duration timeout, + @Advice.Local("message") Message message) throws InterruptedException { + message = connection.request(NatsMessageWritableHeaders.create(subject, headers, body), timeout); + return message; } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.This Connection connection, - @Advice.Thrown Throwable throwable, - @Advice.Return Message message, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - Throwable error = throwable; - NatsRequest natsResponse = null; - if (error == null && message == null) { - error = new TimeoutException("Timed out waiting for message"); - } else { - natsResponse = NatsRequest.create(connection, message); - } - - otelScope.close(); - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, error); + public static Message onExit( + @Advice.Return(readOnly = false) Message returned, + @Advice.Local("message") Message message) { + returned = message; + return returned; } } @@ -219,10 +172,13 @@ public static class RequestMessageAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @Advice.This Connection connection, - @Advice.Argument(0) Message message, + @Advice.Argument(value = 0, readOnly = false) Message message, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { + message = NatsMessageWritableHeaders.create(message); + + natsRequest = NatsRequest.create(connection, message); Context parentContext = Context.current(); @@ -246,113 +202,58 @@ public static void onExit( return; } - Throwable error = throwable; NatsRequest natsResponse = null; - if (error == null && message == null) { - error = new TimeoutException("Timed out waiting for message"); - } else { + if (message != null) { natsResponse = NatsRequest.create(connection, message); } otelScope.close(); - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, error); + PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, throwable); } } @SuppressWarnings("unused") public static class RequestFutureBodyAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, @Advice.Argument(1) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelParentContext") Context otelParentContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - natsRequest = NatsRequest.create(connection, null, subject, null, body); - otelParentContext = Context.current(); - - if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(otelParentContext, natsRequest); - otelScope = otelContext.makeCurrent(); + @Advice.Local("future") CompletableFuture future) { + future = connection.request(NatsMessageWritableHeaders.create(subject, body)); + return future; } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.This Connection connection, - @Advice.Thrown Throwable throwable, + public static CompletableFuture onExit( @Advice.Return(readOnly = false) CompletableFuture messageFuture, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelParentContext") Context otelParentContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - otelScope.close(); - if (throwable != null) { - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); - } else { - messageFuture = - messageFuture.whenComplete( - new SpanFinisher(PRODUCER_INSTRUMENTER, otelContext, connection, natsRequest)); - messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); - } + @Advice.Local("future") CompletableFuture future) { + messageFuture = future; + return messageFuture; } } @SuppressWarnings("unused") public static class RequestFutureHeadersBodyAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(1) Headers headers, + @Advice.Argument(value = 1,readOnly = false) Headers headers, @Advice.Argument(2) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelParentContext") Context otelParentContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - natsRequest = NatsRequest.create(connection, null, subject, headers, body); - otelParentContext = Context.current(); - - if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(otelParentContext, natsRequest); - otelScope = otelContext.makeCurrent(); + @Advice.Local("future") CompletableFuture future) { + future = connection.request(NatsMessageWritableHeaders.create(subject, headers, body)); + return future; } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.This Connection connection, - @Advice.Thrown Throwable throwable, + public static CompletableFuture onExit( @Advice.Return(readOnly = false) CompletableFuture messageFuture, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelParentContext") Context otelParentContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - otelScope.close(); - if (throwable != null) { - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); - } else { - messageFuture = - messageFuture.whenComplete( - new SpanFinisher(PRODUCER_INSTRUMENTER, otelContext, connection, natsRequest)); - messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); - } + @Advice.Local("future") CompletableFuture future) { + messageFuture = future; + return messageFuture; } } @@ -362,11 +263,14 @@ public static class RequestFutureMessageAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @Advice.This Connection connection, - @Advice.Argument(0) Message message, + @Advice.Argument(value = 0, readOnly = false) Message message, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelParentContext") Context otelParentContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { + message = NatsMessageWritableHeaders.create(message); + + natsRequest = NatsRequest.create(connection, message); otelParentContext = Context.current(); @@ -406,97 +310,45 @@ public static void onExit( @SuppressWarnings("unused") public static class RequestTimeoutFutureBodyAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, @Advice.Argument(1) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelParentContext") Context otelParentContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - natsRequest = NatsRequest.create(connection, null, subject, null, body); - otelParentContext = Context.current(); - - if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(otelParentContext, natsRequest); - otelScope = otelContext.makeCurrent(); + @Advice.Local("future") CompletableFuture future) { + future = connection.request(NatsMessageWritableHeaders.create(subject, body)); + return future; } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.This Connection connection, - @Advice.Thrown Throwable throwable, + public static CompletableFuture onExit( @Advice.Return(readOnly = false) CompletableFuture messageFuture, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelParentContext") Context otelParentContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - otelScope.close(); - if (throwable != null) { - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); - } else { - messageFuture = - messageFuture.whenComplete( - new SpanFinisher(PRODUCER_INSTRUMENTER, otelContext, connection, natsRequest)); - messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); - } + @Advice.Local("future") CompletableFuture future) { + messageFuture = future; + return messageFuture; } } @SuppressWarnings("unused") public static class RequestTimeoutFutureHeadersBodyAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(1) Headers headers, + @Advice.Argument(value = 1, readOnly = false) Headers headers, @Advice.Argument(2) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelParentContext") Context otelParentContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - natsRequest = NatsRequest.create(connection, null, subject, headers, body); - otelParentContext = Context.current(); - - if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(otelParentContext, natsRequest); - otelScope = otelContext.makeCurrent(); + @Advice.Local("future") CompletableFuture future) { + future = connection.request(NatsMessageWritableHeaders.create(subject, headers, body)); + return future; } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.This Connection connection, - @Advice.Thrown Throwable throwable, + public static CompletableFuture onExit( @Advice.Return(readOnly = false) CompletableFuture messageFuture, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelParentContext") Context otelParentContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - otelScope.close(); - if (throwable != null) { - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); - } else { - messageFuture = - messageFuture.whenComplete( - new SpanFinisher(PRODUCER_INSTRUMENTER, otelContext, connection, natsRequest)); - messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); - } + @Advice.Local("future") CompletableFuture future) { + messageFuture = future; + return messageFuture; } } @@ -506,11 +358,13 @@ public static class RequestTimeoutFutureMessageAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @Advice.This Connection connection, - @Advice.Argument(0) Message message, + @Advice.Argument(value = 0,readOnly = false) Message message, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelParentContext") Context otelParentContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { + message = NatsMessageWritableHeaders.create(message); + natsRequest = NatsRequest.create(connection, message); otelParentContext = Context.current(); diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java index 4e52387bf51f..ae91a178e8d7 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java @@ -5,54 +5,19 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_17; -import io.nats.client.Connection; -import io.nats.client.Nats; import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsInstrumentationDispatcherTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import java.io.IOException; -import java.time.Duration; -import java.util.concurrent.TimeoutException; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.utility.DockerImageName; class NatsInstrumentationDispatcherTest extends AbstractNatsInstrumentationDispatcherTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - static DockerImageName natsImage; - static GenericContainer natsContainer; - static Connection natsConnection; - - @BeforeAll - static void beforeAll() throws IOException, InterruptedException { - natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); - - natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); - natsContainer.start(); - - String host = natsContainer.getHost(); - Integer port = natsContainer.getMappedPort(4222); - natsConnection = Nats.connect("nats://" + host + ":" + port); - } - - @AfterAll - static void afterAll() throws InterruptedException, TimeoutException { - natsConnection.drain(Duration.ZERO); - natsContainer.close(); - } - @Override protected InstrumentationExtension testing() { return testing; } - @Override - protected Connection connection() { - return natsConnection; - } } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java index bbbba814e45d..e16d1dd9ca83 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java @@ -5,54 +5,19 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_17; -import io.nats.client.Connection; -import io.nats.client.Nats; import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsInstrumentationExperimentalTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import java.io.IOException; -import java.time.Duration; -import java.util.concurrent.TimeoutException; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.utility.DockerImageName; class NatsInstrumentationExperimentalTest extends AbstractNatsInstrumentationExperimentalTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - static DockerImageName natsImage; - static GenericContainer natsContainer; - static Connection natsConnection; - - @BeforeAll - static void beforeAll() throws IOException, InterruptedException { - natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); - - natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); - natsContainer.start(); - - String host = natsContainer.getHost(); - Integer port = natsContainer.getMappedPort(4222); - natsConnection = Nats.connect("nats://" + host + ":" + port); - } - - @AfterAll - static void afterAll() throws InterruptedException, TimeoutException { - natsConnection.drain(Duration.ZERO); - natsContainer.close(); - } - @Override protected InstrumentationExtension testing() { return testing; } - @Override - protected Connection connection() { - return natsConnection; - } } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java index e98063b6578c..c37b11b275aa 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java @@ -5,54 +5,19 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_17; -import io.nats.client.Connection; -import io.nats.client.Nats; import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsInstrumentationPublishTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import java.io.IOException; -import java.time.Duration; -import java.util.concurrent.TimeoutException; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.utility.DockerImageName; class NatsInstrumentationPublishTest extends AbstractNatsInstrumentationPublishTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - static DockerImageName natsImage; - static GenericContainer natsContainer; - static Connection natsConnection; - - @BeforeAll - static void beforeAll() throws IOException, InterruptedException { - natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); - - natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); - natsContainer.start(); - - String host = natsContainer.getHost(); - Integer port = natsContainer.getMappedPort(4222); - natsConnection = Nats.connect("nats://" + host + ":" + port); - } - - @AfterAll - static void afterAll() throws InterruptedException, TimeoutException { - natsConnection.drain(Duration.ZERO); - natsContainer.close(); - } - @Override protected InstrumentationExtension testing() { return testing; } - @Override - protected Connection connection() { - return natsConnection; - } } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java index 0bf164f722fe..daaab71471fe 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java @@ -5,59 +5,24 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_17; -import io.nats.client.Connection; -import io.nats.client.Nats; import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsInstrumentationRequestTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import java.io.IOException; -import java.time.Duration; -import java.util.concurrent.TimeoutException; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.utility.DockerImageName; class NatsInstrumentationRequestTest extends AbstractNatsInstrumentationRequestTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - static DockerImageName natsImage; - static GenericContainer natsContainer; - static Connection natsConnection; - - @BeforeAll - static void beforeAll() throws IOException, InterruptedException { - natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); - - natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); - natsContainer.start(); - - String host = natsContainer.getHost(); - Integer port = natsContainer.getMappedPort(4222); - natsConnection = Nats.connect("nats://" + host + ":" + port); - } - - @AfterAll - static void afterAll() throws InterruptedException, TimeoutException { - natsConnection.drain(Duration.ZERO); - natsContainer.close(); - } - @Override protected InstrumentationExtension testing() { return testing; } - @Override - protected Connection connection() { - return natsConnection; - } - @Override protected boolean isInboxMonitored() { return true; } + } diff --git a/instrumentation/nats/nats-2.17/library/build.gradle.kts b/instrumentation/nats/nats-2.17/library/build.gradle.kts index 81a03b6e8e17..040c68f98a06 100644 --- a/instrumentation/nats/nats-2.17/library/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/library/build.gradle.kts @@ -10,3 +10,10 @@ dependencies { testImplementation(project(":instrumentation:nats:nats-2.17:testing")) } + + +tasks { + withType().configureEach { + usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + } +} diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java index 3c6884812451..8457b98d8c0b 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java @@ -13,13 +13,14 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsMessageWritableHeaders; import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.time.Duration; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; final class OpenTelemetryConnection implements InvocationHandler { @@ -58,7 +59,8 @@ public static Connection wrap( @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("publish".equals(method.getName()) && method.getReturnType().equals(Void.TYPE)) { - return publish(method, args); + publish(method, args); + return null; } if ("request".equals(method.getName()) && method.getReturnType().equals(Message.class)) { @@ -96,46 +98,51 @@ private static Object invokeMethod(Method method, Object target, Object[] args) // void publish(String subject, String replyTo, byte[] body) // void publish(String subject, String replyTo, Headers headers, byte[] body) // void publish(Message message) - private Object publish(Method method, Object[] args) throws Throwable { + private void publish(Method method, Object[] args) throws Throwable { + Message message = null; NatsRequest natsRequest = null; if (method.getParameterCount() == 2 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == byte[].class) { - natsRequest = NatsRequest.create(delegate, null, (String) args[0], null, (byte[]) args[1]); + message = NatsMessageWritableHeaders.create((String) args[0], (byte[]) args[1]); } else if (method.getParameterCount() == 3 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == Headers.class && method.getParameterTypes()[2] == byte[].class) { - natsRequest = - NatsRequest.create(delegate, null, (String) args[0], (Headers) args[1], (byte[]) args[2]); + message = NatsMessageWritableHeaders.create((String) args[0], (Headers) args[1], + (byte[]) args[2]); } else if (method.getParameterCount() == 3 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == String.class && method.getParameterTypes()[2] == byte[].class) { - natsRequest = - NatsRequest.create(delegate, (String) args[1], (String) args[0], null, (byte[]) args[2]); + message = NatsMessageWritableHeaders.create((String) args[0], (String) args[1], + (byte[]) args[2]); } else if (method.getParameterCount() == 4 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == String.class && method.getParameterTypes()[2] == Headers.class && method.getParameterTypes()[3] == byte[].class) { - natsRequest = - NatsRequest.create( - delegate, (String) args[1], (String) args[0], (Headers) args[2], (byte[]) args[3]); + message = NatsMessageWritableHeaders.create((String) args[0], (String) args[1], + (Headers) args[2], (byte[]) args[3]); } else if (method.getParameterCount() == 1 && method.getParameterTypes()[0] == Message.class) { - natsRequest = NatsRequest.create(delegate, (Message) args[0]); + message = NatsMessageWritableHeaders.create((Message) args[0]); } Context parentContext = Context.current(); + if (message != null) { + natsRequest = NatsRequest.create(delegate, message); + } + if (natsRequest == null || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { - return invokeMethod(method, delegate, args); + invokeMethod(method, delegate, args); + return; } Context context = producerInstrumenter.start(parentContext, natsRequest); try (Scope ignored = context.makeCurrent()) { - return invokeMethod(method, delegate, args); + delegate.publish(message); } finally { producerInstrumenter.end(context, natsRequest, null, null); } @@ -146,44 +153,51 @@ private Object publish(Method method, Object[] args) throws Throwable { // InterruptedException; // Message request(Message message, Duration timeout) throws InterruptedException; private Message request(Method method, Object[] args) throws Throwable { + Message message = null; NatsRequest natsRequest = null; + Duration timeout = null; if (method.getParameterCount() == 3 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == byte[].class) { - natsRequest = NatsRequest.create(delegate, null, (String) args[0], null, (byte[]) args[1]); + message = NatsMessageWritableHeaders.create((String) args[0], (byte[]) args[1]); + timeout = (Duration) args[2]; } else if (method.getParameterCount() == 4 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == Headers.class && method.getParameterTypes()[2] == byte[].class) { - natsRequest = - NatsRequest.create(delegate, null, (String) args[0], (Headers) args[1], (byte[]) args[2]); + message = NatsMessageWritableHeaders.create((String) args[0], (Headers) args[1], + (byte[]) args[2]); + timeout = (Duration) args[3]; } else if (method.getParameterCount() == 2 && method.getParameterTypes()[0] == Message.class) { - natsRequest = NatsRequest.create(delegate, (Message) args[0]); + message = NatsMessageWritableHeaders.create((Message) args[0]); + timeout = (Duration) args[1]; } Context parentContext = Context.current(); - if (natsRequest == null || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { + if (message != null) { + natsRequest = NatsRequest.create(delegate, message); + } + + if (timeout == null || natsRequest == null || !producerInstrumenter.shouldStart(parentContext, + natsRequest)) { return (Message) invokeMethod(method, delegate, args); } Context context = producerInstrumenter.start(parentContext, natsRequest); - TimeoutException timeout = null; NatsRequest response = null; try (Scope ignored = context.makeCurrent()) { - Message message = (Message) invokeMethod(method, delegate, args); + Message result = delegate.request(message, timeout); - if (message == null) { - timeout = new TimeoutException("Timed out waiting for message"); - } else { - response = NatsRequest.create(delegate, message); + if (result != null) { + response = NatsRequest.create(delegate, result); } - return message; + return result; } finally { - producerInstrumenter.end(context, natsRequest, response, timeout); + producerInstrumenter.end(context, natsRequest, response, null); } } @@ -196,25 +210,47 @@ private Message request(Method method, Object[] args) throws Throwable { // CompletableFuture requestWithTimeout(Message message, Duration timeout); @SuppressWarnings("unchecked") private CompletableFuture requestAsync(Method method, Object[] args) throws Throwable { + Message message = null; NatsRequest natsRequest = null; + Duration timeout = null; - if ((method.getParameterCount() == 2 || method.getParameterCount() == 3) + if ((method.getParameterCount() == 2) + && method.getParameterTypes()[0] == String.class + && method.getParameterTypes()[1] == byte[].class) { + message = NatsMessageWritableHeaders.create((String) args[0], (byte[]) args[1]); + } else if ((method.getParameterCount() == 3) && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == byte[].class) { - natsRequest = NatsRequest.create(delegate, null, (String) args[0], null, (byte[]) args[1]); - } else if ((method.getParameterCount() == 3 || method.getParameterCount() == 4) + message = NatsMessageWritableHeaders.create((String) args[0], (byte[]) args[1]); + timeout = (Duration) args[2]; + } else if ((method.getParameterCount() == 3) && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == Headers.class && method.getParameterTypes()[2] == byte[].class) { - natsRequest = - NatsRequest.create(delegate, null, (String) args[0], (Headers) args[1], (byte[]) args[2]); - } else if ((method.getParameterCount() == 1 || method.getParameterCount() == 2) + message = + NatsMessageWritableHeaders.create((String) args[0], (Headers) args[1], (byte[]) args[2]); + } else if ((method.getParameterCount() == 4) + && method.getParameterTypes()[0] == String.class + && method.getParameterTypes()[1] == Headers.class + && method.getParameterTypes()[2] == byte[].class) { + message = + NatsMessageWritableHeaders.create((String) args[0], (Headers) args[1], (byte[]) args[2]); + timeout = (Duration) args[3]; + } else if ((method.getParameterCount() == 1) && method.getParameterTypes()[0] == Message.class) { - natsRequest = NatsRequest.create(delegate, (Message) args[0]); + message = NatsMessageWritableHeaders.create((Message) args[0]); + } else if ((method.getParameterCount() == 2) + && method.getParameterTypes()[0] == Message.class) { + message = NatsMessageWritableHeaders.create((Message) args[0]); + timeout = (Duration) args[1]; } Context parentContext = Context.current(); + if (message != null) { + natsRequest = NatsRequest.create(delegate, message); + } + if (natsRequest == null || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { return (CompletableFuture) invokeMethod(method, delegate, args); } @@ -222,11 +258,18 @@ private CompletableFuture requestAsync(Method method, Object[] args) th NatsRequest notNullNatsRequest = natsRequest; Context context = producerInstrumenter.start(parentContext, notNullNatsRequest); - return ((CompletableFuture) invokeMethod(method, delegate, args)) + CompletableFuture future; + if (timeout != null) { + future = delegate.requestWithTimeout(message, timeout); + } else { + future = delegate.request(message); + } + + return future .whenComplete( - (message, exception) -> { - if (message != null) { - NatsRequest response = NatsRequest.create(delegate, message); + (result, exception) -> { + if (result != null) { + NatsRequest response = NatsRequest.create(delegate, result); producerInstrumenter.end(context, notNullNatsRequest, response, exception); } else { producerInstrumenter.end(context, notNullNatsRequest, null, exception); diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java index 70798dcaae75..612a03e00cd8 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java @@ -35,12 +35,11 @@ public OpenTelemetryMessageHandler( @Override public void onMessage(Message message) throws InterruptedException { - Timer timer = Timer.start(); - Context parentContext = Context.current(); NatsRequest natsRequest = NatsRequest.create(message.getConnection(), message); if (consumerReceiveInstrumenter.shouldStart(parentContext, natsRequest)) { + Timer timer = Timer.start(); parentContext = InstrumenterUtil.startAndEnd( consumerReceiveInstrumenter, diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java new file mode 100644 index 000000000000..49835bda53e8 --- /dev/null +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java @@ -0,0 +1,48 @@ +package io.opentelemetry.instrumentation.nats.v2_17.internal; + + +import io.nats.client.Message; +import io.nats.client.impl.Headers; +import io.nats.client.impl.NatsMessage; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class NatsMessageWritableHeaders { + + public static Message create(String subject, byte[] body) { + return NatsMessage.builder().subject(subject).headers(new Headers()).data(body).build(); + } + + public static Message create(String subject, String replyTo, byte[] body) { + return NatsMessage.builder().subject(subject).replyTo(replyTo).headers(new Headers()).data(body).build(); + } + + public static Message create(String subject, Headers headers, byte[] body) { + if(headers == null || headers.isReadOnly()) { + headers = new Headers(headers); + } + return NatsMessage.builder().subject(subject).headers(headers).data(body).build(); + } + + public static Message create(String subject, String replyTo, Headers headers, byte[] body) { + if(headers == null || headers.isReadOnly()) { + headers = new Headers(headers); + } + return NatsMessage.builder().subject(subject).replyTo(replyTo).headers(headers).data(body).build(); + } + + public static Message create(Message message) { + if (message instanceof NatsMessage && + (!message.hasHeaders() || message.getHeaders().isReadOnly()) + ) { + return NatsMessage.builder().subject(message.getSubject()).replyTo(message.getReplyTo()) + .headers(new Headers(message.getHeaders())).data(message.getData()).build(); + } + return message; + } + + private NatsMessageWritableHeaders() {} + +} diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java index cfb202d9f194..bb607a24cdc1 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java @@ -19,18 +19,13 @@ public abstract class NatsRequest { public static NatsRequest create(Connection connection, Message message) { - return create( - message.getConnection() == null ? connection : message.getConnection(), + return new AutoValue_NatsRequest( message.getReplyTo(), + (message.getConnection() == null ? connection : message.getConnection()).getServerInfo() + .getClientId(), message.getSubject(), message.getHeaders(), - message.getData()); - } - - public static NatsRequest create( - Connection connection, String replyTo, String subject, Headers headers, byte[] data) { - return new AutoValue_NatsRequest( - replyTo, connection.getServerInfo().getClientId(), subject, headers, getDataSize(data)); + getDataSize(message.getData())); } @Nullable diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java index 83bfa114a197..4a3289eb6de3 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java @@ -5,7 +5,6 @@ package io.opentelemetry.instrumentation.nats.v2_17; -import io.nats.client.Connection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import org.junit.jupiter.api.BeforeAll; @@ -16,22 +15,14 @@ class NatsInstrumentationDispatcherTest extends AbstractNatsInstrumentationDispa @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - static NatsTelemetry telemetry; - static Connection natsConnection; - - @BeforeAll - static void beforeAll() { - telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); - natsConnection = telemetry.wrap(new TestConnection()); - } - @Override protected InstrumentationExtension testing() { return testing; } - @Override - protected Connection connection() { - return natsConnection; + @BeforeAll + static void beforeAll() { + connection = NatsTelemetry.create(testing.getOpenTelemetry()).wrap(connection); } + } diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java index cba06fcc48b5..859ccde20e46 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java @@ -7,7 +7,6 @@ import static java.util.Collections.singletonList; -import io.nats.client.Connection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import org.junit.jupiter.api.BeforeAll; @@ -18,26 +17,16 @@ class NatsInstrumentationExperimentalTest extends AbstractNatsInstrumentationExp @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - static NatsTelemetry telemetry; - static Connection natsConnection; - - @BeforeAll - static void beforeAll() { - telemetry = - NatsTelemetry.builder(testing.getOpenTelemetry()) - .setMessagingReceiveInstrumentationEnabled(true) - .setCapturedHeaders(singletonList("captured-header")) - .build(); - natsConnection = telemetry.wrap(new TestConnection()); - } - @Override protected InstrumentationExtension testing() { return testing; } - @Override - protected Connection connection() { - return natsConnection; + @BeforeAll + static void beforeAll() { + connection = NatsTelemetry.builder(testing.getOpenTelemetry()) + .setMessagingReceiveInstrumentationEnabled(true) + .setCapturedHeaders(singletonList("captured-header")) + .build().wrap(connection); } } diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java index 95c876549760..c407d57bd291 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java @@ -5,7 +5,6 @@ package io.opentelemetry.instrumentation.nats.v2_17; -import io.nats.client.Connection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import org.junit.jupiter.api.BeforeAll; @@ -16,22 +15,13 @@ class NatsInstrumentationPublishTest extends AbstractNatsInstrumentationPublishT @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - static NatsTelemetry telemetry; - static Connection natsConnection; - - @BeforeAll - static void beforeAll() { - telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); - natsConnection = telemetry.wrap(new TestConnection()); - } - @Override protected InstrumentationExtension testing() { return testing; } - @Override - protected Connection connection() { - return natsConnection; + @BeforeAll + static void beforeAll() { + connection = NatsTelemetry.create(testing.getOpenTelemetry()).wrap(connection); } } diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java index 36fe979f4c3c..28525f6fa7ee 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java @@ -5,7 +5,6 @@ package io.opentelemetry.instrumentation.nats.v2_17; -import io.nats.client.Connection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import org.junit.jupiter.api.BeforeAll; @@ -16,23 +15,14 @@ class NatsInstrumentationRequestTest extends AbstractNatsInstrumentationRequestT @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - static NatsTelemetry telemetry; - static Connection natsConnection; - - @BeforeAll - static void beforeAll() { - telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); - natsConnection = telemetry.wrap(new TestConnection()); - } - @Override protected InstrumentationExtension testing() { return testing; } - @Override - protected Connection connection() { - return natsConnection; + @BeforeAll + static void beforeAll() { + connection = NatsTelemetry.create(testing.getOpenTelemetry()).wrap(connection); } @Override diff --git a/instrumentation/nats/nats-2.17/testing/build.gradle.kts b/instrumentation/nats/nats-2.17/testing/build.gradle.kts index c8f7c07d1eb6..9296a94e62f5 100644 --- a/instrumentation/nats/nats-2.17/testing/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/testing/build.gradle.kts @@ -6,4 +6,6 @@ dependencies { api(project(":testing-common")) compileOnly("io.nats:jnats:2.21.5") + + implementation("org.testcontainers:testcontainers") } diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java index 792b019877ca..5005d7e99983 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java @@ -8,41 +8,32 @@ import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; import static org.assertj.core.api.Assertions.assertThatNoException; -import io.nats.client.Connection; import io.nats.client.Dispatcher; -import io.nats.client.MessageHandler; import io.nats.client.Subscription; import io.nats.client.impl.Headers; import io.nats.client.impl.NatsMessage; import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationDispatcherTest { - - protected abstract InstrumentationExtension testing(); - - protected abstract Connection connection(); +public abstract class AbstractNatsInstrumentationDispatcherTest extends AbstractNatsInstrumentationTest { private int clientId; @BeforeEach void beforeEach() { - clientId = connection().getServerInfo().getClientId(); + clientId = connection.getServerInfo().getClientId(); } @Test void testSubscribe() { - // given - MessageHandler handler = msg -> {}; // global message handler - Dispatcher d1 = connection().createDispatcher(handler).subscribe("sub"); + Dispatcher d1 = connection.createDispatcher(msg -> System.out.println("1 " + msg)).subscribe("sub"); // per-subscription message handler - Dispatcher d2 = connection().createDispatcher(); - Subscription s1 = d2.subscribe("sub", handler); - Subscription s2 = d2.subscribe("sub", "queue", handler); + Dispatcher d2 = connection.createDispatcher(); + Subscription s1 = d2.subscribe("sub", msg -> System.out.println("2 " + msg)); + Subscription s2 = d2.subscribe("sub", "queue", msg -> System.out.println("3 " + msg)); // when testing() @@ -50,13 +41,20 @@ void testSubscribe() { "parent", () -> { NatsMessage.Builder builder = NatsMessage.builder().subject("sub").data("x"); - connection().publish(builder.build()); // no propagation - connection().publish(builder.headers(new Headers()).build()); // propagation + connection.publish(builder.replyTo("a").build()); // no propagation + connection.publish(builder.replyTo("b").headers(new Headers()).build()); // propagation }); - // then 4 traces - // - parent + 2 publish + 3 process (propagation) - // - process (no propagation) (*3) + // then 1 trace + // - parent + // --- 1 publish + // ----- process global (propagation with explicit headers) + // ----- process subject (propagation with explicit headers) + // ----- process subject+queue (propagation with explicit headers) + // --- 1 publish + // ----- process global (propagation with headers override) + // ----- process subject (propagation with headers override) + // ----- process subject+queue (propagation with headers override) testing() .waitAndAssertTraces( trace -> @@ -67,35 +65,36 @@ void testSubscribe() { .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)), span -> - span.hasName("sub publish") - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(0)), + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)), + span -> + span.hasName("sub process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)), span -> span.hasName("sub process") .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(2)), + .hasParent(trace.getSpan(1)), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)), span -> span.hasName("sub process") .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(2)), + .hasParent(trace.getSpan(5)), span -> span.hasName("sub process") .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(2))), - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("sub process").hasKind(SpanKind.CONSUMER).hasNoParent()), - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("sub process").hasKind(SpanKind.CONSUMER).hasNoParent()), - trace -> - trace.hasSpansSatisfyingExactly( + .hasParent(trace.getSpan(5)), span -> span.hasName("sub process") .hasKind(SpanKind.CONSUMER) - .hasNoParent() + .hasParent(trace.getSpan(5)) .hasAttributesSatisfyingExactly( - messagingAttributes("process", "sub", clientId)))); + messagingAttributes("process", "sub", clientId))) + ); // finally, to make sure we're unwrapping properly the // OpenTelemetryDispatcher in the library @@ -104,8 +103,8 @@ void testSubscribe() { () -> { d2.unsubscribe(s1); d2.unsubscribe(s2); - connection().closeDispatcher(d1); - connection().closeDispatcher(d2); + connection.closeDispatcher(d1); + connection.closeDispatcher(d2); }); } } diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java index 0be1d07a8ef5..9439e3a4ad4f 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java @@ -10,7 +10,6 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; -import io.nats.client.Connection; import io.nats.client.Dispatcher; import io.nats.client.Message; import io.nats.client.impl.Headers; @@ -19,29 +18,24 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationExperimentalTest { - - protected abstract InstrumentationExtension testing(); - - protected abstract Connection connection(); +public abstract class AbstractNatsInstrumentationExperimentalTest extends AbstractNatsInstrumentationTest { private int clientId; @BeforeEach void beforeEach() { - clientId = connection().getServerInfo().getClientId(); + clientId = connection.getServerInfo().getClientId(); } @Test void testMessagingReceiveAndCapturedHeaders() { // given - Dispatcher dispatcher = connection().createDispatcher(msg -> {}).subscribe("sub"); + Dispatcher dispatcher = connection.createDispatcher(msg -> {}).subscribe("sub"); // when Headers headers = new Headers(); @@ -53,10 +47,10 @@ void testMessagingReceiveAndCapturedHeaders() { () -> { Message message = NatsMessage.builder().subject("sub").headers(headers).data("x").build(); - connection().publish(message); + connection.publish(message); return Span.fromContext(Context.current()).getSpanContext().getTraceId(); }); - connection().closeDispatcher(dispatcher); + connection.closeDispatcher(dispatcher); // then AttributeAssertion[] headerAssert = diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java index b1d8c9a46238..f14a3b9a7505 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java @@ -5,35 +5,28 @@ package io.opentelemetry.instrumentation.nats.v2_17; -import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.assertNoHeaders; import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.assertTraceparentHeader; import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; -import io.nats.client.Connection; import io.nats.client.Subscription; import io.nats.client.impl.Headers; import io.nats.client.impl.NatsMessage; import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import java.time.Duration; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationPublishTest { - - protected abstract InstrumentationExtension testing(); - - protected abstract Connection connection(); +public abstract class AbstractNatsInstrumentationPublishTest extends AbstractNatsInstrumentationTest { private int clientId; private Subscription subscription; @BeforeEach void beforeEach() { - clientId = connection().getServerInfo().getClientId(); - subscription = connection().subscribe("sub"); + clientId = connection.getServerInfo().getClientId(); + subscription = connection.subscribe("sub"); } @AfterEach @@ -44,18 +37,18 @@ void afterEach() throws InterruptedException { @Test void testPublishBody() throws InterruptedException { // when - testing().runWithSpan("parent", () -> connection().publish("sub", new byte[] {0})); + testing().runWithSpan("parent", () -> connection.publish("sub", new byte[] {0})); // then assertPublishSpan(); - assertNoHeaders(subscription); + assertTraceparentHeader(subscription); } @Test void testPublishHeadersBody() throws InterruptedException { // when testing() - .runWithSpan("parent", () -> connection().publish("sub", new Headers(), new byte[] {0})); + .runWithSpan("parent", () -> connection.publish("sub", new Headers(), new byte[] {0})); // then assertPublishSpan(); @@ -65,11 +58,11 @@ void testPublishHeadersBody() throws InterruptedException { @Test void testPublishReplyToBody() throws InterruptedException { // when - testing().runWithSpan("parent", () -> connection().publish("sub", "rt", new byte[] {0})); + testing().runWithSpan("parent", () -> connection.publish("sub", "rt", new byte[] {0})); // then assertPublishSpan(); - assertNoHeaders(subscription); + assertTraceparentHeader(subscription); } @Test @@ -77,7 +70,7 @@ void testPublishReplyToHeadersBody() throws InterruptedException { // when testing() .runWithSpan( - "parent", () -> connection().publish("sub", "rt", new Headers(), new byte[] {0})); + "parent", () -> connection.publish("sub", "rt", new Headers(), new byte[] {0})); // then assertPublishSpan(); @@ -89,11 +82,11 @@ void testPublishMessage() throws InterruptedException { NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when - testing().runWithSpan("parent", () -> connection().publish(message)); + testing().runWithSpan("parent", () -> connection.publish(message)); // then assertPublishSpan(); - assertNoHeaders(subscription); + assertTraceparentHeader(subscription); } @Test @@ -102,7 +95,7 @@ void testPublishMessageWithHeaders() throws InterruptedException { NatsMessage.builder().subject("sub").data("x").headers(new Headers()).build(); // when - testing().runWithSpan("parent", () -> connection().publish(message)); + testing().runWithSpan("parent", () -> connection.publish(message)); // then assertPublishSpan(); diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java index 6bc3202ca17f..82d489cc7a14 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java @@ -5,25 +5,20 @@ package io.opentelemetry.instrumentation.nats.v2_17; -import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.assertNoHeaders; import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.assertTraceparentHeader; import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; import static java.util.Arrays.asList; -import io.nats.client.Connection; import io.nats.client.Dispatcher; import io.nats.client.Subscription; import io.nats.client.impl.Headers; import io.nats.client.impl.NatsMessage; import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; -import io.opentelemetry.sdk.testing.assertj.TraceAssert; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CancellationException; -import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import org.assertj.core.api.Condition; import org.junit.jupiter.api.AfterEach; @@ -31,11 +26,7 @@ import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationRequestTest { - - protected abstract InstrumentationExtension testing(); - - protected abstract Connection connection(); +public abstract class AbstractNatsInstrumentationRequestTest extends AbstractNatsInstrumentationTest { protected abstract boolean isInboxMonitored(); @@ -44,8 +35,8 @@ public abstract class AbstractNatsInstrumentationRequestTest { @BeforeEach void beforeEach() { - clientId = connection().getServerInfo().getClientId(); - subscription = connection().subscribe("sub"); + clientId = connection.getServerInfo().getClientId(); + subscription = connection.subscribe("sub"); } @AfterEach @@ -58,38 +49,38 @@ void testRequestTimeout() throws InterruptedException { // when testing() .runWithSpan( - "parent", () -> connection().request("sub", new byte[] {0}, Duration.ofSeconds(1))); + "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); // then - assertTimeoutPublishSpan(); - assertNoHeaders(subscription); + // assertTimeoutPublishSpan(); + assertTraceparentHeader(subscription); } @Test void testRequestBody() throws InterruptedException { // given Dispatcher dispatcher = - connection() - .createDispatcher(m -> connection().publish(m.getReplyTo(), m.getData())) + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) .subscribe("sub"); // when testing() .runWithSpan( - "parent", () -> connection().request("sub", new byte[] {0}, Duration.ofSeconds(1))); - connection().closeDispatcher(dispatcher); + "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); + connection.closeDispatcher(dispatcher); // then - assertPublishReceiveSpans(); - assertNoHeaders(subscription); + assertPublishReceiveSpansSameTrace(); + assertTraceparentHeader(subscription); } @Test void testRequestHeadersBody() throws InterruptedException { // given Dispatcher dispatcher = - connection() - .createDispatcher(m -> connection().publish(m.getReplyTo(), new Headers(), m.getData())) + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), new Headers(), m.getData())) .subscribe("sub"); // when @@ -97,8 +88,8 @@ void testRequestHeadersBody() throws InterruptedException { .runWithSpan( "parent", () -> - connection().request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); - connection().closeDispatcher(dispatcher); + connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + connection.closeDispatcher(dispatcher); // then assertPublishReceiveSpansSameTrace(); @@ -109,33 +100,33 @@ void testRequestHeadersBody() throws InterruptedException { void testRequestMessage() throws InterruptedException { // given Dispatcher dispatcher = - connection() - .createDispatcher(m -> connection().publish(m.getReplyTo(), m.getData())) + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) .subscribe("sub"); NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when - testing().runWithSpan("parent", () -> connection().request(message, Duration.ofSeconds(1))); - connection().closeDispatcher(dispatcher); + testing().runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); + connection.closeDispatcher(dispatcher); // then - assertPublishReceiveSpans(); - assertNoHeaders(subscription); + assertPublishReceiveSpansSameTrace(); + assertTraceparentHeader(subscription); } @Test void testRequestMessageHeaders() throws InterruptedException { // given Dispatcher dispatcher = - connection() - .createDispatcher(m -> connection().publish(m.getReplyTo(), new Headers(), m.getData())) + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), new Headers(), m.getData())) .subscribe("sub"); NatsMessage message = NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); // when - testing().runWithSpan("parent", () -> connection().request(message, Duration.ofSeconds(1))); - connection().closeDispatcher(dispatcher); + testing().runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); + connection.closeDispatcher(dispatcher); // then assertPublishReceiveSpansSameTrace(); @@ -146,32 +137,32 @@ void testRequestMessageHeaders() throws InterruptedException { void testRequestFutureBody() throws InterruptedException { // given Dispatcher dispatcher = - connection() - .createDispatcher(m -> connection().publish(m.getReplyTo(), m.getData())) + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) .subscribe("sub"); // when testing() - .runWithSpan("parent", () -> connection().request("sub", new byte[] {0})) - .whenComplete((m, e) -> connection().closeDispatcher(dispatcher)); + .runWithSpan("parent", () -> connection.request("sub", new byte[] {0})) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then - assertPublishReceiveSpans(); - assertNoHeaders(subscription); + assertPublishReceiveSpansSameTrace(); + assertTraceparentHeader(subscription); } @Test void testRequestFutureHeadersBody() throws InterruptedException { // given Dispatcher dispatcher = - connection() - .createDispatcher(m -> connection().publish(m.getReplyTo(), new Headers(), m.getData())) + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), new Headers(), m.getData())) .subscribe("sub"); // when testing() - .runWithSpan("parent", () -> connection().request("sub", new Headers(), new byte[] {0})) - .whenComplete((m, e) -> connection().closeDispatcher(dispatcher)); + .runWithSpan("parent", () -> connection.request("sub", new Headers(), new byte[] {0})) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then assertPublishReceiveSpansSameTrace(); @@ -182,35 +173,35 @@ void testRequestFutureHeadersBody() throws InterruptedException { void testRequestFutureMessage() throws InterruptedException { // given Dispatcher dispatcher = - connection() - .createDispatcher(m -> connection().publish(m.getReplyTo(), m.getData())) + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), m.getData())) .subscribe("sub"); NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when testing() - .runWithSpan("parent", () -> connection().request(message)) - .whenComplete((m, e) -> connection().closeDispatcher(dispatcher)); + .runWithSpan("parent", () -> connection.request(message)) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then - assertPublishReceiveSpans(); - assertNoHeaders(subscription); + assertPublishReceiveSpansSameTrace(); + assertTraceparentHeader(subscription); } @Test void testRequestFutureMessageHeaders() throws InterruptedException { // given Dispatcher dispatcher = - connection() - .createDispatcher(m -> connection().publish(m.getReplyTo(), new Headers(), m.getData())) + connection + .createDispatcher(m -> connection.publish(m.getReplyTo(), new Headers(), m.getData())) .subscribe("sub"); NatsMessage message = NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); // when testing() - .runWithSpan("parent", () -> connection().request(message)) - .whenComplete((m, e) -> connection().closeDispatcher(dispatcher)); + .runWithSpan("parent", () -> connection.request(message)) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then assertPublishReceiveSpansSameTrace(); @@ -223,11 +214,11 @@ void testRequestTimeoutFutureBody() throws InterruptedException { testing() .runWithSpan( "parent", - () -> connection().requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); + () -> connection.requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); - assertNoHeaders(subscription); + assertTraceparentHeader(subscription); } @Test @@ -237,7 +228,7 @@ void testRequestTimeoutFutureHeadersBody() throws InterruptedException { .runWithSpan( "parent", () -> - connection() + connection .requestWithTimeout( "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); @@ -254,11 +245,11 @@ void testRequestTimeoutFutureMessage() throws InterruptedException { // when testing() .runWithSpan( - "parent", () -> connection().requestWithTimeout(message, Duration.ofSeconds(1))); + "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); - assertNoHeaders(subscription); + assertTraceparentHeader(subscription); } @Test @@ -270,62 +261,13 @@ void testRequestTimeoutFutureMessageHeaders() throws InterruptedException { // when testing() .runWithSpan( - "parent", () -> connection().requestWithTimeout(message, Duration.ofSeconds(1))); + "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); assertTraceparentHeader(subscription); } - private void assertPublishReceiveSpans() { - List> asserts = - new ArrayList<>( - asList( - // publisher: parent + publish - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName("sub publish") - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - messagingAttributes("publish", "sub", clientId))), - // subscriber: process + publish(response) (another trace because no propagation) - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("sub process").hasKind(SpanKind.CONSUMER).hasNoParent(), - span -> - span.has( - new Condition<>( - data -> - data.getName().startsWith("_INBOX.") - && data.getName().endsWith(" publish"), - "Name condition")) - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(0))), - // publisher: process (another trace because no propagation) - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.has( - new Condition<>( - data -> - data.getName().startsWith("_INBOX.") - && data.getName().endsWith(" process"), - "Name condition")) - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasAttributesSatisfyingExactly( - messagingAttributes("process", "_INBOX.", clientId))))); - - if (!isInboxMonitored()) { - asserts.remove(asserts.size() - 1); - } - testing().waitAndAssertTraces(asserts); - } - private void assertPublishReceiveSpansSameTrace() { testing() .waitAndAssertTraces( @@ -376,10 +318,6 @@ private void assertPublishReceiveSpansSameTrace() { }); } - private void assertTimeoutPublishSpan() { - assertExceptionPublishSpan(new TimeoutException("Timed out waiting for message")); - } - private void assertCancellationPublishSpan() { assertExceptionPublishSpan( new CancellationException( diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationTest.java new file mode 100644 index 000000000000..4fb2a192a33f --- /dev/null +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationTest.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.nats.v2_17; + +import io.nats.client.Connection; +import io.nats.client.Nats; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +abstract class AbstractNatsInstrumentationTest { + + static DockerImageName natsImage; + static GenericContainer natsContainer; + static Connection connection; + + protected abstract InstrumentationExtension testing(); + + @BeforeAll + static void beforeAll() throws IOException, InterruptedException { + natsImage = DockerImageName.parse("nats:2.11.2-alpine3.21"); + + natsContainer = new GenericContainer<>(natsImage).withExposedPorts(4222); + natsContainer.start(); + + String host = natsContainer.getHost(); + Integer port = natsContainer.getMappedPort(4222); + connection = Nats.connect("nats://" + host + ":" + port); + } + + @AfterAll + static void afterAll() throws InterruptedException, TimeoutException { + connection.drain(Duration.ZERO); + natsContainer.close(); + } +} diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java index dbb85c4b9b5f..fed860e6c70b 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java @@ -47,11 +47,6 @@ public static AttributeAssertion[] messagingAttributes( }; } - public static void assertNoHeaders(Subscription subscription) throws InterruptedException { - Message published = subscription.nextMessage(Duration.ofSeconds(1)); - assertThat(published.getHeaders()).isNull(); - } - public static void assertTraceparentHeader(Subscription subscription) throws InterruptedException { Message published = subscription.nextMessage(Duration.ofSeconds(1)); diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestConnection.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestConnection.java deleted file mode 100644 index d053aa4d9672..000000000000 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestConnection.java +++ /dev/null @@ -1,430 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_17; - -import io.nats.client.Connection; -import io.nats.client.ConnectionListener; -import io.nats.client.ConsumerContext; -import io.nats.client.Dispatcher; -import io.nats.client.ForceReconnectOptions; -import io.nats.client.JetStream; -import io.nats.client.JetStreamApiException; -import io.nats.client.JetStreamManagement; -import io.nats.client.JetStreamOptions; -import io.nats.client.KeyValue; -import io.nats.client.KeyValueManagement; -import io.nats.client.KeyValueOptions; -import io.nats.client.Message; -import io.nats.client.MessageHandler; -import io.nats.client.NUID; -import io.nats.client.ObjectStore; -import io.nats.client.ObjectStoreManagement; -import io.nats.client.ObjectStoreOptions; -import io.nats.client.Options; -import io.nats.client.Statistics; -import io.nats.client.StreamContext; -import io.nats.client.Subscription; -import io.nats.client.api.ServerInfo; -import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsMessage; -import io.nats.client.support.NatsRequestCompletableFuture; -import java.io.IOException; -import java.net.InetAddress; -import java.time.Duration; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public class TestConnection implements Connection { - - public static final String INBOX_PREFIX = "_INBOX."; - - private final NUID nuid = new NUID(); - private final ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1); - - private final List subscriptions = - Collections.synchronizedList(new LinkedList<>()); - - private final List dispatchers = Collections.synchronizedList(new LinkedList<>()); - - private final Map pending = new ConcurrentHashMap<>(); - - public TestConnection() { - TestDispatcher inboxDispatcher = - new TestDispatcher( - msg -> { - NatsRequestCompletableFuture res = pending.remove(msg.getSubject()); - if (res != null) { - res.complete(msg); - } - }); - inboxDispatcher.subscribe(INBOX_PREFIX); - dispatchers.add(inboxDispatcher); - } - - @Override - public void publish(String subject, byte[] body) { - publish(NatsMessage.builder().subject(subject).data(body).build()); - } - - @Override - public void publish(String subject, Headers headers, byte[] body) { - publish(NatsMessage.builder().subject(subject).headers(headers).data(body).build()); - } - - @Override - public void publish(String subject, String replyTo, byte[] body) { - publish(NatsMessage.builder().subject(subject).replyTo(replyTo).data(body).build()); - } - - @Override - public void publish(String subject, String replyTo, Headers headers, byte[] body) { - publish( - NatsMessage.builder() - .subject(subject) - .replyTo(replyTo) - .headers(headers) - .data(body) - .build()); - } - - @Override - public void publish(Message message) { - internalPublish(message); - } - - private void internalPublish(Message message) { - // simulate async boundary, avoids passing Context between publish/dispatcher in testing - new Thread( - () -> { - TestMessage msg = new TestMessage(this, null, message); - subscriptions.forEach(sub -> sub.deliver(msg.setSubscription(sub), null)); - dispatchers.forEach(dispatcher -> dispatcher.deliver(msg)); - }) - .start(); - } - - @Override - public Message request(String subject, byte[] body, Duration timeout) - throws InterruptedException { - return request(NatsMessage.builder().subject(subject).data(body).build(), timeout); - } - - @Override - public Message request(String subject, Headers headers, byte[] body, Duration timeout) - throws InterruptedException { - return request( - NatsMessage.builder().subject(subject).headers(headers).data(body).build(), timeout); - } - - @Override - public Message request(Message message, Duration timeout) throws InterruptedException { - try { - return request(message).get(timeout.toMillis(), TimeUnit.MILLISECONDS); - } catch (ExecutionException | TimeoutException | CancellationException e) { - return null; - } - } - - @Override - public CompletableFuture request(String subject, byte[] body) { - return request(NatsMessage.builder().subject(subject).data(body).build()); - } - - @Override - public CompletableFuture request(String subject, Headers headers, byte[] body) { - return request(NatsMessage.builder().subject(subject).headers(headers).data(body).build()); - } - - @Override - public CompletableFuture request(Message message) { - return requestWithTimeout(message, null); - } - - @Override - public CompletableFuture requestWithTimeout( - String subject, byte[] body, Duration timeout) { - return requestWithTimeout(NatsMessage.builder().subject(subject).data(body).build(), timeout); - } - - @Override - public CompletableFuture requestWithTimeout( - String subject, Headers headers, byte[] body, Duration timeout) { - return requestWithTimeout( - NatsMessage.builder().subject(subject).headers(headers).data(body).build(), timeout); - } - - @Override - public CompletableFuture requestWithTimeout(Message message, Duration timeout) { - NatsRequestCompletableFuture future = - new NatsRequestCompletableFuture( - NatsRequestCompletableFuture.CancelAction.CANCEL, timeout, false); - - String inbox = createInbox(); - pending.put(inbox, future); - - internalPublish( - NatsMessage.builder() - .subject(message.getSubject()) - .replyTo(inbox) - .headers(message.getHeaders()) - .data(message.getData()) - .build()); - - if (timeout != null) { - scheduler.schedule( - () -> { - if (!future.isDone()) { - pending.remove(inbox).cancelTimedOut(); - } - }, - timeout.toMillis(), - TimeUnit.MILLISECONDS); - } - - return future; - } - - @Override - public Subscription subscribe(String subject) { - TestSubscription subscription = new TestSubscription(subject); - subscriptions.add(subscription); - return subscription; - } - - @Override - public Subscription subscribe(String subject, String queueName) { - TestSubscription subscription = new TestSubscription(subject, queueName); - subscriptions.add(subscription); - return subscription; - } - - @Override - public Dispatcher createDispatcher(MessageHandler handler) { - TestDispatcher dispatcher = new TestDispatcher(handler); - dispatchers.add(dispatcher); - return dispatcher; - } - - @Override - public Dispatcher createDispatcher() { - TestDispatcher dispatcher = new TestDispatcher(); - dispatchers.add(dispatcher); - return dispatcher; - } - - @Override - public void closeDispatcher(Dispatcher dispatcher) { - if (dispatcher instanceof TestDispatcher && dispatchers.contains((TestDispatcher) dispatcher)) { - dispatchers.remove(dispatcher); - return; - } - - throw new IllegalArgumentException("Unexpected dispatcher: " + dispatcher); - } - - @Override - public void addConnectionListener(ConnectionListener connectionListener) {} - - @Override - public void removeConnectionListener(ConnectionListener connectionListener) {} - - @Override - public void flush(Duration timeout) throws TimeoutException, InterruptedException {} - - @Override - public CompletableFuture drain(Duration timeout) - throws TimeoutException, InterruptedException { - return null; - } - - @Override - public void close() throws InterruptedException {} - - @Override - public Status getStatus() { - return null; - } - - @Override - public long getMaxPayload() { - return 0; - } - - @Override - public Collection getServers() { - return Collections.emptyList(); - } - - @Override - public Statistics getStatistics() { - return null; - } - - @Override - public Options getOptions() { - return null; - } - - @Override - public ServerInfo getServerInfo() { - return new ServerInfo( - "{" - + "\"server_id\": \"SID\", " - + "\"server_name\": \"opentelemetry-nats\", " - + "\"version\": \"2.10.24\", " - + "\"go\": \"go1.23.4\", " - + "\"host\": \"0.0.0.0\", " - + "\"headers_supported\": true, " - + "\"auth_required\": true, " - + "\"nonce\": null, " - + "\"tls_required\": false, " - + "\"tls_available\": false, " - + "\"ldm\": false, " - + "\"jetstream\": false, " - + "\"port\": 4222, " - + "\"proto\": 1, " - + "\"max_payload\": 1048576, " - + "\"client_id\": 1, " - + "\"client_ip\": \"192.168.1.1\", " - + "\"cluster\": \"opentelemetry-nats\", " - + "\"connect_urls\": []" - + "}"); - } - - @Override - public String getConnectedUrl() { - return ""; - } - - @Override - public InetAddress getClientInetAddress() { - return null; - } - - @Override - public String getLastError() { - return ""; - } - - @Override - public void clearLastError() {} - - @Override - public String createInbox() { - return INBOX_PREFIX + nuid.next(); - } - - @Override - public void flushBuffer() throws IOException {} - - @Override - public void forceReconnect() throws IOException, InterruptedException {} - - @Override - public void forceReconnect(ForceReconnectOptions options) - throws IOException, InterruptedException {} - - @Override - public Duration RTT() throws IOException { - return null; - } - - @Override - public StreamContext getStreamContext(String streamName) - throws IOException, JetStreamApiException { - return null; - } - - @Override - public StreamContext getStreamContext(String streamName, JetStreamOptions options) - throws IOException, JetStreamApiException { - return null; - } - - @Override - public ConsumerContext getConsumerContext(String streamName, String consumerName) - throws IOException, JetStreamApiException { - return null; - } - - @Override - public ConsumerContext getConsumerContext( - String streamName, String consumerName, JetStreamOptions options) - throws IOException, JetStreamApiException { - return null; - } - - @Override - public JetStream jetStream() throws IOException { - return null; - } - - @Override - public JetStream jetStream(JetStreamOptions options) throws IOException { - return null; - } - - @Override - public JetStreamManagement jetStreamManagement() throws IOException { - return null; - } - - @Override - public JetStreamManagement jetStreamManagement(JetStreamOptions options) throws IOException { - return null; - } - - @Override - public KeyValue keyValue(String bucketName) throws IOException { - return null; - } - - @Override - public KeyValue keyValue(String bucketName, KeyValueOptions options) throws IOException { - return null; - } - - @Override - public KeyValueManagement keyValueManagement() throws IOException { - return null; - } - - @Override - public KeyValueManagement keyValueManagement(KeyValueOptions options) throws IOException { - return null; - } - - @Override - public ObjectStore objectStore(String bucketName) throws IOException { - return null; - } - - @Override - public ObjectStore objectStore(String bucketName, ObjectStoreOptions options) throws IOException { - return null; - } - - @Override - public ObjectStoreManagement objectStoreManagement() throws IOException { - return null; - } - - @Override - public ObjectStoreManagement objectStoreManagement(ObjectStoreOptions options) - throws IOException { - return null; - } -} diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestDispatcher.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestDispatcher.java deleted file mode 100644 index a492108877ee..000000000000 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestDispatcher.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_17; - -import static io.opentelemetry.instrumentation.nats.v2_17.TestConnection.INBOX_PREFIX; - -import io.nats.client.Dispatcher; -import io.nats.client.MessageHandler; -import io.nats.client.Subscription; -import java.time.Duration; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -public class TestDispatcher implements Dispatcher { - - private final MessageHandler defaultHandler; - private final List subjects = Collections.synchronizedList(new LinkedList<>()); - private final Map subscriptions = - Collections.synchronizedMap(new HashMap<>()); - - public TestDispatcher() { - defaultHandler = msg -> {}; - } - - public TestDispatcher(MessageHandler defaultHandler) { - this.defaultHandler = defaultHandler; - } - - @SuppressWarnings("EmptyCatch") - public void deliver(TestMessage message) { - subjects.forEach( - subject -> { - if (message.getSubject().equalsIgnoreCase(subject) - || (subject.equals(INBOX_PREFIX) && message.getSubject().startsWith(INBOX_PREFIX))) { - try { - defaultHandler.onMessage(message); - } catch (InterruptedException ignored) { - } - } - }); - subscriptions.forEach((sub, handler) -> sub.deliver(message.setSubscription(sub), handler)); - } - - @Override - public void start(String id) {} - - @Override - public Dispatcher subscribe(String subject) { - subjects.add(subject); - return this; - } - - @Override - public Dispatcher subscribe(String subject, String queue) { - subjects.add(subject); - return this; - } - - @Override - public Subscription subscribe(String subject, MessageHandler handler) { - TestSubscription subscription = new TestSubscription(subject, null, this); - subscriptions.put(subscription, handler); - return subscription; - } - - @Override - public Subscription subscribe(String subject, String queue, MessageHandler handler) { - TestSubscription subscription = new TestSubscription(subject, queue, this); - subscriptions.put(subscription, handler); - return subscription; - } - - @Override - public Dispatcher unsubscribe(String subject) { - if (subjects.contains(subject)) { - subjects.remove(subject); - return this; - } - throw new IllegalStateException("Cannot unsubscribe to " + subject); - } - - @Override - public Dispatcher unsubscribe(Subscription subscription) { - if (subscription instanceof TestSubscription - && subscriptions.containsKey((TestSubscription) subscription)) { - subscriptions.remove(subscription); - return this; - } - throw new IllegalArgumentException("Unexpected subscription: " + subscription); - } - - @Override - public Dispatcher unsubscribe(String subject, int after) { - if (subjects.contains(subject)) { - subjects.remove(subject); - return this; - } - throw new IllegalStateException("Cannot unsubscribe to " + subject); - } - - @Override - public Dispatcher unsubscribe(Subscription subscription, int after) { - if (subscription instanceof TestSubscription - && subscriptions.containsKey((TestSubscription) subscription)) { - subscriptions.remove(subscription); - return this; - } - throw new IllegalArgumentException("Unexpected subscription: " + subscription); - } - - @Override - public void setPendingLimits(long maxMessages, long maxBytes) {} - - @Override - public long getPendingMessageLimit() { - return 0; - } - - @Override - public long getPendingByteLimit() { - return 0; - } - - @Override - public long getPendingMessageCount() { - return 0; - } - - @Override - public long getPendingByteCount() { - return 0; - } - - @Override - public long getDeliveredCount() { - return 0; - } - - @Override - public long getDroppedCount() { - return 0; - } - - @Override - public void clearDroppedCount() {} - - @Override - public boolean isActive() { - return false; - } - - @Override - public CompletableFuture drain(Duration timeout) throws InterruptedException { - return null; - } -} diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestMessage.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestMessage.java deleted file mode 100644 index 4d742c06f621..000000000000 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestMessage.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_17; - -import io.nats.client.Connection; -import io.nats.client.Message; -import io.nats.client.Subscription; -import io.nats.client.impl.AckType; -import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsJetStreamMetaData; -import io.nats.client.support.Status; -import java.time.Duration; -import java.util.concurrent.TimeoutException; - -public class TestMessage implements Message { - - private final Connection connection; - private Subscription subscription; - private final Message message; - - public TestMessage(Connection connection, Subscription subscription, Message message) { - this.connection = connection; - this.subscription = subscription; - this.message = message; - } - - @Override - public String getSubject() { - return message.getSubject(); - } - - @Override - public String getReplyTo() { - return message.getReplyTo(); - } - - @Override - public boolean hasHeaders() { - return message.hasHeaders(); - } - - @Override - public Headers getHeaders() { - return message.getHeaders(); - } - - @Override - public boolean isStatusMessage() { - return message.isStatusMessage(); - } - - @Override - public Status getStatus() { - return message.getStatus(); - } - - @Override - public byte[] getData() { - return message.getData(); - } - - @Override - public boolean isUtf8mode() { - return message.isUtf8mode(); - } - - @Override - public Subscription getSubscription() { - return subscription; - } - - @Override - public String getSID() { - return subscription.toString(); - } - - @Override - public Connection getConnection() { - return connection; - } - - @Override - public NatsJetStreamMetaData metaData() { - return message.metaData(); - } - - @Override - public AckType lastAck() { - return message.lastAck(); - } - - @Override - public void ack() { - message.ack(); - } - - @Override - public void ackSync(Duration timeout) throws TimeoutException, InterruptedException { - message.ackSync(timeout); - } - - @Override - public void nak() { - message.nak(); - } - - @Override - public void nakWithDelay(Duration nakDelay) { - message.nakWithDelay(nakDelay); - } - - @SuppressWarnings("PreferJavaTimeOverload") - @Override - public void nakWithDelay(long nakDelayMillis) { - message.nakWithDelay(nakDelayMillis); - } - - @Override - public void term() { - message.term(); - } - - @Override - public void inProgress() { - message.inProgress(); - } - - @Override - public boolean isJetStream() { - return message.isJetStream(); - } - - public TestMessage setSubscription(Subscription subscription) { - this.subscription = subscription; - return this; - } -} diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestSubscription.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestSubscription.java deleted file mode 100644 index 2b4172c9b956..000000000000 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/TestSubscription.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_17; - -import io.nats.client.Dispatcher; -import io.nats.client.Message; -import io.nats.client.MessageHandler; -import io.nats.client.Subscription; -import java.time.Duration; -import java.util.Queue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; - -public class TestSubscription implements Subscription { - private final String subject; - private final String queueName; - private final Dispatcher dispatcher; - - public final Queue messages = new ConcurrentLinkedQueue<>(); - - public TestSubscription(String subject) { - this.subject = subject; - this.queueName = null; - this.dispatcher = null; - } - - public TestSubscription(String subject, String queueName) { - this.subject = subject; - this.queueName = queueName; - this.dispatcher = null; - } - - public TestSubscription(String subject, String queueName, Dispatcher dispatcher) { - this.subject = subject; - this.queueName = queueName; - this.dispatcher = dispatcher; - } - - @SuppressWarnings({"EmptyCatchBlock", "EmptyCatch"}) - public void deliver(TestMessage message, MessageHandler handler) { - if (message.getSubject().equalsIgnoreCase(getSubject())) { - messages.add(message); - if (handler != null) { - try { - handler.onMessage(message); - } catch (InterruptedException e) { - } - } - } - } - - @Override - public String getSubject() { - return subject; - } - - @Override - public String getQueueName() { - return queueName; - } - - @Override - public Dispatcher getDispatcher() { - return dispatcher; - } - - @SuppressWarnings("ThrowsUncheckedException") - @Override - public Message nextMessage(Duration timeout) throws InterruptedException, IllegalStateException { - if (dispatcher != null) { - throw new IllegalStateException( - "Subscriptions that belong to a dispatcher cannot respond to nextMessage directly."); - } - - return messages.poll(); - } - - @SuppressWarnings("ThrowsUncheckedException") - @Override - public Message nextMessage(long timeoutMillis) - throws InterruptedException, IllegalStateException { - if (dispatcher != null) { - throw new IllegalStateException( - "Subscriptions that belong to a dispatcher cannot respond to nextMessage directly."); - } - - return messages.poll(); - } - - @Override - public void unsubscribe() { - if (dispatcher != null) { - dispatcher.unsubscribe(this); - return; - } - throw new IllegalStateException( - "Subscriptions that belong to a dispatcher cannot be unsubscribed."); - } - - @Override - public Subscription unsubscribe(int after) { - if (dispatcher != null) { - dispatcher.unsubscribe(this, after); - return this; - } - throw new IllegalStateException( - "Subscriptions that belong to a dispatcher cannot be unsubscribed."); - } - - @Override - public void setPendingLimits(long maxMessages, long maxBytes) {} - - @Override - public long getPendingMessageLimit() { - return 0; - } - - @Override - public long getPendingByteLimit() { - return 0; - } - - @Override - public long getPendingMessageCount() { - return 0; - } - - @Override - public long getPendingByteCount() { - return 0; - } - - @Override - public long getDeliveredCount() { - return 0; - } - - @Override - public long getDroppedCount() { - return 0; - } - - @Override - public void clearDroppedCount() {} - - @Override - public boolean isActive() { - return false; - } - - @Override - public CompletableFuture drain(Duration timeout) throws InterruptedException { - return null; - } -} From bdad7d3d92a20bbc630d39a3a10ef481c39a98bd Mon Sep 17 00:00:00 2001 From: Alix Date: Fri, 29 Aug 2025 14:11:50 +0200 Subject: [PATCH 22/34] remove receive spans --- .../nats/nats-2.17/javaagent/README.md | 1 - .../ConnectionPublishInstrumentation.java | 6 +- .../ConnectionRequestInstrumentation.java | 24 ++++---- .../v2_17/MessageHandlerInstrumentation.java | 16 ----- .../nats/v2_17/NatsSingletons.java | 5 -- .../NatsInstrumentationDispatcherTest.java | 1 - .../NatsInstrumentationExperimentalTest.java | 1 - .../v2_17/NatsInstrumentationPublishTest.java | 1 - .../v2_17/NatsInstrumentationRequestTest.java | 1 - .../nats/nats-2.17/library/build.gradle.kts | 1 - .../impl/OpenTelemetryDispatcherFactory.java | 9 +-- .../nats/v2_17/NatsTelemetry.java | 8 +-- .../nats/v2_17/NatsTelemetryBuilder.java | 2 - .../nats/v2_17/OpenTelemetryConnection.java | 59 ++++++++----------- .../nats/v2_17/OpenTelemetryDispatcher.java | 19 ++---- .../v2_17/OpenTelemetryMessageHandler.java | 21 +------ .../internal/NatsInstrumenterFactory.java | 26 ++------ .../internal/NatsMessageWritableHeaders.java | 38 ++++++++---- .../nats/v2_17/internal/NatsRequest.java | 3 +- .../NatsRequestMessagingAttributesGetter.java | 5 +- .../NatsInstrumentationDispatcherTest.java | 1 - .../NatsInstrumentationExperimentalTest.java | 10 ++-- ...ractNatsInstrumentationDispatcherTest.java | 12 ++-- ...ctNatsInstrumentationExperimentalTest.java | 11 +--- ...bstractNatsInstrumentationPublishTest.java | 6 +- ...bstractNatsInstrumentationRequestTest.java | 17 +++--- 26 files changed, 110 insertions(+), 194 deletions(-) diff --git a/instrumentation/nats/nats-2.17/javaagent/README.md b/instrumentation/nats/nats-2.17/javaagent/README.md index cbe7240f3438..a2fac28fb29a 100644 --- a/instrumentation/nats/nats-2.17/javaagent/README.md +++ b/instrumentation/nats/nats-2.17/javaagent/README.md @@ -1,4 +1,3 @@ # Auto-instrumentation for NATS version 2.17 Provides OpenTelemetry auto-instrumentation for [NATS 2.17](https://github.com/nats-io/nats.java). - diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java index b10f5dd3146c..37ee94f94b00 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java @@ -81,8 +81,8 @@ public static boolean onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, @Advice.Argument(1) byte[] body) { - connection.publish( NatsMessageWritableHeaders.create(subject, body)); - return true; + connection.publish(NatsMessageWritableHeaders.create(subject, body)); + return true; } } @@ -94,7 +94,7 @@ public static boolean onEnter( @Advice.Argument(0) String subject, @Advice.Argument(value = 1, readOnly = false) Headers headers, @Advice.Argument(2) byte[] body) { - connection.publish( NatsMessageWritableHeaders.create(subject, headers, body)); + connection.publish(NatsMessageWritableHeaders.create(subject, headers, body)); return true; } } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java index 0e9550347026..b2b6b193245b 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java @@ -127,9 +127,9 @@ public static Message onEnter( @Advice.Argument(0) String subject, @Advice.Argument(1) byte[] body, @Advice.Argument(2) Duration timeout, - @Advice.Local("message") Message message - ) throws InterruptedException { - message = connection.request(NatsMessageWritableHeaders.create(subject,body), timeout); + @Advice.Local("message") Message message) + throws InterruptedException { + message = connection.request(NatsMessageWritableHeaders.create(subject, body), timeout); return message; } @@ -152,8 +152,10 @@ public static Message onEnter( @Advice.Argument(value = 1, readOnly = false) Headers headers, @Advice.Argument(2) byte[] body, @Advice.Argument(3) Duration timeout, - @Advice.Local("message") Message message) throws InterruptedException { - message = connection.request(NatsMessageWritableHeaders.create(subject, headers, body), timeout); + @Advice.Local("message") Message message) + throws InterruptedException { + message = + connection.request(NatsMessageWritableHeaders.create(subject, headers, body), timeout); return message; } @@ -176,8 +178,7 @@ public static void onEnter( @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - message = NatsMessageWritableHeaders.create(message); - + message = NatsMessageWritableHeaders.create(message); natsRequest = NatsRequest.create(connection, message); Context parentContext = Context.current(); @@ -241,7 +242,7 @@ public static class RequestFutureHeadersBodyAdvice { public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(value = 1,readOnly = false) Headers headers, + @Advice.Argument(value = 1, readOnly = false) Headers headers, @Advice.Argument(2) byte[] body, @Advice.Local("future") CompletableFuture future) { future = connection.request(NatsMessageWritableHeaders.create(subject, headers, body)); @@ -268,8 +269,7 @@ public static void onEnter( @Advice.Local("otelParentContext") Context otelParentContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - message = NatsMessageWritableHeaders.create(message); - + message = NatsMessageWritableHeaders.create(message); natsRequest = NatsRequest.create(connection, message); otelParentContext = Context.current(); @@ -358,12 +358,12 @@ public static class RequestTimeoutFutureMessageAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @Advice.This Connection connection, - @Advice.Argument(value = 0,readOnly = false) Message message, + @Advice.Argument(value = 0, readOnly = false) Message message, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelParentContext") Context otelParentContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - message = NatsMessageWritableHeaders.create(message); + message = NatsMessageWritableHeaders.create(message); natsRequest = NatsRequest.create(connection, message); otelParentContext = Context.current(); diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java index f26bd6c349c7..d25df179a09f 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java @@ -7,7 +7,6 @@ import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; import static io.opentelemetry.javaagent.instrumentation.nats.v2_17.NatsSingletons.CONSUMER_PROCESS_INSTRUMENTER; -import static io.opentelemetry.javaagent.instrumentation.nats.v2_17.NatsSingletons.CONSUMER_RECEIVE_INSTRUMENTER; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; @@ -16,8 +15,6 @@ import io.nats.client.Message; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; -import io.opentelemetry.instrumentation.api.internal.Timer; import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; @@ -54,19 +51,6 @@ public static void onEnter( Context parentContext = Context.current(); natsRequest = NatsRequest.create(message.getConnection(), message); - if (CONSUMER_RECEIVE_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - Timer timer = Timer.start(); - parentContext = - InstrumenterUtil.startAndEnd( - CONSUMER_RECEIVE_INSTRUMENTER, - parentContext, - natsRequest, - null, - null, - timer.startTime(), - timer.now()); - } - if (!CONSUMER_PROCESS_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { return; } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsSingletons.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsSingletons.java index ad0a6cceff0a..98320012351b 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsSingletons.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsSingletons.java @@ -6,7 +6,6 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_17; import static io.opentelemetry.instrumentation.nats.v2_17.internal.NatsInstrumenterFactory.createConsumerProcessInstrumenter; -import static io.opentelemetry.instrumentation.nats.v2_17.internal.NatsInstrumenterFactory.createConsumerReceiveInstrumenter; import static io.opentelemetry.instrumentation.nats.v2_17.internal.NatsInstrumenterFactory.createProducerInstrumenter; import io.opentelemetry.api.GlobalOpenTelemetry; @@ -26,10 +25,6 @@ public final class NatsSingletons { public static final Instrumenter PRODUCER_INSTRUMENTER = createProducerInstrumenter(GlobalOpenTelemetry.get(), capturedHeaders); - public static final Instrumenter CONSUMER_RECEIVE_INSTRUMENTER = - createConsumerReceiveInstrumenter( - GlobalOpenTelemetry.get(), messagingReceiveInstrumentationEnabled, capturedHeaders); - public static final Instrumenter CONSUMER_PROCESS_INSTRUMENTER = createConsumerProcessInstrumenter( GlobalOpenTelemetry.get(), messagingReceiveInstrumentationEnabled, capturedHeaders); diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java index ae91a178e8d7..138d7d4beec7 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java @@ -19,5 +19,4 @@ class NatsInstrumentationDispatcherTest extends AbstractNatsInstrumentationDispa protected InstrumentationExtension testing() { return testing; } - } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java index e16d1dd9ca83..bdd437b16250 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java @@ -19,5 +19,4 @@ class NatsInstrumentationExperimentalTest extends AbstractNatsInstrumentationExp protected InstrumentationExtension testing() { return testing; } - } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java index c37b11b275aa..99f98610f43c 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java @@ -19,5 +19,4 @@ class NatsInstrumentationPublishTest extends AbstractNatsInstrumentationPublishT protected InstrumentationExtension testing() { return testing; } - } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java index daaab71471fe..e378b30e845a 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java @@ -24,5 +24,4 @@ protected InstrumentationExtension testing() { protected boolean isInboxMonitored() { return true; } - } diff --git a/instrumentation/nats/nats-2.17/library/build.gradle.kts b/instrumentation/nats/nats-2.17/library/build.gradle.kts index 040c68f98a06..e635c4a59936 100644 --- a/instrumentation/nats/nats-2.17/library/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/library/build.gradle.kts @@ -11,7 +11,6 @@ dependencies { testImplementation(project(":instrumentation:nats:nats-2.17:testing")) } - tasks { withType().configureEach { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java index c086f847f321..e73c67e3efa1 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java @@ -17,15 +17,11 @@ public final class OpenTelemetryDispatcherFactory extends DispatcherFactory { private final DispatcherFactory delegate; - private final Instrumenter consumerReceiveInstrumenter; private final Instrumenter consumerProcessInstrumenter; public OpenTelemetryDispatcherFactory( - DispatcherFactory delegate, - Instrumenter consumerReceiveInstrumenter, - Instrumenter consumerProcessInstrumenter) { + DispatcherFactory delegate, Instrumenter consumerProcessInstrumenter) { this.delegate = delegate; - this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; this.consumerProcessInstrumenter = consumerProcessInstrumenter; } @@ -33,7 +29,6 @@ public OpenTelemetryDispatcherFactory( NatsDispatcher createDispatcher(NatsConnection natsConnection, MessageHandler messageHandler) { return delegate.createDispatcher( natsConnection, - new OpenTelemetryMessageHandler( - messageHandler, consumerReceiveInstrumenter, consumerProcessInstrumenter)); + new OpenTelemetryMessageHandler(messageHandler, consumerProcessInstrumenter)); } } diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java index dfe081fbb99a..9475880560f7 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java @@ -24,15 +24,12 @@ public static NatsTelemetryBuilder builder(OpenTelemetry openTelemetry) { } private final Instrumenter producerInstrumenter; - private final Instrumenter consumerReceiveInstrumenter; private final Instrumenter consumerProcessInstrumenter; public NatsTelemetry( Instrumenter producerInstrumenter, - Instrumenter consumerReceiveInstrumenter, Instrumenter consumerProcessInstrumenter) { this.producerInstrumenter = producerInstrumenter; - this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; this.consumerProcessInstrumenter = consumerProcessInstrumenter; } @@ -43,7 +40,7 @@ public NatsTelemetry( */ public Connection wrap(Connection connection) { return OpenTelemetryConnection.wrap( - connection, producerInstrumenter, consumerReceiveInstrumenter, consumerProcessInstrumenter); + connection, producerInstrumenter, consumerProcessInstrumenter); } /** Returns a {@link Options.Builder} with instrumented {@link DispatcherFactory}. */ @@ -55,7 +52,6 @@ public Options.Builder wrap(Options.Builder options) { } return options.dispatcherFactory( - new OpenTelemetryDispatcherFactory( - factory, consumerReceiveInstrumenter, consumerProcessInstrumenter)); + new OpenTelemetryDispatcherFactory(factory, consumerProcessInstrumenter)); } } diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java index e3e6f56f8b75..6f7e4bd22754 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java @@ -39,8 +39,6 @@ public NatsTelemetryBuilder setCapturedHeaders(Collection capturedHeader public NatsTelemetry build() { return new NatsTelemetry( NatsInstrumenterFactory.createProducerInstrumenter(openTelemetry, capturedHeaders), - NatsInstrumenterFactory.createConsumerReceiveInstrumenter( - openTelemetry, messagingReceiveInstrumentationEnabled, capturedHeaders), NatsInstrumenterFactory.createConsumerProcessInstrumenter( openTelemetry, messagingReceiveInstrumentationEnabled, capturedHeaders)); } diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java index 8457b98d8c0b..674bd7b29505 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java @@ -26,34 +26,27 @@ final class OpenTelemetryConnection implements InvocationHandler { private final Connection delegate; private final Instrumenter producerInstrumenter; - private final Instrumenter consumerReceiveInstrumenter; private final Instrumenter consumerProcessInstrumenter; public OpenTelemetryConnection( Connection connection, Instrumenter producerInstrumenter, - Instrumenter consumerReceiveInstrumenter, Instrumenter consumerProcessInstrumenter) { this.delegate = connection; this.producerInstrumenter = producerInstrumenter; - this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; this.consumerProcessInstrumenter = consumerProcessInstrumenter; } public static Connection wrap( Connection connection, Instrumenter producerInstrumenter, - Instrumenter consumerReceiveInstrumenter, Instrumenter consumerProcessInstrumenter) { return (Connection) Proxy.newProxyInstance( OpenTelemetryConnection.class.getClassLoader(), new Class[] {Connection.class}, new OpenTelemetryConnection( - connection, - producerInstrumenter, - consumerReceiveInstrumenter, - consumerProcessInstrumenter)); + connection, producerInstrumenter, consumerProcessInstrumenter)); } @Override @@ -84,8 +77,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl return invokeMethod(method, delegate, args); } - private static Object invokeMethod(Method method, Object target, Object[] args) - throws Throwable { + private static Object invokeMethod(Method method, Object target, Object[] args) throws Throwable { try { return method.invoke(target, args); } catch (InvocationTargetException exception) { @@ -110,21 +102,22 @@ private void publish(Method method, Object[] args) throws Throwable { && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == Headers.class && method.getParameterTypes()[2] == byte[].class) { - message = NatsMessageWritableHeaders.create((String) args[0], (Headers) args[1], - (byte[]) args[2]); + message = + NatsMessageWritableHeaders.create((String) args[0], (Headers) args[1], (byte[]) args[2]); } else if (method.getParameterCount() == 3 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == String.class && method.getParameterTypes()[2] == byte[].class) { - message = NatsMessageWritableHeaders.create((String) args[0], (String) args[1], - (byte[]) args[2]); + message = + NatsMessageWritableHeaders.create((String) args[0], (String) args[1], (byte[]) args[2]); } else if (method.getParameterCount() == 4 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == String.class && method.getParameterTypes()[2] == Headers.class && method.getParameterTypes()[3] == byte[].class) { - message = NatsMessageWritableHeaders.create((String) args[0], (String) args[1], - (Headers) args[2], (byte[]) args[3]); + message = + NatsMessageWritableHeaders.create( + (String) args[0], (String) args[1], (Headers) args[2], (byte[]) args[3]); } else if (method.getParameterCount() == 1 && method.getParameterTypes()[0] == Message.class) { message = NatsMessageWritableHeaders.create((Message) args[0]); } @@ -166,8 +159,8 @@ private Message request(Method method, Object[] args) throws Throwable { && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == Headers.class && method.getParameterTypes()[2] == byte[].class) { - message = NatsMessageWritableHeaders.create((String) args[0], (Headers) args[1], - (byte[]) args[2]); + message = + NatsMessageWritableHeaders.create((String) args[0], (Headers) args[1], (byte[]) args[2]); timeout = (Duration) args[3]; } else if (method.getParameterCount() == 2 && method.getParameterTypes()[0] == Message.class) { message = NatsMessageWritableHeaders.create((Message) args[0]); @@ -180,8 +173,9 @@ private Message request(Method method, Object[] args) throws Throwable { natsRequest = NatsRequest.create(delegate, message); } - if (timeout == null || natsRequest == null || !producerInstrumenter.shouldStart(parentContext, - natsRequest)) { + if (timeout == null + || natsRequest == null + || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { return (Message) invokeMethod(method, delegate, args); } @@ -265,16 +259,15 @@ private CompletableFuture requestAsync(Method method, Object[] args) th future = delegate.request(message); } - return future - .whenComplete( - (result, exception) -> { - if (result != null) { - NatsRequest response = NatsRequest.create(delegate, result); - producerInstrumenter.end(context, notNullNatsRequest, response, exception); - } else { - producerInstrumenter.end(context, notNullNatsRequest, null, exception); - } - }); + return future.whenComplete( + (result, exception) -> { + if (result != null) { + NatsRequest response = NatsRequest.create(delegate, result); + producerInstrumenter.end(context, notNullNatsRequest, response, exception); + } else { + producerInstrumenter.end(context, notNullNatsRequest, null, exception); + } + }); } // public Dispatcher createDispatcher() @@ -282,13 +275,11 @@ private CompletableFuture requestAsync(Method method, Object[] args) th private Dispatcher createDispatcher(Method method, Object[] args) throws Throwable { if (method.getParameterCount() == 1 && method.getParameterTypes()[0] == MessageHandler.class) { args[0] = - new OpenTelemetryMessageHandler( - (MessageHandler) args[0], consumerReceiveInstrumenter, consumerProcessInstrumenter); + new OpenTelemetryMessageHandler((MessageHandler) args[0], consumerProcessInstrumenter); } Dispatcher wrapped = (Dispatcher) invokeMethod(method, delegate, args); - return OpenTelemetryDispatcher.wrap( - wrapped, consumerReceiveInstrumenter, consumerProcessInstrumenter); + return OpenTelemetryDispatcher.wrap(wrapped, consumerProcessInstrumenter); } // public void closeDispatcher(Dispatcher dispatcher) diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java index 1e4089a5e596..096ce1f51f93 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java @@ -18,28 +18,21 @@ final class OpenTelemetryDispatcher implements InvocationHandler { private final Dispatcher delegate; - private final Instrumenter consumerReceiveInstrumenter; private final Instrumenter consumerProcessInstrumenter; public OpenTelemetryDispatcher( - Dispatcher delegate, - Instrumenter consumerReceiveInstrumenter, - Instrumenter consumerProcessInstrumenter) { + Dispatcher delegate, Instrumenter consumerProcessInstrumenter) { this.delegate = delegate; - this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; this.consumerProcessInstrumenter = consumerProcessInstrumenter; } public static Dispatcher wrap( - Dispatcher delegate, - Instrumenter consumerReceiveInstrumenter, - Instrumenter consumerProcessInstrumenter) { + Dispatcher delegate, Instrumenter consumerProcessInstrumenter) { return (Dispatcher) Proxy.newProxyInstance( OpenTelemetryDispatcher.class.getClassLoader(), new Class[] {Dispatcher.class}, - new OpenTelemetryDispatcher( - delegate, consumerReceiveInstrumenter, consumerProcessInstrumenter)); + new OpenTelemetryDispatcher(delegate, consumerProcessInstrumenter)); } @Override @@ -69,13 +62,11 @@ private static Object invokeProxyMethod(Method method, Object target, Object[] a private Subscription subscribe(Method method, Object[] args) throws Throwable { if (method.getParameterCount() == 2 && method.getParameterTypes()[1] == MessageHandler.class) { args[1] = - new OpenTelemetryMessageHandler( - (MessageHandler) args[1], consumerReceiveInstrumenter, consumerProcessInstrumenter); + new OpenTelemetryMessageHandler((MessageHandler) args[1], consumerProcessInstrumenter); } else if (method.getParameterCount() == 3 && method.getParameterTypes()[2] == MessageHandler.class) { args[2] = - new OpenTelemetryMessageHandler( - (MessageHandler) args[2], consumerReceiveInstrumenter, consumerProcessInstrumenter); + new OpenTelemetryMessageHandler((MessageHandler) args[2], consumerProcessInstrumenter); } return (Subscription) invokeProxyMethod(method, delegate, args); diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java index 612a03e00cd8..26b5c2c9932e 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java @@ -10,8 +10,6 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; -import io.opentelemetry.instrumentation.api.internal.Timer; import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; /** @@ -21,15 +19,11 @@ public final class OpenTelemetryMessageHandler implements MessageHandler { private final MessageHandler delegate; - private final Instrumenter consumerReceiveInstrumenter; private final Instrumenter consumerProcessInstrumenter; public OpenTelemetryMessageHandler( - MessageHandler delegate, - Instrumenter consumerReceiveInstrumenter, - Instrumenter consumerProcessInstrumenter) { + MessageHandler delegate, Instrumenter consumerProcessInstrumenter) { this.delegate = delegate; - this.consumerReceiveInstrumenter = consumerReceiveInstrumenter; this.consumerProcessInstrumenter = consumerProcessInstrumenter; } @@ -38,19 +32,6 @@ public void onMessage(Message message) throws InterruptedException { Context parentContext = Context.current(); NatsRequest natsRequest = NatsRequest.create(message.getConnection(), message); - if (consumerReceiveInstrumenter.shouldStart(parentContext, natsRequest)) { - Timer timer = Timer.start(); - parentContext = - InstrumenterUtil.startAndEnd( - consumerReceiveInstrumenter, - parentContext, - natsRequest, - null, - null, - timer.startTime(), - timer.now()); - } - if (!consumerProcessInstrumenter.shouldStart(parentContext, natsRequest)) { delegate.onMessage(message); return; diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java index 45340bcf0707..123a7a270f2d 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java @@ -28,32 +28,15 @@ public static Instrumenter createProducerInstrumenter( openTelemetry, INSTRUMENTATION_NAME, MessagingSpanNameExtractor.create( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PUBLISH)) + NatsRequestMessagingAttributesGetter.INSTANCE, MessageOperation.PUBLISH)) .addAttributesExtractor( MessagingAttributesExtractor.builder( - NatsRequestMessagingAttributesGetter.NATS_REQUEST_INSTANCE, - MessageOperation.PUBLISH) + NatsRequestMessagingAttributesGetter.INSTANCE, MessageOperation.PUBLISH) .setCapturedHeaders(capturedHeaders) .build()) .buildProducerInstrumenter(NatsRequestTextMapSetter.INSTANCE); } - public static Instrumenter createConsumerReceiveInstrumenter( - OpenTelemetry openTelemetry, boolean enabled, List capturedHeaders) { - return Instrumenter.builder( - openTelemetry, - INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE)) - .addAttributesExtractor( - MessagingAttributesExtractor.builder( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.RECEIVE) - .setCapturedHeaders(capturedHeaders) - .build()) - .setEnabled(enabled) - .buildConsumerInstrumenter(NatsRequestTextMapGetter.INSTANCE); - } - public static Instrumenter createConsumerProcessInstrumenter( OpenTelemetry openTelemetry, boolean messagingReceiveInstrumentationEnabled, @@ -63,11 +46,10 @@ public static Instrumenter createConsumerProcessInstrumenter( openTelemetry, INSTRUMENTATION_NAME, MessagingSpanNameExtractor.create( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, MessageOperation.PROCESS)) + NatsRequestMessagingAttributesGetter.INSTANCE, MessageOperation.PROCESS)) .addAttributesExtractor( MessagingAttributesExtractor.builder( - NatsRequestMessagingAttributesGetter.VOID_INSTANCE, - MessageOperation.PROCESS) + NatsRequestMessagingAttributesGetter.INSTANCE, MessageOperation.PROCESS) .setCapturedHeaders(capturedHeaders) .build()); diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java index 49835bda53e8..67861a00cc44 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java @@ -1,5 +1,9 @@ -package io.opentelemetry.instrumentation.nats.v2_17.internal; +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package io.opentelemetry.instrumentation.nats.v2_17.internal; import io.nats.client.Message; import io.nats.client.impl.Headers; @@ -16,33 +20,45 @@ public static Message create(String subject, byte[] body) { } public static Message create(String subject, String replyTo, byte[] body) { - return NatsMessage.builder().subject(subject).replyTo(replyTo).headers(new Headers()).data(body).build(); + return NatsMessage.builder() + .subject(subject) + .replyTo(replyTo) + .headers(new Headers()) + .data(body) + .build(); } public static Message create(String subject, Headers headers, byte[] body) { - if(headers == null || headers.isReadOnly()) { + if (headers == null || headers.isReadOnly()) { headers = new Headers(headers); } return NatsMessage.builder().subject(subject).headers(headers).data(body).build(); } public static Message create(String subject, String replyTo, Headers headers, byte[] body) { - if(headers == null || headers.isReadOnly()) { + if (headers == null || headers.isReadOnly()) { headers = new Headers(headers); } - return NatsMessage.builder().subject(subject).replyTo(replyTo).headers(headers).data(body).build(); + return NatsMessage.builder() + .subject(subject) + .replyTo(replyTo) + .headers(headers) + .data(body) + .build(); } public static Message create(Message message) { - if (message instanceof NatsMessage && - (!message.hasHeaders() || message.getHeaders().isReadOnly()) - ) { - return NatsMessage.builder().subject(message.getSubject()).replyTo(message.getReplyTo()) - .headers(new Headers(message.getHeaders())).data(message.getData()).build(); + if (message instanceof NatsMessage + && (!message.hasHeaders() || message.getHeaders().isReadOnly())) { + return NatsMessage.builder() + .subject(message.getSubject()) + .replyTo(message.getReplyTo()) + .headers(new Headers(message.getHeaders())) + .data(message.getData()) + .build(); } return message; } private NatsMessageWritableHeaders() {} - } diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java index bb607a24cdc1..63a73a0d2ead 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java @@ -21,7 +21,8 @@ public abstract class NatsRequest { public static NatsRequest create(Connection connection, Message message) { return new AutoValue_NatsRequest( message.getReplyTo(), - (message.getConnection() == null ? connection : message.getConnection()).getServerInfo() + (message.getConnection() == null ? connection : message.getConnection()) + .getServerInfo() .getClientId(), message.getSubject(), message.getHeaders(), diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java index 5179cf15705c..45a7fb187c0d 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java @@ -9,10 +9,7 @@ class NatsRequestMessagingAttributesGetter { - static final MessagingAttributesGetter VOID_INSTANCE = - NatsRequestMessagingAttributesGetterFactory.create(); - - static final MessagingAttributesGetter NATS_REQUEST_INSTANCE = + static final MessagingAttributesGetter INSTANCE = NatsRequestMessagingAttributesGetterFactory.create(); private NatsRequestMessagingAttributesGetter() {} diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java index 4a3289eb6de3..7fdf75e82071 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java @@ -24,5 +24,4 @@ protected InstrumentationExtension testing() { static void beforeAll() { connection = NatsTelemetry.create(testing.getOpenTelemetry()).wrap(connection); } - } diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java index 859ccde20e46..5f1c9aa0ba5c 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java @@ -24,9 +24,11 @@ protected InstrumentationExtension testing() { @BeforeAll static void beforeAll() { - connection = NatsTelemetry.builder(testing.getOpenTelemetry()) - .setMessagingReceiveInstrumentationEnabled(true) - .setCapturedHeaders(singletonList("captured-header")) - .build().wrap(connection); + connection = + NatsTelemetry.builder(testing.getOpenTelemetry()) + .setMessagingReceiveInstrumentationEnabled(true) + .setCapturedHeaders(singletonList("captured-header")) + .build() + .wrap(connection); } } diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java index 5005d7e99983..99536cdfee5a 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java @@ -17,7 +17,8 @@ import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationDispatcherTest extends AbstractNatsInstrumentationTest { +public abstract class AbstractNatsInstrumentationDispatcherTest + extends AbstractNatsInstrumentationTest { private int clientId; @@ -29,7 +30,8 @@ void beforeEach() { @Test void testSubscribe() { // global message handler - Dispatcher d1 = connection.createDispatcher(msg -> System.out.println("1 " + msg)).subscribe("sub"); + Dispatcher d1 = + connection.createDispatcher(msg -> System.out.println("1 " + msg)).subscribe("sub"); // per-subscription message handler Dispatcher d2 = connection.createDispatcher(); Subscription s1 = d2.subscribe("sub", msg -> System.out.println("2 " + msg)); @@ -42,7 +44,8 @@ void testSubscribe() { () -> { NatsMessage.Builder builder = NatsMessage.builder().subject("sub").data("x"); connection.publish(builder.replyTo("a").build()); // no propagation - connection.publish(builder.replyTo("b").headers(new Headers()).build()); // propagation + connection.publish( + builder.replyTo("b").headers(new Headers()).build()); // propagation }); // then 1 trace @@ -93,8 +96,7 @@ void testSubscribe() { .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(5)) .hasAttributesSatisfyingExactly( - messagingAttributes("process", "sub", clientId))) - ); + messagingAttributes("process", "sub", clientId)))); // finally, to make sure we're unwrapping properly the // OpenTelemetryDispatcher in the library diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java index 9439e3a4ad4f..5ec1af12c86c 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java @@ -23,7 +23,8 @@ import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationExperimentalTest extends AbstractNatsInstrumentationTest { +public abstract class AbstractNatsInstrumentationExperimentalTest + extends AbstractNatsInstrumentationTest { private int clientId; @@ -72,16 +73,10 @@ void testMessagingReceiveAndCapturedHeaders() { messagingAttributes("publish", "sub", clientId, headerAssert))), trace -> trace.hasSpansSatisfyingExactly( - span -> - span.hasName("sub receive") - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasAttributesSatisfyingExactly( - messagingAttributes("receive", "sub", clientId, headerAssert)), span -> span.hasName("sub process") .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) + .hasNoParent() .hasLinksSatisfying( links -> assertThat(links) diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java index f14a3b9a7505..318c9f7ea5e2 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java @@ -18,7 +18,8 @@ import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationPublishTest extends AbstractNatsInstrumentationTest { +public abstract class AbstractNatsInstrumentationPublishTest + extends AbstractNatsInstrumentationTest { private int clientId; private Subscription subscription; @@ -47,8 +48,7 @@ void testPublishBody() throws InterruptedException { @Test void testPublishHeadersBody() throws InterruptedException { // when - testing() - .runWithSpan("parent", () -> connection.publish("sub", new Headers(), new byte[] {0})); + testing().runWithSpan("parent", () -> connection.publish("sub", new Headers(), new byte[] {0})); // then assertPublishSpan(); diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java index 82d489cc7a14..9405e380c7a4 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java @@ -26,7 +26,8 @@ import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationRequestTest extends AbstractNatsInstrumentationTest { +public abstract class AbstractNatsInstrumentationRequestTest + extends AbstractNatsInstrumentationTest { protected abstract boolean isInboxMonitored(); @@ -87,8 +88,7 @@ void testRequestHeadersBody() throws InterruptedException { testing() .runWithSpan( "parent", - () -> - connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + () -> connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); connection.closeDispatcher(dispatcher); // then @@ -228,9 +228,8 @@ void testRequestTimeoutFutureHeadersBody() throws InterruptedException { .runWithSpan( "parent", () -> - connection - .requestWithTimeout( - "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + connection.requestWithTimeout( + "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); @@ -244,8 +243,7 @@ void testRequestTimeoutFutureMessage() throws InterruptedException { // when testing() - .runWithSpan( - "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); + .runWithSpan("parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); @@ -260,8 +258,7 @@ void testRequestTimeoutFutureMessageHeaders() throws InterruptedException { // when testing() - .runWithSpan( - "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); + .runWithSpan("parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); From 041a7a63bffb5940ee4ead85ec64582a0bb82524 Mon Sep 17 00:00:00 2001 From: Alix Date: Fri, 29 Aug 2025 14:32:12 +0200 Subject: [PATCH 23/34] fix to minimal version --- docs/instrumentation-list.yaml | 4 ++-- docs/supported-libraries.md | 2 +- instrumentation/nats/nats-2.17/javaagent/build.gradle.kts | 8 ++++++-- instrumentation/nats/nats-2.17/library/build.gradle.kts | 2 +- .../nats/v2_17/internal/NatsMessageWritableHeaders.java | 2 ++ instrumentation/nats/nats-2.17/testing/build.gradle.kts | 2 +- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index 84d0fdff1eb4..57001d6b5747 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -4180,9 +4180,9 @@ libraries: name: io.opentelemetry.nats-2.17 target_versions: javaagent: - - io.nats:jnats:[2.17.2,) + - io.nats:jnats:[2.17.7,) library: - - io.nats:jnats:2.21.5 + - io.nats:jnats:2.17.7 configurations: - name: otel.instrumentation.messaging.experimental.receive-telemetry.enabled description: | diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 9fcfc15eb556..94f1a07bee07 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -105,7 +105,7 @@ These are the supported libraries and frameworks: | [Micrometer](https://micrometer.io/) | 1.5+ (disabled by default) | [opentelemetry-micrometer-1.5](../instrumentation/micrometer/micrometer-1.5/library) | none | | [MongoDB Driver](https://mongodb.github.io/mongo-java-driver/) | 3.1+ | [opentelemetry-mongo-3.1](../instrumentation/mongo/mongo-3.1/library) | [Database Client Spans], [Database Client Metrics] [6] | | [MyBatis](https://mybatis.org/mybatis-3/) | 3.2+ | N/A | none | -| [NATS](https://github.com/nats-io/nats.java) | 2.17.2+ | [nats-2.17](../instrumentation/nats/nats-2.17/library) | [Messaging Spans] | +| [NATS](https://github.com/nats-io/nats.java) | 2.17.7+ | [nats-2.17](../instrumentation/nats/nats-2.17/library) | [Messaging Spans] | | [Netty HTTP codec [5]](https://github.com/netty/netty) | 3.8+ | [opentelemetry-netty-4.1](../instrumentation/netty/netty-4.1/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] | | [OpenAI Java SDK](https://github.com/openai/openai-java) | 1.1+ | [openai-java-1.1](../instrumentation/openai/openai-java-1.1/library) | [GenAI Client Spans], [GenAI Client Metrics] | | [OpenSearch Rest Client](https://github.com/opensearch-project/opensearch-java) | 1.0+ | | [Database Client Spans], [Database Client Metrics] [6] | diff --git a/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts index ca8a013d1f12..155eeb0a5fa9 100644 --- a/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts @@ -6,17 +6,21 @@ muzzle { pass { group.set("io.nats") module.set("jnats") - versions.set("[2.17.2,)") + versions.set("[2.17.7,)") // Could not find io.nats:nats-parent:1.0-SNAPSHOT skip("0.5.0", "0.5.1") + // Headers are readOnly, so context can not be propagated + // https://github.com/nats-io/nats.java/pull/1123 + skip("2.17.2","2.17.3","2.17.4","2.17.5","2.17.6") + assertInverse.set(true) } } dependencies { - library("io.nats:jnats:2.21.5") + library("io.nats:jnats:2.17.7") implementation(project(":instrumentation:nats:nats-2.17:library")) testImplementation(project(":instrumentation:nats:nats-2.17:testing")) diff --git a/instrumentation/nats/nats-2.17/library/build.gradle.kts b/instrumentation/nats/nats-2.17/library/build.gradle.kts index e635c4a59936..de556150aeef 100644 --- a/instrumentation/nats/nats-2.17/library/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/library/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } dependencies { - library("io.nats:jnats:2.21.5") + library("io.nats:jnats:2.17.7") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java index 67861a00cc44..af2f739ac0e0 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java @@ -16,6 +16,8 @@ public final class NatsMessageWritableHeaders { public static Message create(String subject, byte[] body) { + System.out.println(new Headers()); + System.out.println(new Headers().isReadOnly()); return NatsMessage.builder().subject(subject).headers(new Headers()).data(body).build(); } diff --git a/instrumentation/nats/nats-2.17/testing/build.gradle.kts b/instrumentation/nats/nats-2.17/testing/build.gradle.kts index 9296a94e62f5..74c2181d2ec4 100644 --- a/instrumentation/nats/nats-2.17/testing/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/testing/build.gradle.kts @@ -5,7 +5,7 @@ plugins { dependencies { api(project(":testing-common")) - compileOnly("io.nats:jnats:2.21.5") + compileOnly("io.nats:jnats:2.17.7") implementation("org.testcontainers:testcontainers") } From 85a0d6384baf50990a1eff10fba6cc8da53b8683 Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Fri, 29 Aug 2025 12:36:16 +0000 Subject: [PATCH 24/34] ./gradlew spotlessApply --- instrumentation/nats/nats-2.17/javaagent/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts index 155eeb0a5fa9..a93d2795665d 100644 --- a/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts @@ -13,7 +13,7 @@ muzzle { // Headers are readOnly, so context can not be propagated // https://github.com/nats-io/nats.java/pull/1123 - skip("2.17.2","2.17.3","2.17.4","2.17.5","2.17.6") + skip("2.17.2", "2.17.3", "2.17.4", "2.17.5", "2.17.6") assertInverse.set(true) } From 0a89c79ae9e8cab626bab93ddf1cf9a7bc29f3fe Mon Sep 17 00:00:00 2001 From: Alix Date: Fri, 29 Aug 2025 14:39:33 +0200 Subject: [PATCH 25/34] remove unwanted code --- .../v2_17/internal/NatsMessageWritableHeaders.java | 2 -- .../AbstractNatsInstrumentationDispatcherTest.java | 12 +++++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java index af2f739ac0e0..67861a00cc44 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java @@ -16,8 +16,6 @@ public final class NatsMessageWritableHeaders { public static Message create(String subject, byte[] body) { - System.out.println(new Headers()); - System.out.println(new Headers().isReadOnly()); return NatsMessage.builder().subject(subject).headers(new Headers()).data(body).build(); } diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java index 99536cdfee5a..4f890e4843bf 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java @@ -30,12 +30,11 @@ void beforeEach() { @Test void testSubscribe() { // global message handler - Dispatcher d1 = - connection.createDispatcher(msg -> System.out.println("1 " + msg)).subscribe("sub"); + Dispatcher d1 = connection.createDispatcher(msg -> {}).subscribe("sub"); // per-subscription message handler Dispatcher d2 = connection.createDispatcher(); - Subscription s1 = d2.subscribe("sub", msg -> System.out.println("2 " + msg)); - Subscription s2 = d2.subscribe("sub", "queue", msg -> System.out.println("3 " + msg)); + Subscription s1 = d2.subscribe("sub", msg -> {}); + Subscription s2 = d2.subscribe("sub", "queue", msg -> {}); // when testing() @@ -43,9 +42,8 @@ void testSubscribe() { "parent", () -> { NatsMessage.Builder builder = NatsMessage.builder().subject("sub").data("x"); - connection.publish(builder.replyTo("a").build()); // no propagation - connection.publish( - builder.replyTo("b").headers(new Headers()).build()); // propagation + connection.publish(builder.build()); // no propagation + connection.publish(builder.headers(new Headers()).build()); // propagation }); // then 1 trace From df431c4674856308b6c90346b2de6825d786343b Mon Sep 17 00:00:00 2001 From: Alix Date: Fri, 29 Aug 2025 14:59:13 +0200 Subject: [PATCH 26/34] add tests on default inbox monitoring --- .../v2_17/NatsInstrumentationRequestTest.java | 5 ----- .../v2_17/NatsInstrumentationRequestTest.java | 19 +++++++++---------- ...bstractNatsInstrumentationRequestTest.java | 6 ------ 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java index e378b30e845a..451cb1fc80bb 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java @@ -19,9 +19,4 @@ class NatsInstrumentationRequestTest extends AbstractNatsInstrumentationRequestT protected InstrumentationExtension testing() { return testing; } - - @Override - protected boolean isInboxMonitored() { - return true; - } } diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java index 28525f6fa7ee..aadbad1a7789 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java @@ -5,8 +5,11 @@ package io.opentelemetry.instrumentation.nats.v2_17; +import io.nats.client.Nats; +import io.nats.client.Options; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.io.IOException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; @@ -21,15 +24,11 @@ protected InstrumentationExtension testing() { } @BeforeAll - static void beforeAll() { - connection = NatsTelemetry.create(testing.getOpenTelemetry()).wrap(connection); - } - - @Override - protected boolean isInboxMonitored() { - // in the library instrumentation, as we're proxying Connection, - // we can not properly instrument the Dispatcher and the MessageHandler - // created for every `request` on the _INBOX.* subjects - return false; + static void beforeAll() throws IOException, InterruptedException { + NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); + connection = + telemetry.wrap( + Nats.connect( + telemetry.wrap(Options.builder().server(connection.getConnectedUrl())).build())); } } diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java index 9405e380c7a4..160b30083c19 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java @@ -29,8 +29,6 @@ public abstract class AbstractNatsInstrumentationRequestTest extends AbstractNatsInstrumentationTest { - protected abstract boolean isInboxMonitored(); - private int clientId; private Subscription subscription; @@ -307,10 +305,6 @@ private void assertPublishReceiveSpansSameTrace() { .hasAttributesSatisfyingExactly( messagingAttributes("process", "_INBOX.", clientId)))); - if (!isInboxMonitored()) { - asserts.remove(asserts.size() - 1); - } - trace.hasSpansSatisfyingExactly(asserts); }); } From 6a3baf18a998823d27f4c077f81b148c285cc4bc Mon Sep 17 00:00:00 2001 From: Alix Date: Fri, 29 Aug 2025 17:47:55 +0200 Subject: [PATCH 27/34] remove readOnly=false on headers --- .../nats/v2_17/ConnectionPublishInstrumentation.java | 4 ++-- .../nats/v2_17/ConnectionRequestInstrumentation.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java index 37ee94f94b00..23481d669a70 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java @@ -92,7 +92,7 @@ public static class PublishHeadersBodyAdvice { public static boolean onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(value = 1, readOnly = false) Headers headers, + @Advice.Argument(1) Headers headers, @Advice.Argument(2) byte[] body) { connection.publish(NatsMessageWritableHeaders.create(subject, headers, body)); return true; @@ -119,7 +119,7 @@ public static boolean onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, @Advice.Argument(1) String replyTo, - @Advice.Argument(value = 2, readOnly = false) Headers headers, + @Advice.Argument(2) Headers headers, @Advice.Argument(3) byte[] body) { connection.publish(NatsMessageWritableHeaders.create(subject, replyTo, headers, body)); return true; diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java index b2b6b193245b..ff598d8543f4 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java @@ -149,7 +149,7 @@ public static class RequestHeadersBodyAdvice { public static Message onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(value = 1, readOnly = false) Headers headers, + @Advice.Argument(1) Headers headers, @Advice.Argument(2) byte[] body, @Advice.Argument(3) Duration timeout, @Advice.Local("message") Message message) @@ -242,7 +242,7 @@ public static class RequestFutureHeadersBodyAdvice { public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(value = 1, readOnly = false) Headers headers, + @Advice.Argument(1) Headers headers, @Advice.Argument(2) byte[] body, @Advice.Local("future") CompletableFuture future) { future = connection.request(NatsMessageWritableHeaders.create(subject, headers, body)); @@ -336,7 +336,7 @@ public static class RequestTimeoutFutureHeadersBodyAdvice { public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(value = 1, readOnly = false) Headers headers, + @Advice.Argument(1) Headers headers, @Advice.Argument(2) byte[] body, @Advice.Local("future") CompletableFuture future) { future = connection.request(NatsMessageWritableHeaders.create(subject, headers, body)); From 84ee013fa5666b8d7a5179bb21b55ff832f4a956 Mon Sep 17 00:00:00 2001 From: Alix Date: Wed, 3 Sep 2025 22:11:53 +0200 Subject: [PATCH 28/34] forward all calls to (subject, replyTo, headers, data) instead of (message) --- .../nats/nats-2.17/javaagent/build.gradle.kts | 15 +- .../ConnectionPublishInstrumentation.java | 40 +++--- .../ConnectionRequestInstrumentation.java | 131 +++++++++--------- .../nats/v2_17/NatsSingletons.java | 6 +- .../nats/nats-2.17/library/README.md | 19 +-- .../nats/v2_17/NatsTelemetry.java | 28 ++-- .../nats/v2_17/NatsTelemetryBuilder.java | 10 +- .../nats/v2_17/OpenTelemetryConnection.java | 109 ++++++++++----- .../internal/NatsInstrumenterFactory.java | 16 +-- .../internal/NatsMessageWritableHeaders.java | 45 +----- .../nats/v2_17/internal/NatsRequest.java | 20 ++- .../NatsRequestMessagingAttributesGetter.java | 84 ++++++++++- ...questMessagingAttributesGetterFactory.java | 93 ------------- .../NatsInstrumentationExperimentalTest.java | 1 - .../v2_17/NatsInstrumentationRequestTest.java | 5 +- ...ctNatsInstrumentationExperimentalTest.java | 52 +++---- ...bstractNatsInstrumentationRequestTest.java | 23 ++- .../v2_17/NatsInstrumentationTestHelper.java | 13 +- 18 files changed, 321 insertions(+), 389 deletions(-) delete mode 100644 instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetterFactory.java diff --git a/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts index a93d2795665d..944efe110d2f 100644 --- a/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts @@ -6,15 +6,11 @@ muzzle { pass { group.set("io.nats") module.set("jnats") - versions.set("[2.17.7,)") + versions.set("[2.17.2,)") // Could not find io.nats:nats-parent:1.0-SNAPSHOT skip("0.5.0", "0.5.1") - // Headers are readOnly, so context can not be propagated - // https://github.com/nats-io/nats.java/pull/1123 - skip("2.17.2", "2.17.3", "2.17.4", "2.17.5", "2.17.6") - assertInverse.set(true) } } @@ -26,29 +22,26 @@ dependencies { testImplementation(project(":instrumentation:nats:nats-2.17:testing")) } -val collectMetadata = findProperty("collectMetadata")?.toString() ?: "false" - tasks { withType().configureEach { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") } val testExperimental by registering(Test::class) { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath filter { includeTestsMatching("NatsInstrumentationExperimentalTest") } jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") jvmArgs("-Dotel.instrumentation.messaging.experimental.capture-headers=captured-header") - - systemProperty("collectMetadata", collectMetadata) } test { filter { excludeTestsMatching("NatsInstrumentationExperimentalTest") } - - systemProperty("collectMetadata", collectMetadata) } check { diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java index 23481d669a70..31a86abb80d4 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java @@ -81,7 +81,7 @@ public static boolean onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, @Advice.Argument(1) byte[] body) { - connection.publish(NatsMessageWritableHeaders.create(subject, body)); + connection.publish(subject, null, null, body); return true; } } @@ -94,7 +94,7 @@ public static boolean onEnter( @Advice.Argument(0) String subject, @Advice.Argument(1) Headers headers, @Advice.Argument(2) byte[] body) { - connection.publish(NatsMessageWritableHeaders.create(subject, headers, body)); + connection.publish(subject, null, headers, body); return true; } } @@ -107,39 +107,28 @@ public static boolean onEnter( @Advice.Argument(0) String subject, @Advice.Argument(1) String replyTo, @Advice.Argument(2) byte[] body) { - connection.publish(NatsMessageWritableHeaders.create(subject, replyTo, body)); + connection.publish(subject, replyTo, null, body); return true; } } @SuppressWarnings("unused") public static class PublishReplyToHeadersBodyAdvice { - @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) - public static boolean onEnter( - @Advice.This Connection connection, - @Advice.Argument(0) String subject, - @Advice.Argument(1) String replyTo, - @Advice.Argument(2) Headers headers, - @Advice.Argument(3) byte[] body) { - connection.publish(NatsMessageWritableHeaders.create(subject, replyTo, headers, body)); - return true; - } - } - - @SuppressWarnings("unused") - public static class PublishMessageAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @Advice.This Connection connection, - @Advice.Argument(value = 0, readOnly = false) Message message, + @Advice.Argument(0) String subject, + @Advice.Argument(1) String replyTo, + @Advice.Argument(value = 2, readOnly = false) Headers headers, + @Advice.Argument(3) byte[] body, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - message = NatsMessageWritableHeaders.create(message); + headers = NatsMessageWritableHeaders.create(headers); Context parentContext = Context.current(); - natsRequest = NatsRequest.create(connection, message); + natsRequest = NatsRequest.create(connection, subject, replyTo, headers, body); if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { return; @@ -163,4 +152,15 @@ public static void onExit( PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); } } + + @SuppressWarnings("unused") + public static class PublishMessageAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static boolean onEnter( + @Advice.This Connection connection, @Advice.Argument(0) Message message) { + connection.publish( + message.getSubject(), message.getReplyTo(), message.getHeaders(), message.getData()); + return true; + } + } } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java index ff598d8543f4..016054aac1f4 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java @@ -129,7 +129,7 @@ public static Message onEnter( @Advice.Argument(2) Duration timeout, @Advice.Local("message") Message message) throws InterruptedException { - message = connection.request(NatsMessageWritableHeaders.create(subject, body), timeout); + message = connection.request(subject, null, body, timeout); return message; } @@ -145,42 +145,18 @@ public static Message onExit( @SuppressWarnings("unused") public static class RequestHeadersBodyAdvice { - @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) - public static Message onEnter( + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(1) Headers headers, + @Advice.Argument(value = 1, readOnly = false) Headers headers, @Advice.Argument(2) byte[] body, @Advice.Argument(3) Duration timeout, - @Advice.Local("message") Message message) - throws InterruptedException { - message = - connection.request(NatsMessageWritableHeaders.create(subject, headers, body), timeout); - return message; - } - - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static Message onExit( - @Advice.Return(readOnly = false) Message returned, - @Advice.Local("message") Message message) { - returned = message; - return returned; - } - } - - @SuppressWarnings("unused") - public static class RequestMessageAdvice { - - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( - @Advice.This Connection connection, - @Advice.Argument(value = 0, readOnly = false) Message message, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - message = NatsMessageWritableHeaders.create(message); - - natsRequest = NatsRequest.create(connection, message); + headers = NatsMessageWritableHeaders.create(headers); + natsRequest = NatsRequest.create(connection, subject, null, headers, body); Context parentContext = Context.current(); if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { @@ -214,38 +190,40 @@ public static void onExit( } @SuppressWarnings("unused") - public static class RequestFutureBodyAdvice { + public static class RequestMessageAdvice { @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) - public static CompletableFuture onEnter( + public static Message onEnter( @Advice.This Connection connection, - @Advice.Argument(0) String subject, - @Advice.Argument(1) byte[] body, - @Advice.Local("future") CompletableFuture future) { - future = connection.request(NatsMessageWritableHeaders.create(subject, body)); - return future; + @Advice.Argument(0) Message request, + @Advice.Argument(1) Duration timeout, + @Advice.Local("response") Message response) + throws InterruptedException { + response = + connection.request( + request.getSubject(), request.getHeaders(), request.getData(), timeout); + return response; } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static CompletableFuture onExit( - @Advice.Return(readOnly = false) CompletableFuture messageFuture, - @Advice.Local("future") CompletableFuture future) { - messageFuture = future; - return messageFuture; + public static Message onExit( + @Advice.Return(readOnly = false) Message returned, + @Advice.Local("response") Message response) { + returned = response; + return returned; } } @SuppressWarnings("unused") - public static class RequestFutureHeadersBodyAdvice { + public static class RequestFutureBodyAdvice { @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(1) Headers headers, - @Advice.Argument(2) byte[] body, + @Advice.Argument(1) byte[] body, @Advice.Local("future") CompletableFuture future) { - future = connection.request(NatsMessageWritableHeaders.create(subject, headers, body)); + future = connection.request(subject, null, body); return future; } @@ -259,19 +237,20 @@ public static CompletableFuture onExit( } @SuppressWarnings("unused") - public static class RequestFutureMessageAdvice { + public static class RequestFutureHeadersBodyAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @Advice.This Connection connection, - @Advice.Argument(value = 0, readOnly = false) Message message, + @Advice.Argument(0) String subject, + @Advice.Argument(value = 1, readOnly = false) Headers headers, + @Advice.Argument(2) byte[] body, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelParentContext") Context otelParentContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - message = NatsMessageWritableHeaders.create(message); - - natsRequest = NatsRequest.create(connection, message); + headers = NatsMessageWritableHeaders.create(headers); + natsRequest = NatsRequest.create(connection, subject, null, headers, body); otelParentContext = Context.current(); if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { @@ -308,15 +287,14 @@ public static void onExit( } @SuppressWarnings("unused") - public static class RequestTimeoutFutureBodyAdvice { + public static class RequestFutureMessageAdvice { @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) public static CompletableFuture onEnter( @Advice.This Connection connection, - @Advice.Argument(0) String subject, - @Advice.Argument(1) byte[] body, + @Advice.Argument(0) Message message, @Advice.Local("future") CompletableFuture future) { - future = connection.request(NatsMessageWritableHeaders.create(subject, body)); + future = connection.request(message.getSubject(), message.getHeaders(), message.getData()); return future; } @@ -330,16 +308,16 @@ public static CompletableFuture onExit( } @SuppressWarnings("unused") - public static class RequestTimeoutFutureHeadersBodyAdvice { + public static class RequestTimeoutFutureBodyAdvice { @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(1) Headers headers, - @Advice.Argument(2) byte[] body, + @Advice.Argument(1) byte[] body, + @Advice.Argument(2) Duration timeout, @Advice.Local("future") CompletableFuture future) { - future = connection.request(NatsMessageWritableHeaders.create(subject, headers, body)); + future = connection.requestWithTimeout(subject, null, body, timeout); return future; } @@ -353,19 +331,20 @@ public static CompletableFuture onExit( } @SuppressWarnings("unused") - public static class RequestTimeoutFutureMessageAdvice { + public static class RequestTimeoutFutureHeadersBodyAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( @Advice.This Connection connection, - @Advice.Argument(value = 0, readOnly = false) Message message, + @Advice.Argument(0) String subject, + @Advice.Argument(value = 1, readOnly = false) Headers headers, + @Advice.Argument(2) byte[] body, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelParentContext") Context otelParentContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { - message = NatsMessageWritableHeaders.create(message); - - natsRequest = NatsRequest.create(connection, message); + headers = NatsMessageWritableHeaders.create(headers); + natsRequest = NatsRequest.create(connection, subject, null, headers, body); otelParentContext = Context.current(); if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { @@ -400,4 +379,28 @@ public static void onExit( } } } + + @SuppressWarnings("unused") + public static class RequestTimeoutFutureMessageAdvice { + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static CompletableFuture onEnter( + @Advice.This Connection connection, + @Advice.Argument(value = 0, readOnly = false) Message message, + @Advice.Argument(1) Duration timeout, + @Advice.Local("future") CompletableFuture future) { + future = + connection.requestWithTimeout( + message.getSubject(), message.getHeaders(), message.getData(), timeout); + return future; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static CompletableFuture onExit( + @Advice.Return(readOnly = false) CompletableFuture messageFuture, + @Advice.Local("future") CompletableFuture future) { + messageFuture = future; + return messageFuture; + } + } } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsSingletons.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsSingletons.java index 98320012351b..13e95063e94c 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsSingletons.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsSingletons.java @@ -16,9 +16,6 @@ public final class NatsSingletons { - private static final boolean messagingReceiveInstrumentationEnabled = - ExperimentalConfig.get().messagingReceiveInstrumentationEnabled(); - private static final List capturedHeaders = ExperimentalConfig.get().getMessagingHeaders(); @@ -26,8 +23,7 @@ public final class NatsSingletons { createProducerInstrumenter(GlobalOpenTelemetry.get(), capturedHeaders); public static final Instrumenter CONSUMER_PROCESS_INSTRUMENTER = - createConsumerProcessInstrumenter( - GlobalOpenTelemetry.get(), messagingReceiveInstrumentationEnabled, capturedHeaders); + createConsumerProcessInstrumenter(GlobalOpenTelemetry.get(), capturedHeaders); private NatsSingletons() {} } diff --git a/instrumentation/nats/nats-2.17/library/README.md b/instrumentation/nats/nats-2.17/library/README.md index 1ad359013ffe..c97ea40de88c 100644 --- a/instrumentation/nats/nats-2.17/library/README.md +++ b/instrumentation/nats/nats-2.17/library/README.md @@ -60,26 +60,9 @@ public class OpenTelemetryNatsConnection { // prefer wrapping the Options.Builder to get the full instrumentation // when using the default NatsConnection implementation public Connection create(Options.Builder builder) throws IOException, InterruptedException { - Options options = telemetry.wrap(builder).build(); + Options options = telemetry.configureDispatcher(builder).build(); Connection connection = Nats.connect(options); return wrap(connection); } } ``` - -### Trace propagation - -It's recommended to provide `Message` with a writable `Header` structure -to allow propagation between publishers and subscribers. Without headers, -the tracing context will not be propagated in the headers. - -```java -import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsMessage; - -// don't -Message msg = NatsMessage.builder().subject("sub").build(); - -// do -Message msg = NatsMessage.builder().subject("sub").headers(new Headers()).build(); -``` diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java index 9475880560f7..fda29d445eea 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java @@ -12,6 +12,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; +import java.io.IOException; public final class NatsTelemetry { @@ -33,18 +34,12 @@ public NatsTelemetry( this.consumerProcessInstrumenter = consumerProcessInstrumenter; } - /** - * Returns a decorated {@link Connection} with messaging spans instrumentation. This will *not* - * instrument the connection's main inbox by default. Use {@link #wrap(Options.Builder)} - * beforehand to build an Options doing so. - */ - public Connection wrap(Connection connection) { + Connection wrap(Connection connection) { return OpenTelemetryConnection.wrap( connection, producerInstrumenter, consumerProcessInstrumenter); } - /** Returns a {@link Options.Builder} with instrumented {@link DispatcherFactory}. */ - public Options.Builder wrap(Options.Builder options) { + Options.Builder configure(Options.Builder options) { DispatcherFactory factory = options.build().getDispatcherFactory(); if (factory == null) { @@ -54,4 +49,21 @@ public Options.Builder wrap(Options.Builder options) { return options.dispatcherFactory( new OpenTelemetryDispatcherFactory(factory, consumerProcessInstrumenter)); } + + /** Returns a {@link Connection} with messaging spans instrumentation. */ + public Connection newConnection(Options options, ConnectionFactory connectionFactory) + throws IOException, InterruptedException { + return wrap(connectionFactory.create(configure(new Options.Builder(options)).build())); + } + + /** Returns a {@link Connection} with messaging spans instrumentation. */ + public Connection newConnection( + Options.Builder builder, ConnectionFactory connectionFactory) + throws IOException, InterruptedException { + return wrap(connectionFactory.create(configure(builder))); + } + + public interface ConnectionFactory { + Connection create(T options) throws IOException, InterruptedException; + } } diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java index 6f7e4bd22754..00fbb0b22382 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java @@ -17,19 +17,12 @@ public final class NatsTelemetryBuilder { private final OpenTelemetry openTelemetry; - private boolean messagingReceiveInstrumentationEnabled = false; private List capturedHeaders = emptyList(); NatsTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; } - @CanIgnoreReturnValue - public NatsTelemetryBuilder setMessagingReceiveInstrumentationEnabled(boolean enabled) { - this.messagingReceiveInstrumentationEnabled = enabled; - return this; - } - @CanIgnoreReturnValue public NatsTelemetryBuilder setCapturedHeaders(Collection capturedHeaders) { this.capturedHeaders = new ArrayList<>(capturedHeaders); @@ -39,7 +32,6 @@ public NatsTelemetryBuilder setCapturedHeaders(Collection capturedHeader public NatsTelemetry build() { return new NatsTelemetry( NatsInstrumenterFactory.createProducerInstrumenter(openTelemetry, capturedHeaders), - NatsInstrumenterFactory.createConsumerProcessInstrumenter( - openTelemetry, messagingReceiveInstrumentationEnabled, capturedHeaders)); + NatsInstrumenterFactory.createConsumerProcessInstrumenter(openTelemetry, capturedHeaders)); } } diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java index 674bd7b29505..051a707694b5 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java @@ -91,41 +91,52 @@ private static Object invokeMethod(Method method, Object target, Object[] args) // void publish(String subject, String replyTo, Headers headers, byte[] body) // void publish(Message message) private void publish(Method method, Object[] args) throws Throwable { - Message message = null; - NatsRequest natsRequest = null; + String subject = null; + String replyTo = null; + Headers headers = null; + byte[] body = null; if (method.getParameterCount() == 2 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == byte[].class) { - message = NatsMessageWritableHeaders.create((String) args[0], (byte[]) args[1]); + subject = (String) args[0]; + body = (byte[]) args[1]; } else if (method.getParameterCount() == 3 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == Headers.class && method.getParameterTypes()[2] == byte[].class) { - message = - NatsMessageWritableHeaders.create((String) args[0], (Headers) args[1], (byte[]) args[2]); + subject = (String) args[0]; + headers = (Headers) args[1]; + body = (byte[]) args[2]; } else if (method.getParameterCount() == 3 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == String.class && method.getParameterTypes()[2] == byte[].class) { - message = - NatsMessageWritableHeaders.create((String) args[0], (String) args[1], (byte[]) args[2]); + subject = (String) args[0]; + replyTo = (String) args[1]; + body = (byte[]) args[2]; } else if (method.getParameterCount() == 4 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == String.class && method.getParameterTypes()[2] == Headers.class && method.getParameterTypes()[3] == byte[].class) { - message = - NatsMessageWritableHeaders.create( - (String) args[0], (String) args[1], (Headers) args[2], (byte[]) args[3]); + subject = (String) args[0]; + replyTo = (String) args[1]; + headers = (Headers) args[2]; + body = (byte[]) args[3]; } else if (method.getParameterCount() == 1 && method.getParameterTypes()[0] == Message.class) { - message = NatsMessageWritableHeaders.create((Message) args[0]); + subject = ((Message) args[0]).getSubject(); + replyTo = ((Message) args[0]).getReplyTo(); + headers = ((Message) args[0]).getHeaders(); + body = ((Message) args[0]).getData(); } Context parentContext = Context.current(); + headers = NatsMessageWritableHeaders.create(headers); + NatsRequest natsRequest = null; - if (message != null) { - natsRequest = NatsRequest.create(delegate, message); + if (subject != null) { + natsRequest = NatsRequest.create(delegate, subject, replyTo, headers, body); } if (natsRequest == null || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { @@ -135,7 +146,7 @@ private void publish(Method method, Object[] args) throws Throwable { Context context = producerInstrumenter.start(parentContext, natsRequest); try (Scope ignored = context.makeCurrent()) { - delegate.publish(message); + delegate.publish(subject, replyTo, headers, body); } finally { producerInstrumenter.end(context, natsRequest, null, null); } @@ -146,31 +157,38 @@ private void publish(Method method, Object[] args) throws Throwable { // InterruptedException; // Message request(Message message, Duration timeout) throws InterruptedException; private Message request(Method method, Object[] args) throws Throwable { - Message message = null; - NatsRequest natsRequest = null; + String subject = null; + Headers headers = null; + byte[] body = null; Duration timeout = null; if (method.getParameterCount() == 3 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == byte[].class) { - message = NatsMessageWritableHeaders.create((String) args[0], (byte[]) args[1]); + subject = (String) args[0]; + body = (byte[]) args[1]; timeout = (Duration) args[2]; } else if (method.getParameterCount() == 4 && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == Headers.class && method.getParameterTypes()[2] == byte[].class) { - message = - NatsMessageWritableHeaders.create((String) args[0], (Headers) args[1], (byte[]) args[2]); + subject = (String) args[0]; + headers = (Headers) args[1]; + body = (byte[]) args[2]; timeout = (Duration) args[3]; } else if (method.getParameterCount() == 2 && method.getParameterTypes()[0] == Message.class) { - message = NatsMessageWritableHeaders.create((Message) args[0]); + subject = ((Message) args[0]).getSubject(); + headers = ((Message) args[0]).getHeaders(); + body = ((Message) args[0]).getData(); timeout = (Duration) args[1]; } Context parentContext = Context.current(); + headers = NatsMessageWritableHeaders.create(headers); + NatsRequest natsRequest = null; - if (message != null) { - natsRequest = NatsRequest.create(delegate, message); + if (subject != null) { + natsRequest = NatsRequest.create(delegate, subject, null, headers, body); } if (timeout == null @@ -182,16 +200,20 @@ private Message request(Method method, Object[] args) throws Throwable { Context context = producerInstrumenter.start(parentContext, natsRequest); NatsRequest response = null; + Throwable throwable = null; try (Scope ignored = context.makeCurrent()) { - Message result = delegate.request(message, timeout); + Message result = delegate.request(subject, headers, body, timeout); if (result != null) { response = NatsRequest.create(delegate, result); } return result; + } catch (InterruptedException t) { + throwable = t; + throw t; } finally { - producerInstrumenter.end(context, natsRequest, response, null); + producerInstrumenter.end(context, natsRequest, response, throwable); } } @@ -204,45 +226,56 @@ private Message request(Method method, Object[] args) throws Throwable { // CompletableFuture requestWithTimeout(Message message, Duration timeout); @SuppressWarnings("unchecked") private CompletableFuture requestAsync(Method method, Object[] args) throws Throwable { - Message message = null; - NatsRequest natsRequest = null; + String subject = null; + Headers headers = null; + byte[] body = null; Duration timeout = null; if ((method.getParameterCount() == 2) && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == byte[].class) { - message = NatsMessageWritableHeaders.create((String) args[0], (byte[]) args[1]); + subject = (String) args[0]; + body = (byte[]) args[1]; } else if ((method.getParameterCount() == 3) && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == byte[].class) { - message = NatsMessageWritableHeaders.create((String) args[0], (byte[]) args[1]); + subject = (String) args[0]; + body = (byte[]) args[1]; timeout = (Duration) args[2]; } else if ((method.getParameterCount() == 3) && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == Headers.class && method.getParameterTypes()[2] == byte[].class) { - message = - NatsMessageWritableHeaders.create((String) args[0], (Headers) args[1], (byte[]) args[2]); + subject = (String) args[0]; + headers = (Headers) args[1]; + body = (byte[]) args[2]; } else if ((method.getParameterCount() == 4) && method.getParameterTypes()[0] == String.class && method.getParameterTypes()[1] == Headers.class && method.getParameterTypes()[2] == byte[].class) { - message = - NatsMessageWritableHeaders.create((String) args[0], (Headers) args[1], (byte[]) args[2]); + subject = (String) args[0]; + headers = (Headers) args[1]; + body = (byte[]) args[2]; timeout = (Duration) args[3]; } else if ((method.getParameterCount() == 1) && method.getParameterTypes()[0] == Message.class) { - message = NatsMessageWritableHeaders.create((Message) args[0]); + subject = ((Message) args[0]).getSubject(); + headers = ((Message) args[0]).getHeaders(); + body = ((Message) args[0]).getData(); } else if ((method.getParameterCount() == 2) && method.getParameterTypes()[0] == Message.class) { - message = NatsMessageWritableHeaders.create((Message) args[0]); + subject = ((Message) args[0]).getSubject(); + headers = ((Message) args[0]).getHeaders(); + body = ((Message) args[0]).getData(); timeout = (Duration) args[1]; } Context parentContext = Context.current(); + headers = NatsMessageWritableHeaders.create(headers); + NatsRequest natsRequest = null; - if (message != null) { - natsRequest = NatsRequest.create(delegate, message); + if (subject != null) { + natsRequest = NatsRequest.create(delegate, subject, null, headers, body); } if (natsRequest == null || !producerInstrumenter.shouldStart(parentContext, natsRequest)) { @@ -254,9 +287,9 @@ private CompletableFuture requestAsync(Method method, Object[] args) th CompletableFuture future; if (timeout != null) { - future = delegate.requestWithTimeout(message, timeout); + future = delegate.requestWithTimeout(subject, headers, body, timeout); } else { - future = delegate.request(message); + future = delegate.request(subject, headers, body); } return future.whenComplete( diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java index 123a7a270f2d..303335627e01 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsInstrumenterFactory.java @@ -11,8 +11,6 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; -import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; -import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor; import java.util.List; /** @@ -38,9 +36,7 @@ public static Instrumenter createProducerInstrumenter( } public static Instrumenter createConsumerProcessInstrumenter( - OpenTelemetry openTelemetry, - boolean messagingReceiveInstrumentationEnabled, - List capturedHeaders) { + OpenTelemetry openTelemetry, List capturedHeaders) { InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, @@ -53,15 +49,7 @@ public static Instrumenter createConsumerProcessInstrumenter( .setCapturedHeaders(capturedHeaders) .build()); - if (messagingReceiveInstrumentationEnabled) { - builder.addSpanLinksExtractor( - new PropagatorBasedSpanLinksExtractor<>( - openTelemetry.getPropagators().getTextMapPropagator(), - NatsRequestTextMapGetter.INSTANCE)); - return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); - } else { - return builder.buildConsumerInstrumenter(NatsRequestTextMapGetter.INSTANCE); - } + return builder.buildConsumerInstrumenter(NatsRequestTextMapGetter.INSTANCE); } private NatsInstrumenterFactory() {} diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java index 67861a00cc44..ff35e1f866c5 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsMessageWritableHeaders.java @@ -5,9 +5,7 @@ package io.opentelemetry.instrumentation.nats.v2_17.internal; -import io.nats.client.Message; import io.nats.client.impl.Headers; -import io.nats.client.impl.NatsMessage; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -15,49 +13,12 @@ */ public final class NatsMessageWritableHeaders { - public static Message create(String subject, byte[] body) { - return NatsMessage.builder().subject(subject).headers(new Headers()).data(body).build(); - } - - public static Message create(String subject, String replyTo, byte[] body) { - return NatsMessage.builder() - .subject(subject) - .replyTo(replyTo) - .headers(new Headers()) - .data(body) - .build(); - } - - public static Message create(String subject, Headers headers, byte[] body) { - if (headers == null || headers.isReadOnly()) { - headers = new Headers(headers); - } - return NatsMessage.builder().subject(subject).headers(headers).data(body).build(); - } - - public static Message create(String subject, String replyTo, Headers headers, byte[] body) { + public static Headers create(Headers headers) { if (headers == null || headers.isReadOnly()) { - headers = new Headers(headers); + return new Headers(headers); } - return NatsMessage.builder() - .subject(subject) - .replyTo(replyTo) - .headers(headers) - .data(body) - .build(); - } - public static Message create(Message message) { - if (message instanceof NatsMessage - && (!message.hasHeaders() || message.getHeaders().isReadOnly())) { - return NatsMessage.builder() - .subject(message.getSubject()) - .replyTo(message.getReplyTo()) - .headers(new Headers(message.getHeaders())) - .data(message.getData()) - .build(); - } - return message; + return headers; } private NatsMessageWritableHeaders() {} diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java index 63a73a0d2ead..0648e2490ec4 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequest.java @@ -18,15 +18,25 @@ @AutoValue public abstract class NatsRequest { + public static NatsRequest create( + Connection connection, String subject, String replyTo, Headers headers, byte[] body) { + return new AutoValue_NatsRequest( + replyTo, + connection.getServerInfo().getClientId(), + subject, + headers, + getDataSize(body), + connection.getOptions().getInboxPrefix()); + } + public static NatsRequest create(Connection connection, Message message) { return new AutoValue_NatsRequest( message.getReplyTo(), - (message.getConnection() == null ? connection : message.getConnection()) - .getServerInfo() - .getClientId(), + connection.getServerInfo().getClientId(), message.getSubject(), message.getHeaders(), - getDataSize(message.getData())); + getDataSize(message.getData()), + connection.getOptions().getInboxPrefix()); } @Nullable @@ -44,4 +54,6 @@ public static NatsRequest create(Connection connection, Message message) { private static long getDataSize(byte[] data) { return data == null ? 0 : data.length; } + + public abstract String getInboxPrefix(); } diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java index 45a7fb187c0d..9674cf91c5b1 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java @@ -5,12 +5,88 @@ package io.opentelemetry.instrumentation.nats.v2_17.internal; +import io.nats.client.impl.Headers; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; -class NatsRequestMessagingAttributesGetter { +enum NatsRequestMessagingAttributesGetter + implements MessagingAttributesGetter { + INSTANCE; - static final MessagingAttributesGetter INSTANCE = - NatsRequestMessagingAttributesGetterFactory.create(); + @Nullable + @Override + public String getSystem(NatsRequest request) { + return "nats"; + } - private NatsRequestMessagingAttributesGetter() {} + @Nullable + @Override + public String getDestination(NatsRequest request) { + return request.getSubject(); + } + + @Nullable + @Override + public String getDestinationTemplate(NatsRequest request) { + if (isTemporaryDestination(request)) { + return request.getInboxPrefix(); + } + return null; + } + + @Override + public boolean isTemporaryDestination(NatsRequest request) { + return request.getSubject().startsWith(request.getInboxPrefix()); + } + + @Override + public boolean isAnonymousDestination(NatsRequest request) { + return false; + } + + @Nullable + @Override + public String getConversationId(NatsRequest request) { + return null; + } + + @Nullable + @Override + public Long getMessageBodySize(NatsRequest request) { + return request.getDataSize(); + } + + @Nullable + @Override + public Long getMessageEnvelopeSize(NatsRequest request) { + return null; + } + + @Nullable + @Override + public String getMessageId(NatsRequest request, @Nullable Object unused) { + return null; + } + + @Nullable + @Override + public String getClientId(NatsRequest request) { + return String.valueOf(request.getClientId()); + } + + @Nullable + @Override + public Long getBatchMessageCount(NatsRequest request, @Nullable Object unused) { + return null; + } + + @Override + public List getMessageHeader(NatsRequest request, String name) { + Headers headers = request.getHeaders(); + return headers == null || headers.get(name) == null + ? Collections.emptyList() + : headers.get(name); + } } diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetterFactory.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetterFactory.java deleted file mode 100644 index a34c6c2e71ba..000000000000 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetterFactory.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.nats.v2_17.internal; - -import io.nats.client.impl.Headers; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; -import java.util.Collections; -import java.util.List; -import javax.annotation.Nullable; - -class NatsRequestMessagingAttributesGetterFactory { - - static MessagingAttributesGetter create() { - return new MessagingAttributesGetter() { - @Nullable - @Override - public String getSystem(NatsRequest request) { - return "nats"; - } - - @Nullable - @Override - public String getDestination(NatsRequest request) { - return request.getSubject(); - } - - @Nullable - @Override - public String getDestinationTemplate(NatsRequest request) { - return null; - } - - @Override - public boolean isTemporaryDestination(NatsRequest request) { - return false; - } - - @Override - public boolean isAnonymousDestination(NatsRequest request) { - return false; - } - - @Nullable - @Override - public String getConversationId(NatsRequest request) { - return null; - } - - @Nullable - @Override - public Long getMessageBodySize(NatsRequest request) { - return request.getDataSize(); - } - - @Nullable - @Override - public Long getMessageEnvelopeSize(NatsRequest request) { - return null; - } - - @Nullable - @Override - public String getMessageId(NatsRequest request, @Nullable RESPONSE unused) { - return null; - } - - @Nullable - @Override - public String getClientId(NatsRequest request) { - return String.valueOf(request.getClientId()); - } - - @Nullable - @Override - public Long getBatchMessageCount(NatsRequest request, @Nullable RESPONSE unused) { - return null; - } - - @Override - public List getMessageHeader(NatsRequest request, String name) { - Headers headers = request.getHeaders(); - return headers == null || headers.get(name) == null - ? Collections.emptyList() - : headers.get(name); - } - }; - } - - private NatsRequestMessagingAttributesGetterFactory() {} -} diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java index 5f1c9aa0ba5c..bb6f3cacfd5c 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java @@ -26,7 +26,6 @@ protected InstrumentationExtension testing() { static void beforeAll() { connection = NatsTelemetry.builder(testing.getOpenTelemetry()) - .setMessagingReceiveInstrumentationEnabled(true) .setCapturedHeaders(singletonList("captured-header")) .build() .wrap(connection); diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java index aadbad1a7789..06720e8f1245 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java @@ -27,8 +27,7 @@ protected InstrumentationExtension testing() { static void beforeAll() throws IOException, InterruptedException { NatsTelemetry telemetry = NatsTelemetry.create(testing.getOpenTelemetry()); connection = - telemetry.wrap( - Nats.connect( - telemetry.wrap(Options.builder().server(connection.getConnectedUrl())).build())); + telemetry.newConnection( + Options.builder().server(connection.getConnectedUrl()).build(), Nats::connect); } } diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java index 5ec1af12c86c..f3a22354e192 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java @@ -8,17 +8,13 @@ import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; import io.nats.client.Dispatcher; import io.nats.client.Message; import io.nats.client.impl.Headers; import io.nats.client.impl.NatsMessage; import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.context.Context; -import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -34,33 +30,24 @@ void beforeEach() { } @Test - void testMessagingReceiveAndCapturedHeaders() { + void testCapturedHeaders() { // given Dispatcher dispatcher = connection.createDispatcher(msg -> {}).subscribe("sub"); // when Headers headers = new Headers(); headers.put("captured-header", "value"); - String traceId = - testing() - .runWithSpan( - "parent", - () -> { - Message message = - NatsMessage.builder().subject("sub").headers(headers).data("x").build(); - connection.publish(message); - return Span.fromContext(Context.current()).getSpanContext().getTraceId(); - }); + testing() + .runWithSpan( + "parent", + () -> { + Message message = + NatsMessage.builder().subject("sub").headers(headers).data("x").build(); + connection.publish(message); + }); connection.closeDispatcher(dispatcher); // then - AttributeAssertion[] headerAssert = - new AttributeAssertion[] { - equalTo( - AttributeKey.stringArrayKey("messaging.header.captured_header"), - singletonList("value")) - }; - testing() .waitAndAssertTraces( trace -> @@ -70,20 +57,17 @@ void testMessagingReceiveAndCapturedHeaders() { span.hasName("sub publish") .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - messagingAttributes("publish", "sub", clientId, headerAssert))), - trace -> - trace.hasSpansSatisfyingExactly( + messagingAttributes( + "publish", + "sub", + clientId, + equalTo( + AttributeKey.stringArrayKey( + "messaging.header.captured_header"), + singletonList("value")))), span -> span.hasName("sub process") .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasLinksSatisfying( - links -> - assertThat(links) - .singleElement() - .satisfies( - link -> - assertThat(link.getSpanContext().getTraceId()) - .isEqualTo(traceId))))); + .hasParent(trace.getSpan(1)))); } } diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java index 160b30083c19..9599bb62cb5f 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java @@ -7,6 +7,8 @@ import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.assertTraceparentHeader; import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_TEMPORARY; import static java.util.Arrays.asList; import io.nats.client.Dispatcher; @@ -20,7 +22,6 @@ import java.util.List; import java.util.concurrent.CancellationException; import java.util.function.Consumer; -import org.assertj.core.api.Condition; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -284,26 +285,20 @@ private void assertPublishReceiveSpansSameTrace() { .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(1)), span -> - span.has( - new Condition<>( - data -> - data.getName().startsWith("_INBOX.") - && data.getName().endsWith(" publish"), - "Name condition")) + span.hasName("(temporary) publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(2)), // publisher: process span -> - span.has( - new Condition<>( - data -> - data.getName().startsWith("_INBOX.") - && data.getName().endsWith(" process"), - "Name condition")) + span.hasName("(temporary) process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(3)) .hasAttributesSatisfyingExactly( - messagingAttributes("process", "_INBOX.", clientId)))); + messagingAttributes( + "process", + "(temporary)", + clientId, + equalTo(MESSAGING_DESTINATION_TEMPORARY, true))))); trace.hasSpansSatisfyingExactly(asserts); }); diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java index fed860e6c70b..36898e2f4e8c 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java @@ -6,7 +6,6 @@ package io.opentelemetry.instrumentation.nats.v2_17; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; @@ -22,6 +21,11 @@ @SuppressWarnings("deprecation") // using deprecated semconv public class NatsInstrumentationTestHelper { + public static AttributeAssertion[] messagingAttributes( + String operation, String subject, int clientId, AttributeAssertion other) { + return messagingAttributes(operation, subject, clientId, new AttributeAssertion[] {other}); + } + public static AttributeAssertion[] messagingAttributes( String operation, String subject, int clientId, AttributeAssertion[] other) { AttributeAssertion[] standard = messagingAttributes(operation, subject, clientId); @@ -33,15 +37,10 @@ public static AttributeAssertion[] messagingAttributes( public static AttributeAssertion[] messagingAttributes( String operation, String subject, int clientId) { - AttributeAssertion destinationName = equalTo(MESSAGING_DESTINATION_NAME, subject); - if (subject.startsWith("_INBOX.")) { - destinationName = satisfies(MESSAGING_DESTINATION_NAME, name -> name.startsWith("_INBOX.")); - } - return new AttributeAssertion[] { equalTo(MESSAGING_OPERATION, operation), equalTo(MESSAGING_SYSTEM, "nats"), - destinationName, + equalTo(MESSAGING_DESTINATION_NAME, subject), equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), equalTo(AttributeKey.stringKey("messaging.client_id"), String.valueOf(clientId)) }; From a2d15b45a726f0a0e99762254631c37eac56838f Mon Sep 17 00:00:00 2001 From: Alix Date: Sat, 6 Sep 2025 13:13:37 +0200 Subject: [PATCH 29/34] review fixes --- docs/supported-libraries.md | 2 +- .../nats/nats-2.17/javaagent/build.gradle.kts | 2 +- .../ConnectionPublishInstrumentation.java | 8 ++++ .../ConnectionRequestInstrumentation.java | 19 +++++++++- .../nats/nats-2.17/library/README.md | 19 ++++------ .../nats/nats-2.17/library/build.gradle.kts | 2 +- .../nats/v2_17/NatsTelemetry.java | 19 ++++++++-- .../nats/v2_17/OpenTelemetryConnection.java | 38 +++++++++++++------ .../nats/v2_17/OpenTelemetryDispatcher.java | 5 +-- .../v2_17/OpenTelemetryMessageHandler.java | 10 ++--- instrumentation/nats/nats-2.17/metadata.yaml | 6 --- .../nats/nats-2.17/testing/build.gradle.kts | 2 +- 12 files changed, 86 insertions(+), 46 deletions(-) diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 94f1a07bee07..b1b0a7ca90c2 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -105,7 +105,7 @@ These are the supported libraries and frameworks: | [Micrometer](https://micrometer.io/) | 1.5+ (disabled by default) | [opentelemetry-micrometer-1.5](../instrumentation/micrometer/micrometer-1.5/library) | none | | [MongoDB Driver](https://mongodb.github.io/mongo-java-driver/) | 3.1+ | [opentelemetry-mongo-3.1](../instrumentation/mongo/mongo-3.1/library) | [Database Client Spans], [Database Client Metrics] [6] | | [MyBatis](https://mybatis.org/mybatis-3/) | 3.2+ | N/A | none | -| [NATS](https://github.com/nats-io/nats.java) | 2.17.7+ | [nats-2.17](../instrumentation/nats/nats-2.17/library) | [Messaging Spans] | +| [NATS](https://github.com/nats-io/nats.java) | 2.17.2+ | [nats-2.17](../instrumentation/nats/nats-2.17/library) | [Messaging Spans] | | [Netty HTTP codec [5]](https://github.com/netty/netty) | 3.8+ | [opentelemetry-netty-4.1](../instrumentation/netty/netty-4.1/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] | | [OpenAI Java SDK](https://github.com/openai/openai-java) | 1.1+ | [openai-java-1.1](../instrumentation/openai/openai-java-1.1/library) | [GenAI Client Spans], [GenAI Client Metrics] | | [OpenSearch Rest Client](https://github.com/opensearch-project/opensearch-java) | 1.0+ | | [Database Client Spans], [Database Client Metrics] [6] | diff --git a/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts index 944efe110d2f..aaa0d9c3e45f 100644 --- a/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts @@ -16,7 +16,7 @@ muzzle { } dependencies { - library("io.nats:jnats:2.17.7") + library("io.nats:jnats:2.17.2") implementation(project(":instrumentation:nats:nats-2.17:library")) testImplementation(project(":instrumentation:nats:nats-2.17:testing")) diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java index 31a86abb80d4..3dabbac45c2d 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java @@ -81,6 +81,7 @@ public static boolean onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, @Advice.Argument(1) byte[] body) { + // call the instrumented publish method connection.publish(subject, null, null, body); return true; } @@ -94,6 +95,7 @@ public static boolean onEnter( @Advice.Argument(0) String subject, @Advice.Argument(1) Headers headers, @Advice.Argument(2) byte[] body) { + // call the instrumented publish method connection.publish(subject, null, headers, body); return true; } @@ -107,6 +109,7 @@ public static boolean onEnter( @Advice.Argument(0) String subject, @Advice.Argument(1) String replyTo, @Advice.Argument(2) byte[] body) { + // call the instrumented publish method connection.publish(subject, replyTo, null, body); return true; } @@ -158,6 +161,11 @@ public static class PublishMessageAdvice { @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) public static boolean onEnter( @Advice.This Connection connection, @Advice.Argument(0) Message message) { + if (message == null) { + return false; + } + + // call the instrumented publish method connection.publish( message.getSubject(), message.getReplyTo(), message.getHeaders(), message.getData()); return true; diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java index 016054aac1f4..8dc3640bf43a 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java @@ -129,6 +129,7 @@ public static Message onEnter( @Advice.Argument(2) Duration timeout, @Advice.Local("message") Message message) throws InterruptedException { + // call the instrumented request method message = connection.request(subject, null, body, timeout); return message; } @@ -151,7 +152,6 @@ public static void onEnter( @Advice.Argument(0) String subject, @Advice.Argument(value = 1, readOnly = false) Headers headers, @Advice.Argument(2) byte[] body, - @Advice.Argument(3) Duration timeout, @Advice.Local("otelContext") Context otelContext, @Advice.Local("otelScope") Scope otelScope, @Advice.Local("natsRequest") NatsRequest natsRequest) { @@ -199,6 +199,11 @@ public static Message onEnter( @Advice.Argument(1) Duration timeout, @Advice.Local("response") Message response) throws InterruptedException { + if (request == null) { + return null; + } + + // call the instrumented request method response = connection.request( request.getSubject(), request.getHeaders(), request.getData(), timeout); @@ -223,6 +228,7 @@ public static CompletableFuture onEnter( @Advice.Argument(0) String subject, @Advice.Argument(1) byte[] body, @Advice.Local("future") CompletableFuture future) { + // call the instrumented request method future = connection.request(subject, null, body); return future; } @@ -294,6 +300,11 @@ public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) Message message, @Advice.Local("future") CompletableFuture future) { + if (message == null) { + return null; + } + + // call the instrumented request method future = connection.request(message.getSubject(), message.getHeaders(), message.getData()); return future; } @@ -317,6 +328,7 @@ public static CompletableFuture onEnter( @Advice.Argument(1) byte[] body, @Advice.Argument(2) Duration timeout, @Advice.Local("future") CompletableFuture future) { + // call the instrumented requestWithTimeout method future = connection.requestWithTimeout(subject, null, body, timeout); return future; } @@ -389,6 +401,11 @@ public static CompletableFuture onEnter( @Advice.Argument(value = 0, readOnly = false) Message message, @Advice.Argument(1) Duration timeout, @Advice.Local("future") CompletableFuture future) { + if (message == null) { + return null; + } + + // call the instrumented requestWithTimeout method future = connection.requestWithTimeout( message.getSubject(), message.getHeaders(), message.getData(), timeout); diff --git a/instrumentation/nats/nats-2.17/library/README.md b/instrumentation/nats/nats-2.17/library/README.md index c97ea40de88c..5bfdfe8c6d96 100644 --- a/instrumentation/nats/nats-2.17/library/README.md +++ b/instrumentation/nats/nats-2.17/library/README.md @@ -31,7 +31,7 @@ implementation("io.opentelemetry.instrumentation:opentelemetry-nats-2.17:OPENTEL The instrumentation library provides the class `NatsTelemetry` that has a builder method and allows the creation of an instance of the `Connection` to provide -OpenTelemetry-based spans and context propagation: +OpenTelemetry-based instrumentation: ```java import io.nats.client.Connection; @@ -39,6 +39,7 @@ import io.nats.client.Nats; import io.nats.client.Options; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.nats.v2_17.NatsTelemetry; +import java.io.IOException; public class OpenTelemetryNatsConnection { @@ -50,19 +51,13 @@ public class OpenTelemetryNatsConnection { this.telemetry = NatsTelemetry.create(openTelemetry); } - // creates a new connection with opentelemetry instrumentation. - // This will *not* instrument the connection's main inbox - // if you're using the default NatsConnection implementation - public Connection wrap(Connection connection) { - return telemetry.wrap(connection); + public Connection newConnection() throws IOException, InterruptedException { + return newConnection(Options.builder()); } - // prefer wrapping the Options.Builder to get the full instrumentation - // when using the default NatsConnection implementation - public Connection create(Options.Builder builder) throws IOException, InterruptedException { - Options options = telemetry.configureDispatcher(builder).build(); - Connection connection = Nats.connect(options); - return wrap(connection); + public Connection newConnection(Options.Builder options) throws IOException, InterruptedException { + return telemetry.newConnection(options.build(), Nats::connect); } + } ``` diff --git a/instrumentation/nats/nats-2.17/library/build.gradle.kts b/instrumentation/nats/nats-2.17/library/build.gradle.kts index de556150aeef..dbee7d91c511 100644 --- a/instrumentation/nats/nats-2.17/library/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/library/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } dependencies { - library("io.nats:jnats:2.17.7") + library("io.nats:jnats:2.17.2") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java index fda29d445eea..0c0d7a2aaec8 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java @@ -34,12 +34,23 @@ public NatsTelemetry( this.consumerProcessInstrumenter = consumerProcessInstrumenter; } - Connection wrap(Connection connection) { + /** + * Returns a {@link Connection} with telemetry instrumentation. + * + *

This does *not* monitor the main inbox of the default dispatcher. {@link + * #configure(Options.Builder)} {@link #newConnection(Options.Builder, ConnectionFactory)} {@link + * #newConnection(Options, ConnectionFactory)} + */ + public Connection wrap(Connection connection) { return OpenTelemetryConnection.wrap( connection, producerInstrumenter, consumerProcessInstrumenter); } - Options.Builder configure(Options.Builder options) { + /** + * Returns a {@link Options.Builder} with the main inbox from the default dispatcher monitored + * with telemetry instrumentation. + */ + public Options.Builder configure(Options.Builder options) { DispatcherFactory factory = options.build().getDispatcherFactory(); if (factory == null) { @@ -50,13 +61,13 @@ Options.Builder configure(Options.Builder options) { new OpenTelemetryDispatcherFactory(factory, consumerProcessInstrumenter)); } - /** Returns a {@link Connection} with messaging spans instrumentation. */ + /** Returns a {@link Connection} with telemetry instrumentation. */ public Connection newConnection(Options options, ConnectionFactory connectionFactory) throws IOException, InterruptedException { return wrap(connectionFactory.create(configure(new Options.Builder(options)).build())); } - /** Returns a {@link Connection} with messaging spans instrumentation. */ + /** Returns a {@link Connection} with telemetry instrumentation. */ public Connection newConnection( Options.Builder builder, ConnectionFactory connectionFactory) throws IOException, InterruptedException { diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java index 051a707694b5..6edc037cba5b 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java @@ -124,7 +124,9 @@ private void publish(Method method, Object[] args) throws Throwable { replyTo = (String) args[1]; headers = (Headers) args[2]; body = (byte[]) args[3]; - } else if (method.getParameterCount() == 1 && method.getParameterTypes()[0] == Message.class) { + } else if (method.getParameterCount() == 1 + && method.getParameterTypes()[0] == Message.class + && args[0] != null) { subject = ((Message) args[0]).getSubject(); replyTo = ((Message) args[0]).getReplyTo(); headers = ((Message) args[0]).getHeaders(); @@ -145,10 +147,14 @@ private void publish(Method method, Object[] args) throws Throwable { } Context context = producerInstrumenter.start(parentContext, natsRequest); + Throwable throwable = null; try (Scope ignored = context.makeCurrent()) { delegate.publish(subject, replyTo, headers, body); + } catch (Throwable t) { + throwable = t; + throw t; } finally { - producerInstrumenter.end(context, natsRequest, null, null); + producerInstrumenter.end(context, natsRequest, null, throwable); } } @@ -156,6 +162,7 @@ private void publish(Method method, Object[] args) throws Throwable { // Message request(String subject, Headers headers, byte[] body, Duration timeout) throws // InterruptedException; // Message request(Message message, Duration timeout) throws InterruptedException; + @SuppressWarnings("InterruptedExceptionSwallowed") private Message request(Method method, Object[] args) throws Throwable { String subject = null; Headers headers = null; @@ -176,7 +183,9 @@ private Message request(Method method, Object[] args) throws Throwable { headers = (Headers) args[1]; body = (byte[]) args[2]; timeout = (Duration) args[3]; - } else if (method.getParameterCount() == 2 && method.getParameterTypes()[0] == Message.class) { + } else if (method.getParameterCount() == 2 + && method.getParameterTypes()[0] == Message.class + && args[0] != null) { subject = ((Message) args[0]).getSubject(); headers = ((Message) args[0]).getHeaders(); body = ((Message) args[0]).getData(); @@ -199,8 +208,8 @@ private Message request(Method method, Object[] args) throws Throwable { Context context = producerInstrumenter.start(parentContext, natsRequest); NatsRequest response = null; - Throwable throwable = null; + try (Scope ignored = context.makeCurrent()) { Message result = delegate.request(subject, headers, body, timeout); @@ -209,7 +218,7 @@ private Message request(Method method, Object[] args) throws Throwable { } return result; - } catch (InterruptedException t) { + } catch (Throwable t) { throwable = t; throw t; } finally { @@ -258,12 +267,14 @@ private CompletableFuture requestAsync(Method method, Object[] args) th body = (byte[]) args[2]; timeout = (Duration) args[3]; } else if ((method.getParameterCount() == 1) - && method.getParameterTypes()[0] == Message.class) { + && method.getParameterTypes()[0] == Message.class + && args[0] != null) { subject = ((Message) args[0]).getSubject(); headers = ((Message) args[0]).getHeaders(); body = ((Message) args[0]).getData(); } else if ((method.getParameterCount() == 2) - && method.getParameterTypes()[0] == Message.class) { + && method.getParameterTypes()[0] == Message.class + && args[0] != null) { subject = ((Message) args[0]).getSubject(); headers = ((Message) args[0]).getHeaders(); body = ((Message) args[0]).getData(); @@ -286,10 +297,15 @@ private CompletableFuture requestAsync(Method method, Object[] args) th Context context = producerInstrumenter.start(parentContext, notNullNatsRequest); CompletableFuture future; - if (timeout != null) { - future = delegate.requestWithTimeout(subject, headers, body, timeout); - } else { - future = delegate.request(subject, headers, body); + try { + if (timeout != null) { + future = delegate.requestWithTimeout(subject, headers, body, timeout); + } else { + future = delegate.request(subject, headers, body); + } + } catch (Throwable t) { + producerInstrumenter.end(context, notNullNatsRequest, null, t); + throw t; } return future.whenComplete( diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java index 096ce1f51f93..fc12dffb77c3 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java @@ -48,8 +48,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl } } - private static Object invokeProxyMethod(Method method, Object target, Object[] args) - throws Throwable { + private static Object invokeMethod(Method method, Object target, Object[] args) throws Throwable { try { return method.invoke(target, args); } catch (InvocationTargetException exception) { @@ -69,7 +68,7 @@ private Subscription subscribe(Method method, Object[] args) throws Throwable { new OpenTelemetryMessageHandler((MessageHandler) args[2], consumerProcessInstrumenter); } - return (Subscription) invokeProxyMethod(method, delegate, args); + return (Subscription) invokeMethod(method, delegate, args); } Dispatcher getDelegate() { diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java index 26b5c2c9932e..c4a16a939631 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java @@ -38,15 +38,15 @@ public void onMessage(Message message) throws InterruptedException { } Context processContext = consumerProcessInstrumenter.start(parentContext, natsRequest); - InterruptedException exception = null; + Throwable error = null; try (Scope ignored = processContext.makeCurrent()) { delegate.onMessage(message); - } catch (InterruptedException e) { - exception = e; - throw e; + } catch (Throwable t) { + error = t; + throw t; } finally { - consumerProcessInstrumenter.end(processContext, natsRequest, null, exception); + consumerProcessInstrumenter.end(processContext, natsRequest, null, error); } } } diff --git a/instrumentation/nats/nats-2.17/metadata.yaml b/instrumentation/nats/nats-2.17/metadata.yaml index dbbd9f50496a..1a4d1361c7ef 100644 --- a/instrumentation/nats/nats-2.17/metadata.yaml +++ b/instrumentation/nats/nats-2.17/metadata.yaml @@ -1,12 +1,6 @@ disabled_by_default: false description: This instrumentation provides messaging spans for NATS configurations: - - name: otel.instrumentation.messaging.experimental.receive-telemetry.enabled - description: > - Enables experimental receive telemetry, which will cause consumers to start a new trace, with - only a span link connecting it to the producer trace. - type: boolean - default: false - name: otel.instrumentation.messaging.experimental.capture-headers description: Allows configuring headers to capture as span attributes. type: list diff --git a/instrumentation/nats/nats-2.17/testing/build.gradle.kts b/instrumentation/nats/nats-2.17/testing/build.gradle.kts index 74c2181d2ec4..18ea3fbf6a5b 100644 --- a/instrumentation/nats/nats-2.17/testing/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/testing/build.gradle.kts @@ -5,7 +5,7 @@ plugins { dependencies { api(project(":testing-common")) - compileOnly("io.nats:jnats:2.17.7") + compileOnly("io.nats:jnats:2.17.2") implementation("org.testcontainers:testcontainers") } From fec879b4d502d7848061b9925e68abe3738baee9 Mon Sep 17 00:00:00 2001 From: Alix Date: Fri, 12 Sep 2025 17:26:16 +0200 Subject: [PATCH 30/34] reviews --- docs/supported-libraries.md | 2 +- .../nats/nats-2.17/javaagent/README.md | 3 - .../nats/nats-2.17/javaagent/build.gradle.kts | 5 +- .../ConnectionRequestInstrumentation.java | 14 +-- ...questTest.java => NatsDispatcherTest.java} | 4 +- ...ishTest.java => NatsExperimentalTest.java} | 4 +- ...spatcherTest.java => NatsPublishTest.java} | 4 +- ...rimentalTest.java => NatsRequestTest.java} | 4 +- .../nats/nats-2.17/library/README.md | 2 +- .../nats/v2_17/NatsTelemetry.java | 18 ++- .../nats/v2_17/NatsTelemetryBuilder.java | 7 ++ ...blishTest.java => NatsDispatcherTest.java} | 2 +- ...talTest.java => NatsExperimentalTest.java} | 2 +- ...spatcherTest.java => NatsPublishTest.java} | 2 +- ...nRequestTest.java => NatsRequestTest.java} | 2 +- ...t.java => AbstractNatsDispatcherTest.java} | 116 +++++++++++------- ...java => AbstractNatsExperimentalTest.java} | 5 +- ...Test.java => AbstractNatsPublishTest.java} | 7 +- ...Test.java => AbstractNatsRequestTest.java} | 7 +- ...ntationTest.java => AbstractNatsTest.java} | 2 +- ...ionTestHelper.java => NatsTestHelper.java} | 4 +- 21 files changed, 121 insertions(+), 95 deletions(-) delete mode 100644 instrumentation/nats/nats-2.17/javaagent/README.md rename instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/{NatsInstrumentationRequestTest.java => NatsDispatcherTest.java} (76%) rename instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/{NatsInstrumentationPublishTest.java => NatsExperimentalTest.java} (76%) rename instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/{NatsInstrumentationDispatcherTest.java => NatsPublishTest.java} (75%) rename instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/{NatsInstrumentationExperimentalTest.java => NatsRequestTest.java} (74%) rename instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/{NatsInstrumentationPublishTest.java => NatsDispatcherTest.java} (89%) rename instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/{NatsInstrumentationExperimentalTest.java => NatsExperimentalTest.java} (90%) rename instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/{NatsInstrumentationDispatcherTest.java => NatsPublishTest.java} (88%) rename instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/{NatsInstrumentationRequestTest.java => NatsRequestTest.java} (92%) rename instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/{AbstractNatsInstrumentationDispatcherTest.java => AbstractNatsDispatcherTest.java} (50%) rename instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/{AbstractNatsInstrumentationExperimentalTest.java => AbstractNatsExperimentalTest.java} (91%) rename instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/{AbstractNatsInstrumentationPublishTest.java => AbstractNatsPublishTest.java} (90%) rename instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/{AbstractNatsInstrumentationRequestTest.java => AbstractNatsRequestTest.java} (97%) rename instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/{AbstractNatsInstrumentationTest.java => AbstractNatsTest.java} (96%) rename instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/{NatsInstrumentationTestHelper.java => NatsTestHelper.java} (96%) diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index b1b0a7ca90c2..c068ea2d21bd 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -105,7 +105,7 @@ These are the supported libraries and frameworks: | [Micrometer](https://micrometer.io/) | 1.5+ (disabled by default) | [opentelemetry-micrometer-1.5](../instrumentation/micrometer/micrometer-1.5/library) | none | | [MongoDB Driver](https://mongodb.github.io/mongo-java-driver/) | 3.1+ | [opentelemetry-mongo-3.1](../instrumentation/mongo/mongo-3.1/library) | [Database Client Spans], [Database Client Metrics] [6] | | [MyBatis](https://mybatis.org/mybatis-3/) | 3.2+ | N/A | none | -| [NATS](https://github.com/nats-io/nats.java) | 2.17.2+ | [nats-2.17](../instrumentation/nats/nats-2.17/library) | [Messaging Spans] | +| [NATS Client](https://github.com/nats-io/nats.java) | 2.17.2+ | [nats-2.17](../instrumentation/nats/nats-2.17/library) | [Messaging Spans] | | [Netty HTTP codec [5]](https://github.com/netty/netty) | 3.8+ | [opentelemetry-netty-4.1](../instrumentation/netty/netty-4.1/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] | | [OpenAI Java SDK](https://github.com/openai/openai-java) | 1.1+ | [openai-java-1.1](../instrumentation/openai/openai-java-1.1/library) | [GenAI Client Spans], [GenAI Client Metrics] | | [OpenSearch Rest Client](https://github.com/opensearch-project/opensearch-java) | 1.0+ | | [Database Client Spans], [Database Client Metrics] [6] | diff --git a/instrumentation/nats/nats-2.17/javaagent/README.md b/instrumentation/nats/nats-2.17/javaagent/README.md deleted file mode 100644 index a2fac28fb29a..000000000000 --- a/instrumentation/nats/nats-2.17/javaagent/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Auto-instrumentation for NATS version 2.17 - -Provides OpenTelemetry auto-instrumentation for [NATS 2.17](https://github.com/nats-io/nats.java). diff --git a/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts index aaa0d9c3e45f..ea0dcf8ad4c5 100644 --- a/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts +++ b/instrumentation/nats/nats-2.17/javaagent/build.gradle.kts @@ -32,15 +32,14 @@ tasks { testClassesDirs = sourceSets.test.get().output.classesDirs classpath = sourceSets.test.get().runtimeClasspath filter { - includeTestsMatching("NatsInstrumentationExperimentalTest") + includeTestsMatching("NatsExperimentalTest") } - jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") jvmArgs("-Dotel.instrumentation.messaging.experimental.capture-headers=captured-header") } test { filter { - excludeTestsMatching("NatsInstrumentationExperimentalTest") + excludeTestsMatching("NatsExperimentalTest") } } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java index 8dc3640bf43a..c59417d3ebc2 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java @@ -126,21 +126,14 @@ public static Message onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, @Advice.Argument(1) byte[] body, - @Advice.Argument(2) Duration timeout, - @Advice.Local("message") Message message) + @Advice.Argument(2) Duration timeout) throws InterruptedException { // call the instrumented request method - message = connection.request(subject, null, body, timeout); - return message; + return connection.request(subject, null, body, timeout); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static Message onExit( - @Advice.Return(readOnly = false) Message returned, - @Advice.Local("message") Message message) { - returned = message; - return returned; - } + public static void onExit(@Advice.Enter Message message) {} } @SuppressWarnings("unused") @@ -300,6 +293,7 @@ public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) Message message, @Advice.Local("future") CompletableFuture future) { + // execute original method body to handle null message if (message == null) { return null; } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsDispatcherTest.java similarity index 76% rename from instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java rename to instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsDispatcherTest.java index 451cb1fc80bb..1260668a9534 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsDispatcherTest.java @@ -5,12 +5,12 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_17; -import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsInstrumentationRequestTest; +import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsDispatcherTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import org.junit.jupiter.api.extension.RegisterExtension; -class NatsInstrumentationRequestTest extends AbstractNatsInstrumentationRequestTest { +class NatsDispatcherTest extends AbstractNatsDispatcherTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsExperimentalTest.java similarity index 76% rename from instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java rename to instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsExperimentalTest.java index 99f98610f43c..55c174bb8323 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsExperimentalTest.java @@ -5,12 +5,12 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_17; -import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsInstrumentationPublishTest; +import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsExperimentalTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import org.junit.jupiter.api.extension.RegisterExtension; -class NatsInstrumentationPublishTest extends AbstractNatsInstrumentationPublishTest { +class NatsExperimentalTest extends AbstractNatsExperimentalTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsPublishTest.java similarity index 75% rename from instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java rename to instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsPublishTest.java index 138d7d4beec7..940d64d3117f 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsPublishTest.java @@ -5,12 +5,12 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_17; -import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsInstrumentationDispatcherTest; +import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsPublishTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import org.junit.jupiter.api.extension.RegisterExtension; -class NatsInstrumentationDispatcherTest extends AbstractNatsInstrumentationDispatcherTest { +class NatsPublishTest extends AbstractNatsPublishTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); diff --git a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsRequestTest.java similarity index 74% rename from instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java rename to instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsRequestTest.java index bdd437b16250..01eb0301e12a 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsRequestTest.java @@ -5,12 +5,12 @@ package io.opentelemetry.javaagent.instrumentation.nats.v2_17; -import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsInstrumentationExperimentalTest; +import io.opentelemetry.instrumentation.nats.v2_17.AbstractNatsRequestTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import org.junit.jupiter.api.extension.RegisterExtension; -class NatsInstrumentationExperimentalTest extends AbstractNatsInstrumentationExperimentalTest { +class NatsRequestTest extends AbstractNatsRequestTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); diff --git a/instrumentation/nats/nats-2.17/library/README.md b/instrumentation/nats/nats-2.17/library/README.md index 5bfdfe8c6d96..bc4e587b621b 100644 --- a/instrumentation/nats/nats-2.17/library/README.md +++ b/instrumentation/nats/nats-2.17/library/README.md @@ -1,6 +1,6 @@ # Library Instrumentation for NATS version 2.17 -Provides OpenTelemetry instrumentation for [NATS 2.17](https://github.com/nats-io/nats.java). +Provides OpenTelemetry instrumentation for [NATS Client](https://github.com/nats-io/nats.java). ## Quickstart diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java index 0c0d7a2aaec8..eedd1311bae3 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetry.java @@ -14,12 +14,15 @@ import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import java.io.IOException; +/** Entrypoint for instrumenting NATS clients. */ public final class NatsTelemetry { + /** Returns a new {@link NatsTelemetry} configured with the given {@link OpenTelemetry}. */ public static NatsTelemetry create(OpenTelemetry openTelemetry) { return new NatsTelemetryBuilder(openTelemetry).build(); } + /** Returns a new {@link NatsTelemetryBuilder} configured with the given {@link OpenTelemetry}. */ public static NatsTelemetryBuilder builder(OpenTelemetry openTelemetry) { return new NatsTelemetryBuilder(openTelemetry); } @@ -27,7 +30,7 @@ public static NatsTelemetryBuilder builder(OpenTelemetry openTelemetry) { private final Instrumenter producerInstrumenter; private final Instrumenter consumerProcessInstrumenter; - public NatsTelemetry( + NatsTelemetry( Instrumenter producerInstrumenter, Instrumenter consumerProcessInstrumenter) { this.producerInstrumenter = producerInstrumenter; @@ -37,9 +40,9 @@ public NatsTelemetry( /** * Returns a {@link Connection} with telemetry instrumentation. * - *

This does *not* monitor the main inbox of the default dispatcher. {@link - * #configure(Options.Builder)} {@link #newConnection(Options.Builder, ConnectionFactory)} {@link - * #newConnection(Options, ConnectionFactory)} + *

This method should be used together with {@link #configure(Options.Builder)}. Consider using + * {@link #newConnection(Options.Builder, ConnectionFactory)} or {@link #newConnection(Options, + * ConnectionFactory)} instead. */ public Connection wrap(Connection connection) { return OpenTelemetryConnection.wrap( @@ -47,8 +50,11 @@ public Connection wrap(Connection connection) { } /** - * Returns a {@link Options.Builder} with the main inbox from the default dispatcher monitored - * with telemetry instrumentation. + * Returns a {@link Options.Builder} configured with telemetry instrumentation. + * + *

This method should be used together with {@link #wrap(Connection)}. Consider using {@link + * #newConnection(Options.Builder, ConnectionFactory)} or {@link #newConnection(Options, + * ConnectionFactory)} instead. */ public Options.Builder configure(Options.Builder options) { DispatcherFactory factory = options.build().getDispatcherFactory(); diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java index 00fbb0b22382..c125d371e64e 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTelemetryBuilder.java @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.List; +/** A builder of {@link NatsTelemetry}. */ public final class NatsTelemetryBuilder { private final OpenTelemetry openTelemetry; @@ -23,12 +24,18 @@ public final class NatsTelemetryBuilder { this.openTelemetry = openTelemetry; } + /** + * Configures the messaging headers that will be captured as span attributes. + * + * @param capturedHeaders A list of messaging header names. + */ @CanIgnoreReturnValue public NatsTelemetryBuilder setCapturedHeaders(Collection capturedHeaders) { this.capturedHeaders = new ArrayList<>(capturedHeaders); return this; } + /** Returns a new {@link NatsTelemetry} with the settings of this {@link NatsTelemetryBuilder}. */ public NatsTelemetry build() { return new NatsTelemetry( NatsInstrumenterFactory.createProducerInstrumenter(openTelemetry, capturedHeaders), diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsDispatcherTest.java similarity index 89% rename from instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java rename to instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsDispatcherTest.java index c407d57bd291..f4b21df6d3b7 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsDispatcherTest.java @@ -10,7 +10,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; -class NatsInstrumentationPublishTest extends AbstractNatsInstrumentationPublishTest { +class NatsDispatcherTest extends AbstractNatsDispatcherTest { @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsExperimentalTest.java similarity index 90% rename from instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java rename to instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsExperimentalTest.java index bb6f3cacfd5c..efca6c50bcd2 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsExperimentalTest.java @@ -12,7 +12,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; -class NatsInstrumentationExperimentalTest extends AbstractNatsInstrumentationExperimentalTest { +class NatsExperimentalTest extends AbstractNatsExperimentalTest { @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsPublishTest.java similarity index 88% rename from instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java rename to instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsPublishTest.java index 7fdf75e82071..13949ced3d30 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsPublishTest.java @@ -10,7 +10,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; -class NatsInstrumentationDispatcherTest extends AbstractNatsInstrumentationDispatcherTest { +class NatsPublishTest extends AbstractNatsPublishTest { @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); diff --git a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsRequestTest.java similarity index 92% rename from instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java rename to instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsRequestTest.java index 06720e8f1245..aed9638c83ea 100644 --- a/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/library/src/test/java/io/opentelemetry/instrumentation/nats/v2_17/NatsRequestTest.java @@ -13,7 +13,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; -class NatsInstrumentationRequestTest extends AbstractNatsInstrumentationRequestTest { +class NatsRequestTest extends AbstractNatsRequestTest { @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsDispatcherTest.java similarity index 50% rename from instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsDispatcherTest.java index 4f890e4843bf..04a918db310a 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsDispatcherTest.java @@ -5,20 +5,23 @@ package io.opentelemetry.instrumentation.nats.v2_17; -import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsTestHelper.messagingAttributes; import static org.assertj.core.api.Assertions.assertThatNoException; import io.nats.client.Dispatcher; import io.nats.client.Subscription; import io.nats.client.impl.Headers; import io.nats.client.impl.NatsMessage; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationDispatcherTest - extends AbstractNatsInstrumentationTest { +public abstract class AbstractNatsDispatcherTest extends AbstractNatsTest { private int clientId; @@ -28,34 +31,69 @@ void beforeEach() { } @Test - void testSubscribe() { - // global message handler - Dispatcher d1 = connection.createDispatcher(msg -> {}).subscribe("sub"); - // per-subscription message handler - Dispatcher d2 = connection.createDispatcher(); - Subscription s1 = d2.subscribe("sub", msg -> {}); - Subscription s2 = d2.subscribe("sub", "queue", msg -> {}); + void testSubscribeDefaultHandler() { + Dispatcher d1 = connection.createDispatcher(msg -> addNestedSpan()).subscribe("sub"); + publishAndAssertTraceAndSpans(); + + // finally, to make sure we're unwrapping properly the + // OpenTelemetryDispatcher in the library + assertThatNoException().isThrownBy(() -> connection.closeDispatcher(d1)); + } + + @Test + void testSubscribeSubscriptionMessageHandler() { + Dispatcher d1 = connection.createDispatcher(); + Subscription s1 = d1.subscribe("sub", msg -> addNestedSpan()); + + publishAndAssertTraceAndSpans(); + + // finally, to make sure we're unwrapping properly the + // OpenTelemetryDispatcher in the library + assertThatNoException() + .isThrownBy( + () -> { + d1.unsubscribe(s1); + connection.closeDispatcher(d1); + }); + } + + @Test + void testSubscribeSubscriptionQueueMessageHandler() { + Dispatcher d1 = connection.createDispatcher(); + Subscription s1 = d1.subscribe("sub", "queue", msg -> addNestedSpan()); + + publishAndAssertTraceAndSpans(); + + // finally, to make sure we're unwrapping properly the + // OpenTelemetryDispatcher in the library + assertThatNoException() + .isThrownBy( + () -> { + d1.unsubscribe(s1); + connection.closeDispatcher(d1); + }); + } + + void publishAndAssertTraceAndSpans() { // when testing() .runWithSpan( "parent", () -> { NatsMessage.Builder builder = NatsMessage.builder().subject("sub").data("x"); - connection.publish(builder.build()); // no propagation - connection.publish(builder.headers(new Headers()).build()); // propagation + connection.publish(builder.build()); + connection.publish(builder.headers(new Headers()).build()); }); // then 1 trace // - parent // --- 1 publish - // ----- process global (propagation with explicit headers) - // ----- process subject (propagation with explicit headers) - // ----- process subject+queue (propagation with explicit headers) + // ----- process (propagation with explicit headers) + // -------- test // --- 1 publish - // ----- process global (propagation with headers override) - // ----- process subject (propagation with headers override) - // ----- process subject+queue (propagation with headers override) + // ----- process (propagation with headers override) + // -------- test testing() .waitAndAssertTraces( trace -> @@ -70,13 +108,7 @@ void testSubscribe() { .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(1)), span -> - span.hasName("sub process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)), - span -> - span.hasName("sub process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)), + span.hasName("test").hasKind(SpanKind.INTERNAL).hasParent(trace.getSpan(2)), span -> span.hasName("sub publish") .hasKind(SpanKind.PRODUCER) @@ -84,27 +116,21 @@ void testSubscribe() { span -> span.hasName("sub process") .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(5)), - span -> - span.hasName("sub process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(5)), - span -> - span.hasName("sub process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(5)) + .hasParent(trace.getSpan(4)) .hasAttributesSatisfyingExactly( - messagingAttributes("process", "sub", clientId)))); + messagingAttributes("process", "sub", clientId)), + span -> + span.hasName("test") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(5)))); + } - // finally, to make sure we're unwrapping properly the - // OpenTelemetryDispatcher in the library - assertThatNoException() - .isThrownBy( - () -> { - d2.unsubscribe(s1); - d2.unsubscribe(s2); - connection.closeDispatcher(d1); - connection.closeDispatcher(d2); - }); + void addNestedSpan() { + Context current = Context.current(); + Tracer tracer = testing().getOpenTelemetry().getTracer("test"); + try (Scope ignored = current.makeCurrent()) { + Span span = tracer.spanBuilder("test").startSpan(); + span.end(); + } } } diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsExperimentalTest.java similarity index 91% rename from instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsExperimentalTest.java index f3a22354e192..4b0f12904c5c 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationExperimentalTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsExperimentalTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.instrumentation.nats.v2_17; -import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsTestHelper.messagingAttributes; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static java.util.Collections.singletonList; @@ -19,8 +19,7 @@ import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationExperimentalTest - extends AbstractNatsInstrumentationTest { +public abstract class AbstractNatsExperimentalTest extends AbstractNatsTest { private int clientId; diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsPublishTest.java similarity index 90% rename from instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsPublishTest.java index 318c9f7ea5e2..c42cdb643b5a 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationPublishTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsPublishTest.java @@ -5,8 +5,8 @@ package io.opentelemetry.instrumentation.nats.v2_17; -import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.assertTraceparentHeader; -import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsTestHelper.assertTraceparentHeader; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsTestHelper.messagingAttributes; import io.nats.client.Subscription; import io.nats.client.impl.Headers; @@ -18,8 +18,7 @@ import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationPublishTest - extends AbstractNatsInstrumentationTest { +public abstract class AbstractNatsPublishTest extends AbstractNatsTest { private int clientId; private Subscription subscription; diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsRequestTest.java similarity index 97% rename from instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsRequestTest.java index 9599bb62cb5f..b8e4924a8d0a 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationRequestTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsRequestTest.java @@ -5,8 +5,8 @@ package io.opentelemetry.instrumentation.nats.v2_17; -import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.assertTraceparentHeader; -import static io.opentelemetry.instrumentation.nats.v2_17.NatsInstrumentationTestHelper.messagingAttributes; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsTestHelper.assertTraceparentHeader; +import static io.opentelemetry.instrumentation.nats.v2_17.NatsTestHelper.messagingAttributes; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_TEMPORARY; import static java.util.Arrays.asList; @@ -27,8 +27,7 @@ import org.junit.jupiter.api.Test; @SuppressWarnings("deprecation") // using deprecated semconv -public abstract class AbstractNatsInstrumentationRequestTest - extends AbstractNatsInstrumentationTest { +public abstract class AbstractNatsRequestTest extends AbstractNatsTest { private int clientId; private Subscription subscription; diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsTest.java similarity index 96% rename from instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationTest.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsTest.java index 4fb2a192a33f..899f41384dc1 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsInstrumentationTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsTest.java @@ -16,7 +16,7 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; -abstract class AbstractNatsInstrumentationTest { +abstract class AbstractNatsTest { static DockerImageName natsImage; static GenericContainer natsContainer; diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTestHelper.java similarity index 96% rename from instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java rename to instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTestHelper.java index 36898e2f4e8c..7d0e3b1adac2 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsInstrumentationTestHelper.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTestHelper.java @@ -19,7 +19,7 @@ import java.time.Duration; @SuppressWarnings("deprecation") // using deprecated semconv -public class NatsInstrumentationTestHelper { +public class NatsTestHelper { public static AttributeAssertion[] messagingAttributes( String operation, String subject, int clientId, AttributeAssertion other) { @@ -52,5 +52,5 @@ public static void assertTraceparentHeader(Subscription subscription) assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); } - private NatsInstrumentationTestHelper() {} + private NatsTestHelper() {} } From 34e2f1d0c185eb105f61d7e1de0dcc9d92cf6842 Mon Sep 17 00:00:00 2001 From: Alix Date: Tue, 16 Sep 2025 19:11:55 +0200 Subject: [PATCH 31/34] add test on Request methods --- .../ConnectionRequestInstrumentation.java | 5 +- .../nats/v2_17/AbstractNatsRequestTest.java | 110 ++++++++++++------ 2 files changed, 76 insertions(+), 39 deletions(-) diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java index c59417d3ebc2..25f36215f99c 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java @@ -133,7 +133,10 @@ public static Message onEnter( } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit(@Advice.Enter Message message) {} + public static void onExit( + @Advice.Enter Message message, @Advice.Return(readOnly = false) Message ret) { + ret = message; + } } @SuppressWarnings("unused") diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsRequestTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsRequestTest.java index b8e4924a8d0a..4230a8b88136 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsRequestTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsRequestTest.java @@ -10,8 +10,10 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_TEMPORARY; import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; import io.nats.client.Dispatcher; +import io.nats.client.Message; import io.nats.client.Subscription; import io.nats.client.impl.Headers; import io.nats.client.impl.NatsMessage; @@ -20,7 +22,9 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -46,12 +50,14 @@ void afterEach() throws InterruptedException { @Test void testRequestTimeout() throws InterruptedException { // when - testing() - .runWithSpan( - "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); + Message message = + testing() + .runWithSpan( + "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); // then // assertTimeoutPublishSpan(); + assertThat(message).isNull(); assertTraceparentHeader(subscription); } @@ -64,12 +70,14 @@ void testRequestBody() throws InterruptedException { .subscribe("sub"); // when - testing() - .runWithSpan( - "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); + Message message = + testing() + .runWithSpan( + "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); connection.closeDispatcher(dispatcher); // then + assertThat(message).isNotNull(); assertPublishReceiveSpansSameTrace(); assertTraceparentHeader(subscription); } @@ -83,13 +91,17 @@ void testRequestHeadersBody() throws InterruptedException { .subscribe("sub"); // when - testing() - .runWithSpan( - "parent", - () -> connection.request("sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + Message message = + testing() + .runWithSpan( + "parent", + () -> + connection.request( + "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); connection.closeDispatcher(dispatcher); // then + assertThat(message).isNotNull(); assertPublishReceiveSpansSameTrace(); assertTraceparentHeader(subscription); } @@ -104,10 +116,12 @@ void testRequestMessage() throws InterruptedException { NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when - testing().runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); + Message response = + testing().runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); connection.closeDispatcher(dispatcher); // then + assertThat(response).isNotNull(); assertPublishReceiveSpansSameTrace(); assertTraceparentHeader(subscription); } @@ -123,10 +137,12 @@ void testRequestMessageHeaders() throws InterruptedException { NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); // when - testing().runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); + Message response = + testing().runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); connection.closeDispatcher(dispatcher); // then + assertThat(response).isNotNull(); assertPublishReceiveSpansSameTrace(); assertTraceparentHeader(subscription); } @@ -140,13 +156,15 @@ void testRequestFutureBody() throws InterruptedException { .subscribe("sub"); // when - testing() - .runWithSpan("parent", () -> connection.request("sub", new byte[] {0})) - .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); + CompletableFuture message = + testing() + .runWithSpan("parent", () -> connection.request("sub", new byte[] {0})) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then assertPublishReceiveSpansSameTrace(); assertTraceparentHeader(subscription); + assertThat(message).isCompletedWithValueMatching(Objects::nonNull); } @Test @@ -158,13 +176,15 @@ void testRequestFutureHeadersBody() throws InterruptedException { .subscribe("sub"); // when - testing() - .runWithSpan("parent", () -> connection.request("sub", new Headers(), new byte[] {0})) - .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); + CompletableFuture message = + testing() + .runWithSpan("parent", () -> connection.request("sub", new Headers(), new byte[] {0})) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then assertPublishReceiveSpansSameTrace(); assertTraceparentHeader(subscription); + assertThat(message).isCompletedWithValueMatching(Objects::nonNull); } @Test @@ -177,13 +197,15 @@ void testRequestFutureMessage() throws InterruptedException { NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when - testing() - .runWithSpan("parent", () -> connection.request(message)) - .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); + CompletableFuture response = + testing() + .runWithSpan("parent", () -> connection.request(message)) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then assertPublishReceiveSpansSameTrace(); assertTraceparentHeader(subscription); + assertThat(response).isCompletedWithValueMatching(Objects::nonNull); } @Test @@ -197,41 +219,47 @@ void testRequestFutureMessageHeaders() throws InterruptedException { NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); // when - testing() - .runWithSpan("parent", () -> connection.request(message)) - .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); + CompletableFuture response = + testing() + .runWithSpan("parent", () -> connection.request(message)) + .whenComplete((m, e) -> connection.closeDispatcher(dispatcher)); // then assertPublishReceiveSpansSameTrace(); assertTraceparentHeader(subscription); + assertThat(response).isCompletedWithValueMatching(Objects::nonNull); } @Test void testRequestTimeoutFutureBody() throws InterruptedException { // when - testing() - .runWithSpan( - "parent", - () -> connection.requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); + CompletableFuture message = + testing() + .runWithSpan( + "parent", + () -> connection.requestWithTimeout("sub", new byte[] {0}, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); assertTraceparentHeader(subscription); + assertThat(message).isCompletedExceptionally(); } @Test void testRequestTimeoutFutureHeadersBody() throws InterruptedException { // when - testing() - .runWithSpan( - "parent", - () -> - connection.requestWithTimeout( - "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + CompletableFuture message = + testing() + .runWithSpan( + "parent", + () -> + connection.requestWithTimeout( + "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); assertTraceparentHeader(subscription); + assertThat(message).isCompletedExceptionally(); } @Test @@ -240,12 +268,15 @@ void testRequestTimeoutFutureMessage() throws InterruptedException { NatsMessage message = NatsMessage.builder().subject("sub").data("x").build(); // when - testing() - .runWithSpan("parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); + CompletableFuture response = + testing() + .runWithSpan( + "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); assertTraceparentHeader(subscription); + assertThat(response).isCompletedExceptionally(); } @Test @@ -255,12 +286,15 @@ void testRequestTimeoutFutureMessageHeaders() throws InterruptedException { NatsMessage.builder().subject("sub").headers(new Headers()).data("x").build(); // when - testing() - .runWithSpan("parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); + CompletableFuture response = + testing() + .runWithSpan( + "parent", () -> connection.requestWithTimeout(message, Duration.ofSeconds(1))); // then assertCancellationPublishSpan(); assertTraceparentHeader(subscription); + assertThat(response).isCompletedExceptionally(); } private void assertPublishReceiveSpansSameTrace() { From 043c8575c80e6afa0841ad431fa6eaff10dedea5 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Wed, 17 Sep 2025 15:11:23 +0300 Subject: [PATCH 32/34] review --- .../ConnectionRequestInstrumentation.java | 99 ++++++++----------- .../nats/v2_17/DispatcherInstrumentation.java | 55 ----------- .../v2_17/NatsIgnoredTypesConfigurer.java | 20 ++++ .../nats/v2_17/NatsInstrumentationModule.java | 1 - .../impl/OpenTelemetryDispatcherFactory.java | 2 +- .../nats/v2_17/OpenTelemetryConnection.java | 7 +- .../nats/v2_17/OpenTelemetryDispatcher.java | 1 + .../NatsRequestMessagingAttributesGetter.java | 11 +-- .../OpenTelemetryMessageHandler.java | 3 +- .../v2_17/AbstractNatsDispatcherTest.java | 25 ++--- .../nats/v2_17/AbstractNatsPublishTest.java | 2 +- .../nats/v2_17/AbstractNatsRequestTest.java | 22 +++-- .../nats/v2_17/NatsTestHelper.java | 6 +- 13 files changed, 103 insertions(+), 151 deletions(-) delete mode 100644 instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/DispatcherInstrumentation.java create mode 100644 instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsIgnoredTypesConfigurer.java rename instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/{ => internal}/OpenTelemetryMessageHandler.java (92%) diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java index 25f36215f99c..bf4eaf2e2355 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java @@ -132,10 +132,10 @@ public static Message onEnter( return connection.request(subject, null, body, timeout); } - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + @Advice.OnMethodExit(suppress = Throwable.class) public static void onExit( - @Advice.Enter Message message, @Advice.Return(readOnly = false) Message ret) { - ret = message; + @Advice.Return(readOnly = false) Message result, @Advice.Enter Message message) { + result = message; } } @@ -192,26 +192,21 @@ public static class RequestMessageAdvice { public static Message onEnter( @Advice.This Connection connection, @Advice.Argument(0) Message request, - @Advice.Argument(1) Duration timeout, - @Advice.Local("response") Message response) + @Advice.Argument(1) Duration timeout) throws InterruptedException { if (request == null) { return null; } // call the instrumented request method - response = - connection.request( - request.getSubject(), request.getHeaders(), request.getData(), timeout); - return response; + return connection.request( + request.getSubject(), request.getHeaders(), request.getData(), timeout); } - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static Message onExit( - @Advice.Return(readOnly = false) Message returned, - @Advice.Local("response") Message response) { - returned = response; - return returned; + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.Return(readOnly = false) Message result, @Advice.Enter Message response) { + result = response; } } @@ -222,19 +217,16 @@ public static class RequestFutureBodyAdvice { public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(1) byte[] body, - @Advice.Local("future") CompletableFuture future) { + @Advice.Argument(1) byte[] body) { // call the instrumented request method - future = connection.request(subject, null, body); - return future; + return connection.request(subject, null, body); } - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static CompletableFuture onExit( - @Advice.Return(readOnly = false) CompletableFuture messageFuture, - @Advice.Local("future") CompletableFuture future) { - messageFuture = future; - return messageFuture; + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.Return(readOnly = false) CompletableFuture result, + @Advice.Enter CompletableFuture future) { + result = future; } } @@ -293,25 +285,23 @@ public static class RequestFutureMessageAdvice { @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) public static CompletableFuture onEnter( - @Advice.This Connection connection, - @Advice.Argument(0) Message message, - @Advice.Local("future") CompletableFuture future) { + @Advice.This Connection connection, @Advice.Argument(0) Message message) { // execute original method body to handle null message if (message == null) { return null; } // call the instrumented request method - future = connection.request(message.getSubject(), message.getHeaders(), message.getData()); - return future; + return connection.request(message.getSubject(), message.getHeaders(), message.getData()); } - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static CompletableFuture onExit( - @Advice.Return(readOnly = false) CompletableFuture messageFuture, - @Advice.Local("future") CompletableFuture future) { - messageFuture = future; - return messageFuture; + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.Return(readOnly = false) CompletableFuture result, + @Advice.Enter CompletableFuture future) { + if (future != null) { + result = future; + } } } @@ -323,19 +313,16 @@ public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, @Advice.Argument(1) byte[] body, - @Advice.Argument(2) Duration timeout, - @Advice.Local("future") CompletableFuture future) { + @Advice.Argument(2) Duration timeout) { // call the instrumented requestWithTimeout method - future = connection.requestWithTimeout(subject, null, body, timeout); - return future; + return connection.requestWithTimeout(subject, null, body, timeout); } - @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static CompletableFuture onExit( - @Advice.Return(readOnly = false) CompletableFuture messageFuture, - @Advice.Local("future") CompletableFuture future) { - messageFuture = future; - return messageFuture; + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.Return(readOnly = false) CompletableFuture result, + @Advice.Enter CompletableFuture future) { + result = future; } } @@ -396,25 +383,23 @@ public static class RequestTimeoutFutureMessageAdvice { public static CompletableFuture onEnter( @Advice.This Connection connection, @Advice.Argument(value = 0, readOnly = false) Message message, - @Advice.Argument(1) Duration timeout, - @Advice.Local("future") CompletableFuture future) { + @Advice.Argument(1) Duration timeout) { if (message == null) { return null; } // call the instrumented requestWithTimeout method - future = - connection.requestWithTimeout( - message.getSubject(), message.getHeaders(), message.getData(), timeout); - return future; + return connection.requestWithTimeout( + message.getSubject(), message.getHeaders(), message.getData(), timeout); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static CompletableFuture onExit( - @Advice.Return(readOnly = false) CompletableFuture messageFuture, - @Advice.Local("future") CompletableFuture future) { - messageFuture = future; - return messageFuture; + public static void onExit( + @Advice.Return(readOnly = false) CompletableFuture result, + @Advice.Enter CompletableFuture future) { + if (future != null) { + result = future; + } } } } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/DispatcherInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/DispatcherInstrumentation.java deleted file mode 100644 index 8f33bb3c57f8..000000000000 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/DispatcherInstrumentation.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.nats.v2_17; - -import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; -import static net.bytebuddy.matcher.ElementMatchers.named; - -import io.opentelemetry.context.Scope; -import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public class DispatcherInstrumentation implements TypeInstrumentation { - - @Override - public ElementMatcher typeMatcher() { - return implementsInterface(named("io.nats.client.Dispatcher")); - } - - @Override - public void transform(TypeTransformer transformer) { - transformer.applyAdviceToMethod( - isPublic().and(named("start")), - DispatcherInstrumentation.class.getName() + "$DisablePropagationAdvice"); - } - - @SuppressWarnings("unused") - public static class DisablePropagationAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static Scope onEnter() { - // NatsConnection creates a long-running dispatcher on the first `request` call - // leading to a leaked context over the `publish` span creating the dispatcher. - // Dispatchers are usually background long-lived threads, we can force root, - // as we're not expecting to be anything else than entry points for network messages. - if (Java8BytecodeBridge.currentContext() != Java8BytecodeBridge.rootContext()) { - return Java8BytecodeBridge.rootContext().makeCurrent(); - } - return null; - } - - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit(@Advice.Enter Scope scope) { - if (scope != null) { - scope.close(); - } - } - } -} diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsIgnoredTypesConfigurer.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsIgnoredTypesConfigurer.java new file mode 100644 index 000000000000..9789bca6af21 --- /dev/null +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsIgnoredTypesConfigurer.java @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.nats.v2_17; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesBuilder; +import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesConfigurer; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; + +@AutoService(IgnoredTypesConfigurer.class) +public class NatsIgnoredTypesConfigurer implements IgnoredTypesConfigurer { + + @Override + public void configure(IgnoredTypesBuilder builder, ConfigProperties config) { + builder.ignoreTaskClass("io.nats.client.impl.NatsDispatcher"); + } +} diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationModule.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationModule.java index 1a0c4fa7c35f..3701dacf3b25 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationModule.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationModule.java @@ -24,7 +24,6 @@ public List typeInstrumentations() { return asList( new ConnectionPublishInstrumentation(), new ConnectionRequestInstrumentation(), - new DispatcherInstrumentation(), new MessageHandlerInstrumentation()); } } diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java index e73c67e3efa1..6202f9aea801 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/nats/client/impl/OpenTelemetryDispatcherFactory.java @@ -7,8 +7,8 @@ import io.nats.client.MessageHandler; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.nats.v2_17.OpenTelemetryMessageHandler; import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.internal.OpenTelemetryMessageHandler; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java index 6edc037cba5b..61fde251dc6d 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryConnection.java @@ -15,6 +15,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsMessageWritableHeaders; import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.internal.OpenTelemetryMessageHandler; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -134,10 +135,10 @@ private void publish(Method method, Object[] args) throws Throwable { } Context parentContext = Context.current(); - headers = NatsMessageWritableHeaders.create(headers); NatsRequest natsRequest = null; if (subject != null) { + headers = NatsMessageWritableHeaders.create(headers); natsRequest = NatsRequest.create(delegate, subject, replyTo, headers, body); } @@ -193,10 +194,10 @@ private Message request(Method method, Object[] args) throws Throwable { } Context parentContext = Context.current(); - headers = NatsMessageWritableHeaders.create(headers); NatsRequest natsRequest = null; if (subject != null) { + headers = NatsMessageWritableHeaders.create(headers); natsRequest = NatsRequest.create(delegate, subject, null, headers, body); } @@ -282,10 +283,10 @@ private CompletableFuture requestAsync(Method method, Object[] args) th } Context parentContext = Context.current(); - headers = NatsMessageWritableHeaders.create(headers); NatsRequest natsRequest = null; if (subject != null) { + headers = NatsMessageWritableHeaders.create(headers); natsRequest = NatsRequest.create(delegate, subject, null, headers, body); } diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java index fc12dffb77c3..0b3bd5c63f2d 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryDispatcher.java @@ -10,6 +10,7 @@ import io.nats.client.Subscription; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; +import io.opentelemetry.instrumentation.nats.v2_17.internal.OpenTelemetryMessageHandler; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java index 9674cf91c5b1..c1daa68711f4 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/NatsRequestMessagingAttributesGetter.java @@ -15,7 +15,6 @@ enum NatsRequestMessagingAttributesGetter implements MessagingAttributesGetter { INSTANCE; - @Nullable @Override public String getSystem(NatsRequest request) { return "nats"; @@ -52,7 +51,6 @@ public String getConversationId(NatsRequest request) { return null; } - @Nullable @Override public Long getMessageBodySize(NatsRequest request) { return request.getDataSize(); @@ -70,7 +68,6 @@ public String getMessageId(NatsRequest request, @Nullable Object unused) { return null; } - @Nullable @Override public String getClientId(NatsRequest request) { return String.valueOf(request.getClientId()); @@ -85,8 +82,10 @@ public Long getBatchMessageCount(NatsRequest request, @Nullable Object unused) { @Override public List getMessageHeader(NatsRequest request, String name) { Headers headers = request.getHeaders(); - return headers == null || headers.get(name) == null - ? Collections.emptyList() - : headers.get(name); + if (headers == null) { + return Collections.emptyList(); + } + List result = headers.get(name); + return result == null ? Collections.emptyList() : result; } } diff --git a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/OpenTelemetryMessageHandler.java similarity index 92% rename from instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java rename to instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/OpenTelemetryMessageHandler.java index c4a16a939631..74732af2ee79 100644 --- a/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/OpenTelemetryMessageHandler.java +++ b/instrumentation/nats/nats-2.17/library/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/internal/OpenTelemetryMessageHandler.java @@ -3,14 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.nats.v2_17; +package io.opentelemetry.instrumentation.nats.v2_17.internal; import io.nats.client.Message; import io.nats.client.MessageHandler; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsDispatcherTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsDispatcherTest.java index 04a918db310a..498d2ff7b234 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsDispatcherTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsDispatcherTest.java @@ -12,11 +12,7 @@ import io.nats.client.Subscription; import io.nats.client.impl.Headers; import io.nats.client.impl.NatsMessage; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -32,7 +28,7 @@ void beforeEach() { @Test void testSubscribeDefaultHandler() { - Dispatcher d1 = connection.createDispatcher(msg -> addNestedSpan()).subscribe("sub"); + Dispatcher d1 = connection.createDispatcher(msg -> addChildSpan()).subscribe("sub"); publishAndAssertTraceAndSpans(); @@ -44,7 +40,7 @@ void testSubscribeDefaultHandler() { @Test void testSubscribeSubscriptionMessageHandler() { Dispatcher d1 = connection.createDispatcher(); - Subscription s1 = d1.subscribe("sub", msg -> addNestedSpan()); + Subscription s1 = d1.subscribe("sub", msg -> addChildSpan()); publishAndAssertTraceAndSpans(); @@ -61,7 +57,7 @@ void testSubscribeSubscriptionMessageHandler() { @Test void testSubscribeSubscriptionQueueMessageHandler() { Dispatcher d1 = connection.createDispatcher(); - Subscription s1 = d1.subscribe("sub", "queue", msg -> addNestedSpan()); + Subscription s1 = d1.subscribe("sub", "queue", msg -> addChildSpan()); publishAndAssertTraceAndSpans(); @@ -108,7 +104,9 @@ void publishAndAssertTraceAndSpans() { .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(1)), span -> - span.hasName("test").hasKind(SpanKind.INTERNAL).hasParent(trace.getSpan(2)), + span.hasName("child") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(2)), span -> span.hasName("sub publish") .hasKind(SpanKind.PRODUCER) @@ -120,17 +118,12 @@ void publishAndAssertTraceAndSpans() { .hasAttributesSatisfyingExactly( messagingAttributes("process", "sub", clientId)), span -> - span.hasName("test") + span.hasName("child") .hasKind(SpanKind.INTERNAL) .hasParent(trace.getSpan(5)))); } - void addNestedSpan() { - Context current = Context.current(); - Tracer tracer = testing().getOpenTelemetry().getTracer("test"); - try (Scope ignored = current.makeCurrent()) { - Span span = tracer.spanBuilder("test").startSpan(); - span.end(); - } + void addChildSpan() { + testing().runWithSpan("child", () -> {}); } } diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsPublishTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsPublishTest.java index c42cdb643b5a..9b89d7bae129 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsPublishTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsPublishTest.java @@ -31,7 +31,7 @@ void beforeEach() { @AfterEach void afterEach() throws InterruptedException { - subscription.drain(Duration.ofSeconds(1)); + subscription.drain(Duration.ofSeconds(10)); } @Test diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsRequestTest.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsRequestTest.java index 4230a8b88136..716f8e38010a 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsRequestTest.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/AbstractNatsRequestTest.java @@ -44,7 +44,7 @@ void beforeEach() { @AfterEach void afterEach() throws InterruptedException { - subscription.drain(Duration.ofSeconds(1)); + subscription.drain(Duration.ofSeconds(10)); } @Test @@ -56,8 +56,18 @@ void testRequestTimeout() throws InterruptedException { "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); // then - // assertTimeoutPublishSpan(); assertThat(message).isNull(); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName("sub publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + messagingAttributes("publish", "sub", clientId)))); assertTraceparentHeader(subscription); } @@ -73,7 +83,7 @@ void testRequestBody() throws InterruptedException { Message message = testing() .runWithSpan( - "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(1))); + "parent", () -> connection.request("sub", new byte[] {0}, Duration.ofSeconds(10))); connection.closeDispatcher(dispatcher); // then @@ -97,7 +107,7 @@ void testRequestHeadersBody() throws InterruptedException { "parent", () -> connection.request( - "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(1))); + "sub", new Headers(), new byte[] {0}, Duration.ofSeconds(10))); connection.closeDispatcher(dispatcher); // then @@ -117,7 +127,7 @@ void testRequestMessage() throws InterruptedException { // when Message response = - testing().runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); + testing().runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(10))); connection.closeDispatcher(dispatcher); // then @@ -138,7 +148,7 @@ void testRequestMessageHeaders() throws InterruptedException { // when Message response = - testing().runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(1))); + testing().runWithSpan("parent", () -> connection.request(message, Duration.ofSeconds(10))); connection.closeDispatcher(dispatcher); // then diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTestHelper.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTestHelper.java index 7d0e3b1adac2..955fcada373b 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTestHelper.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTestHelper.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.nats.v2_17; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_CLIENT_ID; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; @@ -14,7 +15,6 @@ import io.nats.client.Message; import io.nats.client.Subscription; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import java.time.Duration; @@ -42,13 +42,13 @@ public static AttributeAssertion[] messagingAttributes( equalTo(MESSAGING_SYSTEM, "nats"), equalTo(MESSAGING_DESTINATION_NAME, subject), equalTo(MESSAGING_MESSAGE_BODY_SIZE, 1), - equalTo(AttributeKey.stringKey("messaging.client_id"), String.valueOf(clientId)) + equalTo(MESSAGING_CLIENT_ID, String.valueOf(clientId)) }; } public static void assertTraceparentHeader(Subscription subscription) throws InterruptedException { - Message published = subscription.nextMessage(Duration.ofSeconds(1)); + Message published = subscription.nextMessage(Duration.ofSeconds(10)); assertThat(published.getHeaders().get("traceparent")).isNotEmpty(); } From e599182a573137aabb72f77c05dda8b075fad3d4 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Wed, 17 Sep 2025 16:53:02 +0300 Subject: [PATCH 33/34] switch back to messaging.client_id, it is renamed to messaging.client.id in semconv --- .../instrumentation/nats/v2_17/NatsTestHelper.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTestHelper.java b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTestHelper.java index 955fcada373b..c83880fea484 100644 --- a/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTestHelper.java +++ b/instrumentation/nats/nats-2.17/testing/src/main/java/io/opentelemetry/instrumentation/nats/v2_17/NatsTestHelper.java @@ -5,8 +5,8 @@ package io.opentelemetry.instrumentation.nats.v2_17; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_CLIENT_ID; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; @@ -15,12 +15,16 @@ import io.nats.client.Message; import io.nats.client.Subscription; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import java.time.Duration; @SuppressWarnings("deprecation") // using deprecated semconv public class NatsTestHelper { + // copied from MessagingIncubatingAttributes + private static final AttributeKey MESSAGING_CLIENT_ID = stringKey("messaging.client_id"); + public static AttributeAssertion[] messagingAttributes( String operation, String subject, int clientId, AttributeAssertion other) { return messagingAttributes(operation, subject, clientId, new AttributeAssertion[] {other}); From 2d652da3f7afad0a1d2bac4707f427d9368e0844 Mon Sep 17 00:00:00 2001 From: Alix Date: Thu, 2 Oct 2025 14:16:08 +0200 Subject: [PATCH 34/34] Update instrumentation/nats/nats-2.17/library/README.md Co-authored-by: Jay DeLuca --- instrumentation/nats/nats-2.17/library/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/nats/nats-2.17/library/README.md b/instrumentation/nats/nats-2.17/library/README.md index bc4e587b621b..0dfa8e0f6258 100644 --- a/instrumentation/nats/nats-2.17/library/README.md +++ b/instrumentation/nats/nats-2.17/library/README.md @@ -7,7 +7,7 @@ Provides OpenTelemetry instrumentation for [NATS Client](https://github.com/nats ### Add these dependencies to your project Replace `OPENTELEMETRY_VERSION` with the [latest -release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-nats-2.17). +release](https://central.sonatype.com/artifact/io.opentelemetry.instrumentation/opentelemetry-nats-2.17). For Maven, add to your `pom.xml` dependencies: